Back

Benchmarking Nodejs and Golang

MD Rashid Hussain
MD Rashid Hussain
Dec-2022  -  10 minutes to read

In this test, I made two servers, one with node (express) and the other with golang (gin) and tested out their performance on handling requests. I used Apache bench for this and logged the results into a log file at the end. Let me give you a glimpse of the servers made to test out.

Make a directory named nodejs and yarn init inside it, this will spit out a package.json file which will contain all the dependencies of the app. Next create a folder to collect all the logs we are going to do afterwards. I made a folder named express/logs to store all the logs. Then I gave sufficient permissions to the log files to be able to store logs inside it (just a UNIX thing). Then I wrote a basis javascript file named express.js which will act as the server in our case.

mkdir nodejs && cd nodejs
yarn init -y && yarn add express
mkdir express && cd express && mkdir logs && cd logs
touch test1.log test2.log test3.log
chmod 775 test1.log test2.log test3.log
// express.js
const express = require('express');
const app = express();
 
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
 
let hitCount = 0;
 
app.get('/', (req, res) => {
  hitCount += 1;
 
  const healthcheck = {
    uptime: process.uptime(),
    message: 'OK',
    timestamp: Date.now(),
  };
  console.log('HIT:', hitCount, healthcheck);
  console.log();
  try {
    return res.status(200).json({ message: 'Hello World' });
  } catch (error) {
    healthcheck.message = error;
    return res.status(503).send();
  }
});
 
app.listen(3000, () => console.log('Running on port 3000'));

Pretty much everything goes the same in case of Golang. I created a folder golang and go mod init inside it. This will create a go.mod file to store all the dependencies and their versions for the project. A github.com remote url is given so as to be able to convert to a module and be able to store it on github (just in case). This will enable other people to be able to download our code as a module and use it in their codebases. Then as earlier, we created log files and gave them relevant UNIX permissions. Then I wrote a basis go file named gin.go which will act as the server in our case.

mkdir golang && cd golang
go mod init github.com/m3rashid/load-testing-golang
go get github.com/gin-gonic/gin
mkdir gin && cd gin && mkdir logs && cd logs
touch test1.log test2.log test3.log
chmod 775 test1.log test2.log test3.log
// gin.go
package main
 
import (
	"fmt"
	"net/http"
	"time"
 
	"github.com/gin-gonic/gin"
)
 
var startTime time.Time
 
func uptime() time.Duration {
	return time.Since(startTime)
}
 
func init() {
	startTime = time.Now()
}
 
type HealthCheck struct {
	uptime    string
	message   string
	timestamp string
}
 
func main() {
	r := gin.Default()
 
	r.GET("/", func(c *gin.Context) {
		healthCheck := HealthCheck{
			uptime:    uptime().String(),
			message:   "OK",
			timestamp: time.Now().Format(time.RFC3339),
		}
 
		fmt.Printf("HealthCheck: %+v", healthCheck)
		c.JSON(http.StatusOK, gin.H{"message": "Hello World"})
	})
 
	r.Run(":4000")
}

Since, golang is a compiled language, I compiled it to a binary by running go build golang/gin.go. At the end, I did three tests, and the load/concurrency goes as follows. Also, One may argue that Nodejs is single threaded and hence cannot use all the available resources of the host machine, so just for those guys, I tested dedicated using PM2 on cluster mode at max number of connections.

testLoad(total req)Concurrency (req/sec)
1500100
210000500
31000001000

Then I tested the servers


ParameterNodeJsGoNodeJs (PM2)
Concurrency Level:100100100
Time taken for tests [s]:0.4110.0960.746
Complete requests:500500500
Failed requests:41037
Total transferred [bytes]:13545674000135961
HTML transferred [bytes]:319561250032461
Mean request [#/sec]:1216.915230.84669.86
Time per request [ms]:82.17619.117149.285
Time per request (mean, across all conc. requests) [ms]:0.8220.1911.493
Transfer rate [KB/s]:321.95756.02177.88
Param (NodeJs)minmean[+/-sd]medianmax
Connect:042.7411
Processing:47222.372116
Waiting:34518.44784
Total:147620.973116
Param (Go)minmean[+/-sd]medianmax
Connect:061.6511
Processing:3129.4840
Waiting:099.2537
Total:7189.71344
Param (NodeJs, PM2)minmean[+/-sd]medianmax
Connect:011.405
Processing:1013125.0126205
Waiting:1013025.2126204
Total:1413224.6127205

ParameterNodeJsGoNodeJs (PM2)
Concurrency Level:100100100
Time taken for tests [s]:6.4411.3596.776
Complete requests:100001000010000
Failed requests:99301024
Total transferred [bytes]:270889714800002718865
HTML transferred [bytes]:638897250000648865
Mean request [#/sec]:1552.457358.631475.73
Time per request [ms]:64.41413.58967.763
Time per request (mean, across all conc. requests) [ms]:0.6440.1360.678
Transfer rate [KB/s]:410.691063.55391.83
Param (NodeJs)minmean[+/-sd]medianmax
Connect:021.0210
Processing:9627.86297
Waiting:1489.44892
Total:9647.86498
Param (Go)minmean[+/-sd]medianmax
Connect:051.3511
Processing:185.1756
Waiting:065.1554
Total:1134.81359
Param (NodeJs, PM2)minmean[+/-sd]medianmax
Connect:000.506
Processing:7677.067125
Waiting:3676.966124
Total:9676.867125

ParameterNodeJs ServerGo ServerNodeJs (PM2)
Concurrency Level:500500500
Time taken for tests [s]:64.22812.52354.266
Complete requests:100000100000100000
Failed requests:91134010001
Total transferred [bytes]:271888911480000027188887
HTML transferred [bytes]:648889125000006488887
Mean request [#/sec]:1556.967985.541842.76
Time per request [ms]:321.13962.613271.332
Time per request (mean, across all conc. requests) [ms]:0.6420.1250.543
Transfer rate [KB/s]:413.401154.16489.28
Param (NodeJs)minmean[+/-sd]medianmax
Connect:0105.5940
Processing:4331127.9307622
Waiting:323444.7239460
Total:4332028.4316632
Param (Go)minmean[+/-sd]medianmax
Connect:0276.32750
Processing:0359.734170
Waiting:0269.124158
Total:0629.462198
Param (NodeJs, PM2)minmean[+/-sd]medianmax
Connect:001.5026
Processing:1527027.8270400
Waiting:227027.8269399
Total:2827127.3270400

ParamGolangNodeJsNodeJs (PM2)
Failed Requests:Test 1: 0%Test 1: 8.2%Test 1: 7.4%
-Test 2: 0%Test 2: 9.93%Test 2: 10.24
-Test 3: 0%Test 3: 91.13%Test 3: 10.00
Time per request (mean over all tests):31.77ms155.90ms162.79ms
Requests per second (mean over all tests):6858.341442.111329.45
Transfer rate (mean over all tests):991.24 KB/s382.01 KB/s353.00 KB/s
Waiting time (mean over all tests):13.67ms109ms155.67ms

In each of the cases above, Golang simply rocks in each of the parameters above. The main parameters of our concern are :

  • Failed Requests (Most important): Even in the test 1 category, Nodejs fails to deliver a good performance required for a server side application.
  • Number of bytes transferred (should be lesser as both the servers are sending the exact same thing)
  • Requests per second (determines the actual concurrency throughput): Should be maximum
  • Time per request (Golang has an edge here because it is compiled and nodejs (javascript) is interpreted): Should me minimum. Responses must be as quick as possible so as to provide a good user experience
  • Transfer rate (how much the server is capable of): Even if both the test is done on the same network and with same bandwidth, Golang provides faster delivery (not by a very high margin)
  • Waiting time (ms, Average time to wait before handling the request):Requests need to be fulfilled as quick as possible, waiting time must be less as a whole