Javascript is everywhere!
Maybe you're not surprised to know that JS enables us to build nearly any sort of application for any platform.
But what I never heard before is the useability to write Javascript Code to execute load and performance testing, and even better collect metrics from these tests to understand the application behavior.
Tell me more...
The tool that has given us this kind of power is 🔧K6🔧.
But what is this exactly? From k6 official site:
k6 is a developer-centric, free, and open-source load testing tool built for making performance testing a productive and enjoyable experience.
k6 is written in Go but exposes a Javascript API allowing us to write javascript code to interact with.
Installation 💾
There's a lot of ways to get the k6 binary, go ahead into their official guide and get one of them.
You can also follow this guide to run your first load test even in a local environment.
Nodejs vs Golang
Let's use k6 to compare the performance from a simple API written in Golang and the other one in Nodejs.
- The Node app uses the well known
expressjs
framework ➡️ app.js - The Go app uses the
iris
framework ➡️ main.go
Just to Make it quite clear, the App will consume considerable CPU resources being a disadvantage to Nodejs API 😬
Both apps will calculate the nth value of Fibonacci sequence passed as path param, e.g apiUrl/fibonacci/7
must return:
{
"fibonacci": 34
}
There's a health check endpoint either: apiUrl/hc
I encourage you to call this endpoint manually during the second minute of the load test.
Test Scenario 📋
We're testing just an API endpoint in isolation to see how the endpoint performance trends over time. The scenario is as follow:
- In the first minute of the test the system will ramp up until achieving 100 virtual users.
- Each virtual user will make an HTTP Request to the endpoint:
apiUrl/fibonacci/9999999
every 100ms. - Holding the step
2
for two minutes. - The last minute will ramp down virtual users to 0.
- k6 will take 4 minutes to run the test described above.
- The test have a simple goal, declared in options object:
http_req_duration: ['p(95)<1000']
which means, 95% of the requests made must take less than 1 second.
k6-test.js
import http from 'k6/http';
import { sleep } from 'k6';
const SLEEP_DURATION = 0.1;
export let options = {
stages: [
{ duration: "1m", target: 100 },
{ duration: "2m", target: 100 },
{ duration: "1m", target: 0 }
],
thresholds: {
http_req_duration: ['p(95)<1000'] // 99% request must complete below 1s
}
}
const BASE_URL = __ENV.API_BASE === "GOLANG" ? "http://localhost:8080" : "http://localhost:9090"
const HEADERS = { "Content-Type": "application/json" }
export default () => {
http.get(</span><span class="p">${</span><span class="nx">BASE_URL</span><span class="p">}</span><span class="s2">/fibonacci/9999999
);
sleep(SLEEP_DURATION);
}
Running the load test with k6 💪
1.Clone the 📜example code📜 and execute docker-compose up
. Both apps will expose these endpoints:
App | Endpoints | Port |
---|---|---|
Golang |
/hc /fibonacci/n
|
8080 |
Nodejs |
/hc /fibonacci/n
|
9090 |
2.You can run both tests in parallel opening two terminals or one at time, it's up to you.
- To run the Golang load test in the root project folder, execute:
k6 run ./k6-test.js -e API_BASE=GOLANG
- And to run the Nodejs load test:
k6 run ./k6-test.js
I know that it's a really simple test but I like to keep that phrase in mind:
Simple testing is better than no testing
Show me the winner
k6 output for Nodejs
app
k6 output for Golang
app
- You can call the health check endpoint of both apps during the load test to check the response time.
1.The HTTP Request to Nodejs takes on average 15s
and the Golang app 237ms
.
2.Due to this HTTP request duration, in the same amount of time Golang handles 52103
more requests than Nodejs.
Golang won, but how ? 🤔
Analyzing containers resource usage statistics during the tests, you can see the Golang container uses more than one CPU to handle the requests while Nodejs uses just one CPU.
Another important point is that Golang
uses a separate goroutine per HTTP request handling the requests in a concurrency way.
Conclusion
In case that you have executed both tests will figure out that Go surpasses NodeJS in this scenario. But why?
Both apps have one main task which is to calculate the nth sequence of a Fibonacci number, and depending on the nth number the task can consuming considerable CPU resources, and Nodejs is not designed for that.
Therefore using k6, you'll be able to catch performance regression and problems earlier. You can also write stress tests to check whether the application gradually scale up its infrastructure, allowing you to build resilient systems and robust applications.
Top comments (8)
but by default goland uses multiple cores on your CPU where as node uses one. wouldn't it be more fair to run a pm2 cluster for node to utilize also multiple cores?
There are multiple optimizations that may be done in go as well to make it even more performant. To me, the point of the article is not to crown one language superior, so "fairness" really is beside the point, but I might be wrong.
All it says is that if you want to do CPU-bound work and split the workload across multiple threads/cores, then go will definitely be the better choice as you get really powerful concurrency primitives provided out of the box.
I think would have been interesting to see also how node handles loads if using multiple cores too as who knows it might be even more performant.
Exactly!
Thanks for sharing. I have never stress-tested APIs or servers before however, if I do, I'll definitely look into k6. It does seem like a no-hassle tool to get you started.
K6 seems new for me, thanks for this info.
Big fan of K6. Very simple and easy to use and nice looking dashboards very straight forward using grafana.
Good point about grafana dashboard I'll definitely try it.