PHP or Go

Answering this question depends on your demands, but in case you need high performance, the choice is clear.

First, let’s shed some light on our own experience. We had to create a RESTful API for an ad platform (which we are actively developing in the last 3 years) which is currently handling around 50 millions ad events (impressions and clicks) per day. The dev team included two seasoned PHP backend developers, the platform itself is also developed with PHP, so it made sense to decide to stick to that and write the APi in PHP.

As you are aware PHP is an easy language, relatively fast and provides plenty of useful tools. So we built our API in PHP and were pleased with it - it worked, looked nice but still we decided to give Go a try. You may wonder what was the reason for our decision. The answer is we had some concerns about the performance of the API and were looking for alternatives. Although we had no previous experience with Go we decided to use this language and to rewrite the API in it.

My personal opinion about Go is that it is an interesting language, similar to C but without too many integrated tools. On the other hand it has plenty of packages and libraries. It’s strictly typed language and if your previous experience has nothing to do with such kind of programming languages than you may have a hard time. But strict as it is Go teaches you some discipline.

Our first job was to find some useful packages for the REST service. The requirements the package had to meet were speed and to have some rate limiters.

After doing some research and initial tests we decided to try 3 packages – Chi (https://github.com/go-chi/chi), Mux (https://github.com/gorilla/mux) and Gin (https://github.com/gin-gonic/gin). They are quite similar, but Gin has a lots of features. Since we looked for performance we decided to make a simple test with response static JSON using 100000 request and 100 concurrent connections.

The results were as follows:

Operation/Data Mux Gin Chi
Time taken for tests 6.952 seconds 9.334 seconds 7.142 seconds
Requests per second 14384.26 [#/sec] 10712.98 14000.81 [#/sec]
Time per request 6.952 [ms] 9.334 [ms] 7.142 [ms]
Time per request 0.070 [ms] 0.093 [ms] 0.071 [ms]
Percentage of the requests served within a certain time (ms)
50% 7 8 7
66% 7 10 7
75% 7 11 8
80% 8 12 8
90% 9 15 9
95% 10 18 11
98% 12 22 13
99% 14 25 15
100% 31 48 33

Looking at the test results our choice was to go with Mux – it was a bit faster than Chi and nearly two times faster than Gin.

Now the trickiest part of the work was ahead us - we had to write the API.

The Go's package system is a little bit strange to me, and we were as well not certain how to structure the project. Looking at it now I assume we could have used different approach but even the way it turned out the design looks nice.

When we were ready with our Go RESTful API, we were eager to perform tests to compare it with PHP's performance. In the beginning the results were almost the same. That was quite a surprise, because the test on the local machine showed different values. We started looking for the reason behind these results and in the end we found it. We use Nginx as reverse proxy and it has some limits in configuration. Hence our first results were not what we expected them to be. So we resolved that issue and did the test one more time.

For the test we used 1000 requests and 100 concurrency:

Operation/Value Nginx+PHP+FMP Nginx+Go Go as Server
Time taken for tests 1.336 seconds 0.940 seconds 0.784 seconds
Complete requests 1000 1000 1000
Failed requests 0 0 0
Requests per second 748.26 [#/sec] 1064.02 [#/sec] 1275.25 [#/sec]
Time per request 133.643 [ms] 93.983 [ms] 78.416 [ms]
Time per request 1.336 [ms] 0.940 [ms] 0.784 [ms]
Transfer rate 1117.11 [Kbytes/sec] 1352.07 [Kbytes/sec] 1577.46 [Kbytes/sec]

Although there was a striking difference, we decided to try with more queries - 10000 requests and 100 concurrency:

Operation/Value Nginx+PHP+FMP Nginx+Go Go as server
Time taken for tests 11.364 seconds 9.068 seconds 7.414 seconds
Complete requests 10000 10000 10000
Failed requests 1317 0 0
Requests per second 879.94 [#/sec] 1102.74 [#/sec] 1348.78 [#/sec]
Time per request 113.645 [ms] 90.683 [ms] 74.141 [ms]
Time per request 1.136 [ms] 0.907 [ms] 0.741 [ms]
Transfer rate 1313.70 [Kbytes/sec] 1400.80 [Kbytes/sec] 1668.46 [Kbytes/sec]

At the higher load the PHP started to return failed requests, so we did another test without Nginx using only Go. This time we used 10000 requests and 1000 concurrency for the test and the results were interesting:

Operation/Value Go as server
Time taken for tests 5.819 seconds
Complete requests 10000
Failed requests 0
Requests per second 1718.50 [#/sec]
Time per request 581.904 [ms]
Time per request 0.582 [ms]
Transfer rate 1500.79 [Kbytes/sec]

It was obvious that Go without Nginx worked faster and steadier. This made it clear how to proceed further.

We were clear to go with our next task. Since we wanted to use different domains on one port we had to find small and fast proxy for our Go API. Which we did.

So in case you need to build a high loaded application operating tons of requests we strongly recommend Go and our approach if you find it useful.