Blog
12 min read
Unleash k6: The 4-Step Beginner's Roadmap to Load Testing

In our digital landscape, seamless user experiences reign supreme. A sluggish or unresponsive app erodes customer satisfaction swiftly. Integrating k6 load testing into your development cycle helps proactively identify and rectify performance bottlenecks, ensuring your system handles real-world demands without compromising functionality or usability.

Step 1: Understanding Load Testing and k6

Load testing simulates multiple concurrent users accessing your system to gauge its performance under real-world conditions. It focuses on verifying and validating an application’s performance when it has a significant volume of tasks to process. A straightforward comparison can help grasp load testing - picture throwing a party at your place to evaluate if it can handle a large crowd comfortably, without running short on food, beverages or restroom access.

However, load testing isn’t synonymous with performance testing; it’s a focused methodology under the performance testing umbrella.

Enter k6 - an open-source load testing tool gaining traction among developers. It offers a user-friendly scripting interface to craft custom test cases mimicking authentic user journeys. With k6’s scripting prowess, simulate scenarios like multiple users browsing your e-commerce site, adding products, and completing purchases simultaneously. This insight optimizes system performance under high traffic.

k6 offers a diverse range of load testing methodologies to cater to various scenarios. These methodologies include but not limited to:

  • Stress Testing: These simulations push the application to its limits by replicating the highest anticipated traffic volumes during peak periods, such as the busiest times of the day or seasonal surges. In contrast to average load tests that simulate typical traffic patterns averaged over a week, a month, or longer, stress tests focus on the maximum load the application may encounter.
  • Spike Testing: These tests recreate situations where the application experiences a sudden, massive surge in traffic, allowing you to evaluate how the application responds to such bursts.
  • Endurance Testing (Soak Testing): With longer durations than average or peak tests, endurance testing evaluates the application’s performance under sustained loads over extended periods. This approach helps identify potential bottlenecks, such as resource management issues, that may only manifest after prolonged usage. By leveraging k6’s versatile load testing capabilities, you can comprehensively analyze your application’s performance tailored to your specific needs, ensuring seamless user experiences even under demanding conditions.

Step 2: Setting Up the k6 Environment

Configuring k6 is seriously easy-peasy. Just follow some basic steps, kind of like making a sandwich, and you’ll be cruising and ready to smash through any load testing hurdles.

The first crucial move is making sure your web app is prepped and ready to get tested on. The next step is to install k6 and Node.js depends on the operating system you are using.

— k6: https://grafana.com/docs/k6/latest/set-up/install-k6/

— Node.js: https://nodejs.org/en/download/package-manager/current

Make sure we have installed k6 and Node.js properly by using command:

▸ sh
k6 --version
▸ sh
node --version

Step 3: Writing Your First k6 Test Script

Creating a solid plan is vital before diving into your first k6 load testing script. Proper planning makes the tests productive, streamlined, and aligned with your app’s performance targets. Consider these key points:

  1. Clarify Testing Goals and Critical Scenarios:
    • Explicitly state the aims of your load testing - are you checking max capacity, locating bottlenecks, or validating performance in specific situations? Determine the essential user journeys and scenarios requiring examination, like browsing catalogs, making purchases, or administrative workflows.
  2. Define Performance Metrics and Pass/Fail Criteria:
    • Establish the critical performance benchmarks that need to be tracked, encompassing aspects like response durations, system throughput capacity, frequency of errors or failures, and resource utilization levels. Delineate explicit boundaries for each of these metrics, distinguishing between satisfactory and unsatisfactory performance thresholds. These predefined criteria will serve as the guiding principles for evaluating the acceptability of your application’s behavior under simulated load conditions.

Before you start scripting, let’s set up a dedicated project folder and initialize a new Node.js project. Open your terminal and navigate to the desired location, then follow these steps:

  1. Create a new folder for your k6 project:
▸ sh
mkdir k6-load-tests && cd k6-load-tests
  1. Initialize a new Node.js project:
▸ sh
npm init -y
  1. Open the package.json file and change the "type" field to "module":
▸ package.json
{
  "type": "module"
}
  1. Install the required k6 packages:
▸ sh
npm install k6 && npm install -D @types/k6
  1. Then create 4 files:
▸ sh
touch minimal-test.js stress-test.js spike-test.js soak-test.js

Having formulated your comprehensive testing approach, the next phase entails transcribing your strategy into executable scripts. The k6 tool leverages JavaScript (or its superset, TypeScript) as the programming language for crafting test scripts, thereby offering accessibility to developers well-versed in these widely adopted languages. To illustrate, let us consider an exemplary script that emulates a straightforward user interaction scenario on a WordPress website:

▸ minimal-test.js
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  vus: 10, // 10 virtual users
  duration: '30s',
};

export default function () {
  http.get('https://rokimiftah.me');
  sleep(1);
}
▸ stress-test.js
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 100 }, // Ramp-up to 100 virtual users over 2 minutes
    { duration: '5m', target: 100 }, // Stay at 100 virtual users for 5 minutes
    { duration: '2m', target: 200 }, // Ramp-up to 200 virtual users over 2 minutes
    { duration: '5m', target: 200 }, // Stay at 200 virtual users for 5 minutes
    { duration: '2m', target: 0 }, // Ramp-down to 0 virtual users over 2 minutes
  ],
};

export default function () {
  http.get('https://rokimiftah.me');
  sleep(1);
}
▸ spike-test.js
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  stages: [
    { duration: '1m', target: 50 }, // Ramp-up to 50 virtual users over 1 minute
    { duration: '30s', target: 500 }, // Spike to 500 virtual users for 30 seconds
    { duration: '1m', target: 50 }, // Ramp-down to 50 virtual users over 1 minute
  ],
};

export default function () {
  http.get('https://rokimiftah.me');
  sleep(1);
}
▸ soak-test.js
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  stages: [
    { duration: '5m', target: 100 }, // Ramp-up to 100 virtual users over 5 minutes
    { duration: '2h', target: 100 }, // Stay at 100 virtual users for 2 hours
    { duration: '5m', target: 0 }, // Ramp-down to 0 virtual users over 5 minutes
  ],
};

export default function () {
  http.get('https://rokimiftah.me');
  sleep(1);
}

Alright, we have successfully created the required scripts. Now, let’s break down each part of these scripts for better comprehension.


import http from 'k6/http';
import { sleep } from 'k6';

To begin, we take the http component from k6/http and the sleep function from k6. Meanwhile, the sleep function replicates the pause a user takes when not actively making requests, such as when reading page material or moving between different pages.


export const options = {...};

Next up, we define the parameters for the load testing scenario within the options object. The vus (short for virtual users) setting dictates how many simulated users will be involved, whereas the duration setting controls how long the test will run. We use the stages setting to control the number of simulated users that increase and decrease during the test execution for the stress-test.js, spike-test.js, and soak-test.js scenarios.

Options reference: https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/


export default function () {
  http.get('https://rokimiftah.me');
  sleep(1);
}

The export default function () { ... } function serves as the entry point for the test scenario. This function will be executed once for each virtual user. Inside this function, we call http.get() to make an HTTP GET request to the specified URL. In our example, we access the https://rokimiftah.me URL.

After the HTTP request, we invoke sleep(1) to simulate a 1-second think time. This represents the period when an actual user might be reading the page content or performing other actions before proceeding to the next step in the user flow.


By understanding each part of these scripts, we can observe how k6 assists us in simulating user load and measuring our application’s performance across various load testing scenarios. The minimal-test.js scenario runs 10 virtual users for 30 seconds, which is suitable for basic testing. stress-test.js gradually increases the load up to 200 virtual users, allowing us to evaluate the application’s performance under high load conditions. spike-test.js simulates a traffic spike with 500 virtual users for 30 seconds, helping identify performance issues during sudden spikes in demand. And soak-test.js runs 100 virtual users for 2 hours, which is useful for detecting resource leaks or other performance issues that may arise after prolonged usage periods.

These scripts are just basic examples, and we can further expand them by adding additional HTTP requests, authentication, form submissions, and other relevant user actions specific to our application.

Step 4: Executing and Analyzing k6 Test Results

Once you have crafted your k6 test scripts, the next crucial phase involves executing them and meticulously analyzing the test results. Here are the essential aspects to consider when running and interpreting your k6 load tests:

  1. Running k6 Tests
    • Run k6 using the command k6 run [script_file_name].
    • Throughout the duration of the test run, k6 will continuously present real-time data and metrics, including the count of simulated virtual users, the rate at which requests are being processed, the observed response durations, and any other pertinent statistical information relevant to the test scenario.
▸ sh
k6 run minimal-test.js
  1. Test Results
    • Once the test run has concluded, k6 will present a comprehensive summary encapsulating the test results, encompassing pivotal performance indicators. This synopsis will incorporate critical metrics such as the rate at which requests were processed, the observed response durations, the frequency of errors or failures encountered, and any other pertinent statistical data relevant to evaluating the system’s performance under the simulated load conditions

  1. Result Output
    • In addition to the real-time display, k6 offers the capability to persist the test results in formats conducive to human comprehension, such as JSON or CSV. This feature facilitates further analysis and visualization of the data obtained during the test execution. To leverage this functionality, you can invoke the k6 run command with an additional flag, specifying the desired output format and file name. The command syntax would resemble: k6 run [script_file_name] --out [output_format]=[output_file_name].[output_extension].
▸ sh
k6 run minimal-test.js --out json=minimal-test.json
  1. Result Analysis
    • Stability and Reliability: The application successfully handled a consistent load of 10 concurrent virtual users (VUs) over a 30-second period without any failures. This is evidenced by the 0% failure rate (http_req_failed: 0.00%) and the stable VU count throughout the test.

    • Throughput: The system processed an average of 8.39 requests per second, totaling 260 requests over the test duration. This throughput remained consistent, indicating stable performance under the given load.

    • Response Times:

      — The average response time (http_req_duration) was 151.8ms, with a median of 130.78ms.

      — 90% of requests were completed within 220.49ms, and 95% within 275.19ms.

      — The maximum response time recorded was 564.72ms. These figures suggest reasonably good performance, with most requests being processed quickly.

    • Network Behavior:

      — The average time spent waiting for a server response (http_req_waiting) was 136.43ms, constituting the majority of the total request duration.

      — Initial connection times (http_req_connecting) were minimal, averaging just 318.26µs, indicating efficient connection reuse.

    • Data Transfer: The application transferred data at a rate of 16 kB/s received and 556 B/s sent, which appears appropriate for the nature of the requests.

    • Iteration Performance: Each iteration, likely representing a full user interaction cycle, took an average of 1.18 seconds, with 90% completing within 1.25 seconds.

Overall, the application shows good performance characteristics for the tested load of 10 concurrent users, with no failures and generally quick response times.

Conclusion

In the pursuit of delivering seamless digital experiences, load testing with k6 emerges as a game-changer. This powerful open-source tool equips developers with the ability to proactively identify and address performance bottlenecks, ensuring applications withstand real-world demands. By mastering the four-step roadmap outlined in this article, you gain invaluable insights into crafting targeted test scenarios, executing them with precision, and analyzing the results to make data-driven optimizations. Embrace k6, and unlock the full potential of your digital offerings, captivating users with unparalleled responsiveness and reliability.