Service-icon-list-icon

October 27, 2024

1. Introduction

In today’s digital world, where websites and applications play a crucial role in business success, it is essential to ensure that these platforms can handle high traffic without crashing. This is where load testing comes into the picture. Load testing involves simulating different user interactions and making API calls to assess how an application performs under high load conditions. In this article, we will explore the use of the K6 tool to perform load testing on a backend application and analyze its performance.

2. Understanding Load Testing

Load testing is a critical aspect of performance testing, where you test an application’s ability to handle expected user traffic. It helps identify bottlenecks, measure response times, and understand application behavior under different load conditions. By simulating user interactions and generating API calls, load testing evaluates the system’s performance and ensures its stability.

The K6 tool is an excellent choice for load testing backend applications. Let’s dive into how it works and explore different types of load testing scenarios.

Github Repository: https://github.com/Huzaifa-Asif/load-test-backend-with-k6

3. Types of Load Testing

In load testing, different test cases are executed to simulate various scenarios and analyze an application’s performance. Here are the main types of load testing that can be performed using K6:

3.1. Smoke Test

A smoke test is a basic test that checks if all APIs are responding correctly and the server is running fine. It is typically a short test lasting one or two minutes, ensuring that everything is working correctly before running extensive load tests.

import http from 'k6/http'
import { check, sleep } from 'k6'

const BASE_URL = 'https://api.stotte.no/api/v1';

// See https://k6.io/docs/using-k6/k6-options/
export const options = {
  vus: 3,
  duration: '1m',
  ext: {
    loadimpact: {
      name: 'Smoke Test',
    },
  },
}

export default function () {
  const data = { email: 'john.pattrick2@gmail.com', password: '123456' }
  let loginRes = http.post(`${BASE_URL}/organisation_user/login`, JSON.stringify(data), {
    headers: { 'Content-Type': 'application/json' },
  })

  check(loginRes, { 'success login': (r) => r.status === 200 })
  console.log(`Status Code: ${loginRes.status} - Response Body: ${loginRes.body}`)
  sleep(1)
}

3.2. Load Test

A load test simulates the expected number of users and traffic that an application might experience in a production environment. It can vary from 7 to 15 minutes or longer, depending on the desired duration. This test evaluates the entire application to measure its performance under realistic load conditions.

import http from 'k6/http'
import { check, sleep } from 'k6'

const BASE_URL = 'https://api.stotte.no/api/v1'

// See https://k6.io/docs/using-k6/k6-options/
export const options = {
  stages: [
    { duration: '2m', target: 20 }, // traffic ramp-up from 1 to 50 users over 2 minutes.
    { duration: '5m', target: 20 }, // stay at 50 users for 5 minutes
    { duration: '5m', target: 0 }, // ramp-down to 0 users
  ],
  ext: {
    loadimpact: {
      name: 'Load Test',
    },
  },
}

export default function () {
    const data = { email: 'john.pattrick2@gmail.com', password: '123456' }
    let loginRes = http.post(`${BASE_URL}/organisation_user/login`, JSON.stringify(data), {
      headers: { 'Content-Type': 'application/json' },
    })
  
    check(loginRes, { 'success login': (r) => r.status === 200 })
    console.log(`Status Code: ${loginRes.status} - Response Body: ${loginRes.body}`)
    sleep(1)
  }

3.3. Stress Test

A stress test evaluates how an application performs when subjected to high user traffic. It aims to analyze the application’s behavior under extreme conditions. Stress tests are generally shorter, around 15 to 20 minutes, and involve ramping up the number of virtual users to numbers higher than expected, such as tripling or even multiplying by ten the number of users.

import http from 'k6/http'
import { check } from 'k6'

const BASE_URL = 'https://api.stotte.no/api/v1'

// See https://k6.io/docs/using-k6/k6-options/
export const options = {
  stages: [
    { duration: '30s', target: 10 },   // Ramp up to 10 VUs in 30 seconds
    { duration: '1m', target: 100 },   // Ramp up to 100 VUs in 1 minute
    { duration: '1m', target: 150 },   // Ramp up to 150 VUs in 1 minute
    { duration: '10m', target: 200 },   // Stay at 200 VUs for 5 minutes
    { duration: '1m', target: 30 },     // Ramp down to 30 VUs in 1 minute
  ],
  ext: {
    loadimpact: {
      name: 'Stress Test',
    },
  },
}

export default function () {
  const loginPayload = { email: 'john.pattrick2@gmail.com', password: '123456' }
  for (let i = 0; i < 10; i++) {
    const loginRes = http.post(`${BASE_URL}/organisation_user/login`, JSON.stringify(loginPayload), {
      headers: { 'Content-Type': 'application/json' },
    })
    console.log(`loginRes - Status Code: ${loginRes.status} - Response Body: ${loginRes.body}`);

    if (loginRes.status === 200) {
      const userData = JSON.parse(loginRes.body)
      const sportListRes = http.get(`${BASE_URL}/organisation/get_sport_list?organisationId=${userData.data.user.organisation_id._id}`, {
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + userData.data.token,
          'User-Agent': 'PostmanRuntime/7.26.8',
        },
      })
      console.log(`sportListRes - Status Code: ${sportListRes.status} - Response Body: ${sportListRes.body}`)

      const statsRes = http.get(`${BASE_URL}/organisation/stats/${userData.data.user.organisation_id._id}`, {
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer ' + userData.data.token,
          'User-Agent': 'PostmanRuntime/7.26.8',
        },
      })
      console.log(`statsRes- Status Code: ${statsRes.status} - Response Body: ${statsRes.body}`)
    }
  }
}

3.4. Soak Test

A soak test focuses on analyzing the system’s performance when subjected to high load for an extended period. For instance, running the application under heavy load for one or two hours. It helps identify any issues that may arise from long-duration high load conditions.

import http from 'k6/http'
import { check } from 'k6'

const BASE_URL = 'https://api.stotte.no/api/v1'

// See https://k6.io/docs/using-k6/k6-options/
export const options = {
  stages: [
    { duration: '5m', target: 100 }, // traffic ramp-up from 1 to 100 users over 5 minutes.
    { duration: '30m', target: 100 }, // stay at 100 users for 40 minutes
    { duration: '1m', target: 0 }, // ramp-down to 0 users
  ],
  ext: {
    loadimpact: {
      name: 'Soak Test',
    },
  },
}

export default function () {
  const loginPayload = { email: 'john.pattrick2@gmail.com', password: '123456' }
  const loginRes = http.post(`${BASE_URL}/organisation_user/login`, JSON.stringify(loginPayload), {
    headers: { 'Content-Type': 'application/json' },
  })
  check(loginRes, { 'success login': (r) => r.status === 200 })
  console.log(`Status Code: ${loginRes.status} - Response Body: ${loginRes.body}`)
}

3.5. Spike Test

A spike test assesses how an application handles sudden surges in user traffic. It simulates a scenario where the number of users rapidly rises and then falls sharply. This test examines the application’s ability to respond efficiently and whether auto-scaling mechanisms are functioning correctly.

import http from 'k6/http'
import { check } from 'k6'

const BASE_URL = 'https://api.stotte.no/api/v1'

// See https://k6.io/docs/using-k6/k6-options/
export const options = {
  stages: [
    { duration: '2m', target: 2000 }, // fast ramp-up to a high point
    { duration: '1m', target: 0 }, // ramp-down to 0 users
  ],
  ext: {
    loadimpact: {
      name: 'Spike Test',
    },
  },
}

export default function () {
  const loginPayload = { email: 'john.pattrick2@gmail.com', password: '123456' }
  const loginRes = http.post(`${BASE_URL}/organisation_user/login`, JSON.stringify(loginPayload), {
    headers: { 'Content-Type': 'application/json' },
  })
  check(loginRes, { 'success login': (r) => r.status === 200 })
  console.log(`Status Code: ${loginRes.status} - Response Body: ${loginRes.body}`)
}

3.6. Breakpoint Test

A breakpoint test pushes the system to its limits, testing how it performs under unexpected stress conditions. By significantly increasing the number of virtual users and requests beyond the system’s capacity, this test determines the point at which the application starts failing or responding with errors. It helps evaluate system capacity and recovery mechanisms.

import http from 'k6/http'
import { check, sleep } from 'k6'

const BASE_URL = 'https://api.stotte.no/api/v1'

// See https://k6.io/docs/using-k6/k6-options/
export const options = {
  executor: 'ramping-arrival-rate', //Assure load increase if the system slows
  stages: [
    { duration: '20m', target: 20000 }, // just slowly ramp-up to a HUGE load
  ],
  ext: {
    loadimpact: {
      name: 'Spike Test',
    },
  },
}

export default function () {
  const loginPayload = { email: 'john.pattrick2@gmail.com', password: '123456' }
  const loginRes = http.post(`${BASE_URL}/organisation_user/login`, JSON.stringify(loginPayload), {
    headers: { 'Content-Type': 'application/json' },
  })
  check(loginRes, { 'success login': (r) => r.status === 200 })
  console.log(`Status Code: ${loginRes.status} - Response Body: ${loginRes.body}`)
  sleep(0.5)
}

3.7. Additional Test Cases

Apart from the recommended load testing scenarios, you can also create custom test cases based on your specific requirements. These may include response time test to evaluate API call response durations accurately or customized load testing scenarios to simulate various load patterns, such as increasing or decreasing user loads.

4. Using K6 for Load Testing

To conduct load testing using the K6 tool, you first need to install it. The K6 website provides easy installation instructions for various operating systems, such as Linux, macOS, Windows, and even Docker.

Download & Installation of K6: https://k6.io/docs/get-started/installation/

Once you have installed K6, you can begin scripting and executing various load testing test cases. Each test case is defined with specific test parameters and configurations.

5. Writing Load Testing Scripts with K6

In a load testing script, you start by importing the necessary libraries and defining test parameters.
For each test case, you can specify the API calls, request payloads, headers, and any necessary assertions. The script can also include sleep intervals to control request rates and measure response times accurately.

6. Running Load Tests with K6

To run a load test using K6, you execute the script using the K6 CLI. By specifying the script’s filename, you can initiate the load testing process. K6 provides a range of options and flags to customize the load testing execution and output.

Once the load test is running, K6 sends API requests and displays real-time logs, including response times, successful requests, and any failed requests. At the end of the test, K6 provides a summary report that offers insights into the test’s performance.

7. Analyzing Load Test Results

Analyzing load test results is crucial to identify performance bottlenecks and understand how an application behaves under different load conditions. K6’s summary report provides valuable data, including response times, success and failure rates, and data sent and received.

It is important to pay attention to response times, as these directly impact user experience. By monitoring metrics such as average response times, minimum and maximum response times, and percentiles (P90, P95, etc.), you can identify areas for improvement and ensure that the application meets performance goals.

Furthermore, load testing can help evaluate auto-scaling mechanisms and cloud infrastructure configurations. By monitoring cloud management services, such as adding or removing instances, you can validate the effectiveness of auto-scaling in response to increased user loads.

8. Conclusion

Load testing is an essential part of performance testing, evaluating an application’s ability to handle expected user traffic and ensuring its stability under variable load conditions. By simulating different types of load scenarios, load testing helps identify performance bottlenecks, analyze response times, and validate system capacities.

The K6 tool provides a powerful and user-friendly solution for load testing backend applications. With its scripting capabilities and various test case options, load testing becomes more accessible and efficient. By using K6, you can measure an application’s performance, analyze test results, and make informed decisions to optimize system performance and enhance user experience.

Load testing is a vital step in ensuring the reliability and efficiency of your applications. By investing time and effort in load testing, you can proactively address performance issues, maintain high availability, and provide a seamless user experience.

Blog-details-qoute