Carlos Dubón

Is Your API actually ready for user traffic?

May 25, 2024

Cover image for the "Is Your API actually ready for user traffic?" article.

You've built an amazing API to power your web application, but how do you ensure it can handle real-world user traffic when things start to ramp up?

Enter K6

K6 is a modern load-testing tool designed to help you test your API’s performance under various conditions. It’s open-source, simple to use, and works across Windows, macOS, and Linux. K6 supports HTTP/1.1, HTTP/2, and WebSockets, making it versatile for a wide range of APIs.

With K6, you write your test scripts in JavaScript and execute them through the terminal. Here’s an example of a basic GET request to an API endpoint:

// script.js
import http from "k6/http";
 
export default function () {
  http.get("https://api.example.com/");
}

You can run this test by saving it as script.js and running the following command in your terminal:

 k6 run script.js

Benchmark results

K6 provides detailed results, including response times, throughput, and error rates. This data helps identify bottlenecks in your API and guides improvements to ensure scalability.

What Does Performance Mean for Your API?

Before diving into testing, it’s essential to define what "performance" means for your API. Are you more concerned with response times, throughput, or error rates? This understanding shapes your testing strategy and success criteria.

For example, if you expect your API to handle 10 virtual users with responses under 200ms, here’s a simple test:

// script.js
import http from "k6/http";
import { sleep, check } from "k6";
 
export const options = {
  vus: 10,
  duration: "5m",
};
 
export default function () {
  const res = http.get("https://api.example.com/");
  check(res, {
    "status is 200": (r) => r.status === 200,
    "response time is less than 200ms": (r) => r.timings.duration < 200,
  });
  sleep(1);
}

Benchmark results

This test provides a baseline to ensure your API performs well under normal conditions. For continuous monitoring, you can integrate it into your CI/CD pipeline.

Load Testing

Load testing measures how your system handles expected traffic. It’s crucial to set thresholds based on percentages rather than expecting every request to succeed. For example, a valid threshold might be that 99% of requests complete within 200ms.

// script.js
import http from "k6/http";
import { sleep, check } from "k6";
 
export const options = {
  vus: 10,
  duration: "5m",
  thresholds: {
    http_req_failed: ["rate<0.01"], // Error rate < 1%
    http_req_duration: ["p(99)<200"], // 99% of requests under 200ms
  },
};
 
export default function () {
  const res = http.get("https://api.example.com/");
  check(res, {
    "status is 200": (r) => r.status === 200,
    "duration was <= 200ms": (r) => r.timings.duration < 200,
  });
  sleep(1);
}

However, load testing only shows performance under typical traffic. What happens when traffic spikes or stress increases?

Stress Testing

Stress testing pushes your API to extreme levels to evaluate performance under heavy load. You gradually increase the number of users and see how the API responds.

// script.js
export const options = {
  stages: [
    { duration: "1m", target: 100 }, // Ramp to 100 users
    { duration: "2m", target: 100 }, // Stay at 100 users
    { duration: "1m", target: 200 }, // Ramp to 200 users
    { duration: "2m", target: 200 },
    { duration: "1m", target: 500 }, // Ramp to 500 users
    { duration: "2m", target: 500 },
    { duration: "1m", target: 0 }, // Cool down
  ],
};

Stress testing

The goal here isn’t perfection but ensuring your API performs acceptably under pressure.

Spike Testing

When your API faces sudden traffic spikes, such as a concert ticket sale, traditional load or stress testing may not suffice. For this, use spike testing, which introduces a sudden surge in users over a short time.

// script.js
export const options = {
  stages: [
    { duration: "30s", target: 100 }, // Warm-up
    { duration: "1m", target: 2000 }, // Spike to 2000 users
    { duration: "10s", target: 2000 }, // Hold the spike
    { duration: "1m", target: 100 }, // Cool down
    { duration: "30s", target: 0 }, // End
  ],
};

Spike testing

Spike testing reveals how your API behaves when faced with a sudden, large load.

Soak Testing

While stress and spike tests focus on short bursts of traffic, soak testing is designed to uncover issues like memory leaks by applying sustained load over extended periods.

// script.js
export const options = {
  stages: [
    { duration: "1m", target: 200 }, // Ramp to 200 users
    { duration: "4h", target: 200 }, // Sustained load for 4 hours
    { duration: "1m", target: 0 }, // Cool down
  ],
};

Soak testing

Soak tests ensure your API remains stable over time without performance degradation.

Conclusion

Using K6, you can simulate real-world scenarios and ensure your API performs as expected under various conditions. Whether you're preparing for normal traffic, spikes, or sustained heavy loads, defining performance metrics and using the right tests is key to guaranteeing your API is truly ready for user traffic.

So, is your API ready for the real world?

Further Reading