Gin Gonic May Be 40x Faster Than Martini, But It Is Not Better

One of my projects required speed (as most should), the most piece intensive being redirects, and while it could deliver I couldn’t transition 100%.

Gin Gonic came out based off the popularity of the Martini framework, boosting some nice graphs of impressive stats saying it’s close to Martini, which you can see I have written about extensively. I briefly drank the juice of fearing Martini may not be able to scale when push came to shove because it’s “slow” compared to other frameworks, and looked down upon by many developers, I decided to give Gin a shot since it included some middleware that would ease the process (templating, redirects, binding) of transitioning over quickly.

Templating

One of the first issues I ran into right away was when you are creating your application, by default it’s in a “debug” environment which is fine, but there’s a massive bug where templating does not work correctly. It’s a documented bug with a fix in place, but even after running go get -u it didn’t seem to work for me. The only way I was able to get templates to work where it could fetch all of the files and place them in a cache was to run the application in production mode while developing. This wasn’t a huge deal, I just had to make sure my environment variable was set and I lost some routes debugging, but in general it wasn’t a deal breaker.

Router

The deal breaker that made me revert back was ultimately not the fault of Gin, but the fault of httprouter which is how I decided to use Gin in the first place because of it’s performance stats. To get performance, httprouter unfortunately does not support regex patterns in it’s URL structure. So something such as:

router.GET("/(?P<key>[a-zA-Z]+).js", injectJavascript)

Which can take a URL such as www.example.tld/keyname.js and translate the keyname into a parameter I can search in a database or do what I want to output a rendered JS file on the fly. This is something I’ve done when I used to use PHP and was able to set up routes easily with any framework I used previously along with Apache/Nginx, but httprouter doesn’t support anything close to getting a route like that (if I am wrong, please let me know).

What Could I Have Done?

There are a few things I could have done to get around this issue, which I didn’t. For example:

  • Pre-render the JS files - I could have had an application save static JS files when things were updated in the backend, but this causes some issues because I like to keep my backend code and frontend code separate since I consider them different applications, and I don’t want to need to depend on the backend to communicate with the front-end in case there is a conflict for any reason.
  • Use Apache/Nginx to create a rewrite rule If I was hosting on my own server, I could easily do that, but this project is hosted on Heroku because I do enough server management in my day job that I’d prefer this project to be managed by someone else. I looked briefly into setting up rewrite rules for Heroku, but they seemed language/framework specific, and since Go needs a buildpack to begin with to be supported on Heroku, I was coming to the conclusion it may not be something I could apply quickly.
  • Use a different route scheme for these files Again, another great idea was to not use a dynamic route and make it look like a static JS file, it could be deceiving to a developer that comes along later that is looking for those JS files, I could have probably rewritten the router as /js/:keyname and been done with it, but these script filenames are in production.
  • Use the CDN to rewrite the path to a new route This is actually something I could have done, but again the time investment wasn’t worth it to me. If I used a different CDN service, a server changed, or something happened where the application was deployed in unknown circumstances then it really wouldn’t be a “just run the binary and go!” scenario, because they’d have to set up a rewrite somehow.

Performance Gains

This application is hosted on Heroku, and since I’m using more than one dyno I have some nice stats in front of me so I was able to see the performance of gin vs martini when I had deployed the code. Unfortunately as you may have guessed, I deployed this bug with the bad router to production and it went unnoticed for a few hours. It was a curse, but also a blessing since I was able to get a real-world comparison of Gin vs. Martini.

A lot of the application is cached behind a CDN, for the most part the only thing that isn’t cached are the server-side redirects so we can direct the flow of incoming traffic. Heroku shows that we were receiving an average of 6k req/min. In that time, the CPU load dropped a bit when we launched with Gin. Go is pretty efficient for a few reasons and on average Gin showed a .2 CPU load median while Martini did show about a .25-.3 CPU load median.

The request timeout with Gin was 2ms median, and was 4ms median with Martini. That number is suspiciously low, so I have a feeling it’s the time that it takes Heroku’s router to talk to the application and receive a response, not a true representation of the browser response time. You could see it as the response doubled, but the response time is so low to begin with I’m not going to invest hours in a project that doesn’t call for a 2ms performance gain unless there is a huge gain in profit because of those 2ms.

Interestingly enough, according to Heroku Gin did consume more memory, a 24 MB footprint compared to Martini with a 13 MB footprint. The only parts of the code that were changed were necessary to support Gin, but none of the logic of saving to an in-memory cache of saving to the database had changed.

What’s Next?

I should have known up front that Gin wasn’t quite ready for primetime, as on the github readme.md it explains what needs to be done to reach a v1 release, but I figured I’d give it a shot. Unfortunately, the router issue is most likely not going to be fixed by the Gin team because it’s not really an issue or bug, but it’s a feature of httprouter.

If I am to try this again, and I probably will when I have another 2 hours to spare, I will probably try Negroni with Gorilla Mux since Negroni was created by the same developer of Martini, and I’ve had experience with the Gorilla libraries before and am a previous fan.

UPDATE: I updated the code to use negroni/mux, took about an hour. I will write a follow-up oncen I get some performance data

Last Updated: 2015-01-16 00:46:44 +0000 UTC



What are your thoughts on this?

Categories

RSS feed

Follow Doug On Social