How we decreased one of our APIs response time by 87% and used fewer resources
By: Aksel Arzuman
Tags: Golang, Software Development, Dotnet, Dotnet Core, Engineering

First things first, this is not a clickbait title!

Long story short for lazy readers, we rewrote our existing API by using the Go language. It was formerly written with C# and .Net Core.

I must say, the rewriting process and going live with it was hard and took longer than I thought. There are some new experiences that we gained and multiple key-points that made the new API successful and we wanted to share them with you.

How did the rewriting process start?

Well, it started because I was trying to improve myself in concurrency and I thought one of our APIs is suitable for it, and later on we can get the power of the parallelism as well. Moreover, this API makes lots of bitwise operations and we thought Go would perform better in it.

I must admit, it’s the hardest API that I dealt with and took around a week to finish the reverse engineering. After that, I started to rewrite it by using Go in my free time. I always approached it as a hobby project and never showed it to the team until the end.

How did the rewriting process go?

During the reverse engineering process, I found some code that needs to be refactored in the existing API. I’ve done all the refactoring in it then went live. Thus, I guaranteed that the new application will not have them and the code will be cleaner.

Keeping method names was a key part because it was easier to look for it between the apps. It was also easier to point the bugs while debugging.

At first, I did not want to implement concurrency and parallelism, because I wanted two APIs to be identical. However, I could not help myself and implemented them.

I shouldn’t have done that…

Writing unit tests in Go is a little bit more complicated than C#. It took me a while to get used to the mocking part. In my opinion, there are not that many articles(and best practices) about mocking methods/layers in Go. Nearly all the articles/posts that I read were about testing a method that does not include a method/layer to be mocked. I believe I should write something down about it and share my experience.

Luckily, the existing API had unit tests. Once our Lead Developer Yiğit said, “If an app has unit tests, it is really easy to port it to another language.”, and he is very right about it.

How did the testing process go?

Once I finished the unit tests, I started doing some monkey tests between the two APIs. This part was VERY fun because I was comparing the responses, response times, and resource usages as well.

Except for some little differences, responses were identical. For the rest of it, I had to make some fixes.

Go was performing absolutely better according to the response time. The existing API was giving response around in 800ms to 1.2secs and Go was giving response to the same request around in 120ms to 200 ms.

If it is enough for you to go live with the new API by only comparing the responses and response times, then you might do a big mistake. Once I checked the memory and CPU usage in the new API, I was sure that there was a problem. I have written multiple APIs using Go and none of them used more than 50mb of ram but this API was using around 200mbs. In the CPU part, it was using all the CPUs that are assigned to the container. I did not think there was a problem because I implemented parallelism.