MD Rashid Hussain
Cubicle

Follow

Cubicle

Follow
Benchmarking Nodejs and Golang Servers

Benchmarking Nodejs and Golang Servers

MD Rashid Hussain's photo
MD Rashid Hussain
·Dec 5, 2022·

10 min read

Play this article

Table of contents

  • Test 1
  • Test 2
  • Test 3
  • Results
  • Inference

In this test, I made two servers, one with node (express) and the other with golang (gin) and tested out their performance in 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 basic 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 the 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 to be able to convert it 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 basic 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. In 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 the max number of connections.

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

Then I tested the servers, The results came as follows.

Test 1

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 concurrent 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

Test 2

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 concurrent 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

Test 3

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 concurrent 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

Results

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

Inference

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 the good performance required for a server-side application.

  • Number of bytes transferred (should be lesser as both the servers are sending the 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 be minimum. Responses must be as quick as possible 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 the 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 quickly as possible, and waiting time must be less as a whole

 
Share this