
1. Introduction
As web applications scale, they must efficiently handle increasing concurrent users and requests. Load testing is crucial in evaluating an application’s performance under varying traffic conditions, identifying potential bottlenecks, and ensuring reliability.
For instance, an e-commerce platform with a surge of users during a flash sale. Without proper load testing, performance bottlenecks could lead to slow response times or even downtime, affecting user experience and revenue.
The k6 framework is a powerful, open-source load-testing tool that bridges simplicity with sophisticated performance analysis. Designed to enable developers to create comprehensive performance test scenarios, combining JavaScript scripting with efficient execution capabilities.
In this tutorial, we’ll explore the complete process of setting up k6, conducting load tests, analyzing performance metrics, and adhering to best practices for ensuring optimal system performance.
2. Setting up K6 for Load Testing
To begin load testing with k6, we need to install the tool and set up a test script.
2.1. Installation
Getting started with k6 is straightforward. We can install it using our system’s package manager.
For instance, on macOS, we can run the following command:
$ brew install k6
Once the installation is complete, let’s verify it by checking the version:
$ k6 version
The output confirms the installation:
k6 v0.56.0 (go1.23.4, darwin/arm64)
2.2. Test Script
Let’s develop a basic load test script named test.js that demonstrates k6’s core capabilities:
import http from 'k6/http';
import { sleep, check } from 'k6';
// test configuration
export const options = {
vus: 10, // simulate concurrent virtual users
duration: '30s',
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests must complete under 500ms
http_req_failed: ['rate<0.01'] // less than 1% request failure rate
}
};
// test scenario
export default function() {
// simulate request
const response = http.get('https://test-api.k6.io');
// validate response
check(response, {
'status is 200': (r) => r.status === 200,
'response time is acceptable': (r) => r.timings.duration < 500
});
// simulate user activity
sleep(1);
}
In this script, k6 defines test configurations with 10 virtual users and a 30-second duration. It ensures that 95% of requests are completed in under 500ms, with a failure rate of less than 1%.
The script then defines test scenarios in which GET requests are sent to an API endpoint, confirming that the response status is 200 and verifying that the response time stays below 500ms.
Also, to maintain a steady and realistic load on the system, each virtual user pauses for 1 second between requests.
Let’s execute the test script:
k6 run test.js
Once run, the script captures key performance metrics, validates response conditions, and provides a comprehensive report of the load test. This report includes response times, request rates, and any potential failures.
3. Executing Load Tests
By successfully setting up k6 and executing our first basic load test, we laid the groundwork for comprehensive performance evaluation.
With our initial configuration in place, we can now explore more sophisticated testing scenarios that mimic real-world application usage.
3.1. Advanced Testing Scenarios
k6 allows for flexible configuration of test scenarios through the options object, making it easy to simulate real-world traffic patterns. For example, we can define ramp-up and ramp-down stages to mimic user behavior:
export const options = {
stages: [
{ duration: '1m', target: 20 }, // ramp up to 20 users in 1 minute
{ duration: '3m', target: 20 }, // hold steady for 3 minutes
{ duration: '1m', target: 0 }, // ramp down to 0 users in 1 minute
],
};
This setup gradually increases the load, holds it steady for a specific period, and then gracefully decreases it, which helps test the application’s performance under realistic usage conditions.
3.2. Testing Multiple Endpoints
We can evaluate the performance of multiple API routes in a single script, ensuring comprehensive test coverage. Here’s an example:
export default function() {
// simulate diverse user interactions
const responses = http.batch([
['GET', 'https://test-api.k6.io/public/crocodiles/'],
['POST', 'https://test-api.k6.io/auth/basic/login/', JSON.stringify({username:'test',password:'1234'})]
]);
// validate multiple endpoint responses
check(responses[0], { 'Crocodiles loaded': (r) => r.status === 200 });
check(responses[1], { 'Login done': (r) => r.status === 200 });
}
The script demonstrates a multi-endpoint load test by simultaneously executing a batch of HTTP requests (fetching crocodiles and login) and performing synchronous response validation to ensure each endpoint returns a successful HTTP 200 status code.
3.3. Built-in Metrics
k6 automatically provides a range of insightful metrics, such as:
- http_req_duration: total request duration, including DNS lookup, connection, and response time
- http_reqs: total number of HTTP requests made
- http_req_failed: percentage of failed requests
- vus: current number of virtual users
- iterations: total number of script iterations executed
These metrics offer high-level and granular insights, setting the foundation of our performance analysis.
3.4. Custom Metrics
Custom metrics are a powerful feature in k6, enabling us to monitor specific aspects of application performance. For instance, we can track failed login attempts using a custom counter:
import { Counter } from 'k6/metrics';
let loginFailures = new Counter('login_failures');
export default function () {
let res = http.post('https://test-api.k6.io/auth/basic/login/', {
username: 'test',
password: 'wrong_password'
});
if (res.status !== 200) {
loginFailures.add(1); // Increment the counter for failed logins
}
}
Therefore, the features of k6 such as customizable scenarios, multi-endpoint testing, and detailed metric tracking equip us with the tools to evaluate our application performance.
4. Load Test Results
Analyzing load test results is vital for understanding application performance, identifying bottlenecks, and ensuring reliability.
4.1. Export Results
k6 allows us to export test results in various formats for further analysis. For instance, we can save the output in JSON format:
$ k6 run --out json=results.json test.js
This enables us to integrate k6 data with visualization tools like Grafana or to share detailed test reports.
A sample of k6 terminal output might look like this:
execution: local
script: test.js
output: json (results.json)
scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):
* default: 10 looping VUs for 30s (gracefulStop: 30s)
✓ status is 200
✓ response time is acceptable
✓ http_req_duration..............: avg=109.59ms min=104.52ms med=108.86ms max=190.22ms p(90)=113.03ms p(95)=114.66ms
✓ http_req_failed................: 0.00% 0 out of 270
vus............................: 10 min=10 max=10
vus_max........................: 10 min=10 max=10
running (0m30.3s), 00/10 VUs, 270 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs 30s
These metrics provide a clear overview of system performance, highlighting areas that may require optimization. By combining built-in metrics, detailed analysis, and exported reports, we can ensure a thorough evaluation of our application’s behavior under load.
4.2. Results Analysis
When analyzing load test results, let’s start by focusing on the response time analysis, which includes:
- Monitoring p95 and p99 percentiles to measure how the system performs for 95% and 99% of requests
- Tracking maximum response times to identify potential outliers that might impact user experience
- Looking for patterns of degradation, such as increasing response times under sustained load
Next, let’s consider error rate analysis, where we:
- Pay attention to the percentage of failed requests to assess overall reliability
- Analyze the types and patterns of errors, such as timeouts, 4xx, or 5xx responses
- Observe how the system behaves under load, particularly when it approaches its performance limits
Lastly, let’s examine resource utilization, which involves:
- Monitoring CPU usage patterns to detect over-utilization or bottlenecks
- Keeping an eye on memory consumption, particularly during high loads
- Tracking network throughput to identify constraints in bandwidth or API capacity
5. Best Practices and Common Pitfalls
Once results are in hand, following best practices and avoiding common pitfalls is essential for achieving meaningful and reliable outcomes.
5.1. Best Practices
To ensure effective load testing, we should keep the following in mind:
- Start small – gradually increase the load to avoid overwhelming the system and better understand application behavior under incremental stress
- Test realistic scenarios – simulate real-world traffic patterns, including ramp-up and ramp-down phases, to ensure test results reflect user behavior
- Automate tests – incorporate k6 tests into CI/CD pipelines for continuous performance monitoring and early issue detection as the application evolves
- Use tags for categorization – add tags to HTTP requests to organize and analyze results more effectively
5.2. Common Pitfalls
We need to avoid these common pitfalls to ensure accurate test outcomes:
- Overloading the test system – ensure the machine running k6 has adequate resources to handle the load, as underpowered systems can produce inaccurate results
- Unrealistic configurations – configure test parameters that reflect real-world usage, avoiding excessive virtual users or unrealistic request patterns
- Neglecting post-test analysis: without thorough result dissection, performance pitfalls remain hidden, and optimization potential is untapped
6. Conclusion
In this article, we discussed the setup of k6, the creation of realistic test scenarios, result analysis, and best practices to achieve reliable load-testing outcomes.
Load testing isn’t just about stress-testing an application – it’s about understanding its behavior and ensuring it can handle real-world traffic.
With k6’s intuitive scripting, built-in metrics, and flexible configurations, we can simulate realistic traffic patterns, accurately assess performance, and prepare our systems to scale effectively. We must start small, experiment with advanced scenarios, and refine our tests to adapt to evolving traffic demands.
The complete code for this article is available over on GitHub.
The post How to Execute Load Tests Using the k6 Framework first appeared on Baeldung.