Blogs/Technology

How to Simulate Authentic API Load Testing with k6?

Written byGoutham
Aug 4, 2026
12 Min Read
How to Simulate Authentic API Load Testing with k6? Hero
Too Long? Read This First

- Start with production evidence: Use API logs, analytics, traces, and business data to identify request rates, endpoint distribution, peak periods, and common user journeys.
- Choose the right workload model: Use VU-based executors for concurrent user journeys and arrival-rate executors when requests must continue arriving independently of response time.
- Test workflows, not isolated endpoints: Authentication, browsing, writes, retries, and follow-up requests often create more realistic backend pressure than one repeated GET request.
- Use representative data: Vary users, IDs, payload sizes, filters, and cache-hit patterns without corrupting shared test data.
- Separate checks from thresholds: Checks validate individual responses; thresholds determine whether the overall test passes or fails.
- Observe the complete system: Correlate k6 results with database, cache, queue, container, and application telemetry.
- Run different performance tests: Smoke, average-load, stress, spike, and soak tests answer different engineering questions.
- Never run an uncontrolled test against production: Define ownership, traffic limits, test accounts, cleanup, monitoring, and abort conditions first.

An API can pass unit, integration, and staging tests yet fail when production traffic arrives. The reason is often simple: functional tests verify correctness, while load tests examine what happens when many requests compete for application threads, database connections, CPU, memory, caches, and downstream services.

However, generating thousands of identical requests does not automatically create a realistic load test. Production traffic contains different user journeys, request frequencies, data volumes, authentication states, pauses, retries, and sudden demand changes.

k6 is an open-source performance-testing tool that lets developers model these workloads with JavaScript. It can generate controlled traffic, measure API behaviour, and fail a test automatically when defined performance criteria are violated.

This guide explains how to design k6 tests that reflect real API usage rather than producing arbitrary traffic.

What Makes an API Load Test Realistic?

A realistic load test reproduces the characteristics that create pressure in production. It does not need to reproduce every user action perfectly.

The model should account for:

Traffic characteristicQuestion to answer
Arrival rateHow many journeys or requests begin each second?
ConcurrencyHow many users or operations remain active simultaneously?
Endpoint distributionWhich API operations receive most of the traffic?
User journeysWhich requests happen together and in what order?
Data variationDo requests use different users, records, filters, and payloads?
Traffic shapeIs demand constant, gradual, spiky, or seasonal?
Cache behaviourHow much production traffic results in cache hits or misses?
Geographic originDoes traffic reach the API through different regions or network paths?
Failure behaviourDo clients retry, time out, or abandon slow operations?
Arrival rate
Question to answer
How many journeys or requests begin each second?
1 of 9

Device diversity matters primarily when testing the client experience. For protocol-level API testing, the more relevant factors are request behaviour, network location, connection reuse, payload size, and client retry policies.

A test becomes useful when its assumptions can be explained. “Five hundred VUs” means little unless the team knows how that number relates to production concurrency or traffic.

Start With Production Traffic Data

Before writing a k6 script, study how the API is currently used.

Application logs, API gateway metrics, traces, analytics, and database telemetry can reveal:

  • Requests per second during normal and peak periods
  • Most frequently used endpoints
  • Read-to-write ratio
  • Response-time percentiles
  • Common request sequences
  • Payload-size distribution
  • Error and retry rates
  • Geographic traffic distribution
  • Authentication and session behaviour

Suppose production logs show that 65% of traffic reads catalogue data, 20% searches, 10% updates carts, and 5% creates orders. Repeating the order endpoint for every virtual user would produce a very different database and cache workload.

Begin with an approximate model and document its source. As production behaviour changes, update the test rather than allowing an old workload to become a permanent benchmark.

Installing and Setting Up k6

Install k6 using the appropriate method for your development environment.

macOS

brew install k6

Windows

choco install k6

Linux

sudo apt-get update
sudo apt-get install k6

The Linux commands above work only when the appropriate k6 package source is available to the system. Follow the current Grafana k6 installation instructions for your distribution if the package cannot be located.

Verify the installation with:

k6 version

k6 scripts use JavaScript syntax, but they do not run inside Node.js. Node-specific packages and built-in modules are not automatically available.

Understand the Core k6 Concepts

A few terms appear throughout k6 tests:

ConceptMeaning
Virtual userAn independent execution context running a test function
IterationOne complete execution of the assigned scenario function
ScenarioA workload definition with its own function and scheduling model
ExecutorControls how VUs or iterations are scheduled
StageChanges the target VUs or arrival rate over time
CheckValidates an individual response or condition
ThresholdDefines aggregate pass-or-fail performance criteria
MetricA measurement such as duration, failures, requests, or iterations
Virtual user
Meaning
An independent execution context running a test function
1 of 8

The executor is one of the most important choices because it determines whether k6 models concurrent users or arriving work.

Closed vs Open Workload Models

k6 supports both closed and open workload models.

Closed Model

In a closed model, each VU begins its next iteration only after its current iteration finishes. When the API slows down, iterations take longer, and the generated request rate may fall.

This is suitable when modelling a fixed population of users who wait for each operation before continuing.

Open Model

In an open model, new iterations begin at a defined rate independently of how long previous iterations take. k6 provides constant-arrival-rate and ramping-arrival-rate executors for this purpose.

This is useful when modelling external work that continues to arrive even as the API slows, such as webhooks, orders, events, or requests entering through a gateway.

RequirementSuitable model
Simulate a fixed number of active usersClosed
Reproduce a target number of arrivals per secondOpen
Include user think time between actionsUsually closed
Prevent slow responses from reducing incoming loadOpen
Find concurrency under a known traffic rateOpen
Model long user sessionsUsually closed
Simulate a fixed number of active users
Suitable model
Closed
1 of 6

Grafana’s documentation explains that a closed workload can understate pressure when slower responses cause the request rate to decline, a measurement problem sometimes called coordinated omission.

Choosing an executor based only on convenience can produce misleading results even when the script itself is technically correct.

Designing a Basic k6 Scenario

The following example uses VU stages:

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

export let options = {
  stages: [
    { duration: '20s', target: 10 }, // Ramp-up from 0 to 10 virutal Users over 20 seconds
    { duration: '10s', target: 0 },  // Ramp-down to 0 virtual users over 10 seconds
  ],
};

export default function () {
  http.get('https://your-api-endpoint.com');
  sleep(1);
}

This script ramps from zero to ten VUs over 20 seconds and then ramps back to zero over ten seconds. It does not maintain ten VUs for five minutes; the original explanation incorrectly described a stage that is not present in the configuration.

Each active VU repeatedly sends the GET request and waits for one second. Because this is a closed workload, API response time affects how many iterations each VU can complete.

This is appropriate as a small demonstration or smoke test. It is not yet an authentic production workload because it covers only one endpoint, one request type, and one user behaviour.

Model User Journeys, Not Just Requests

Production users usually perform related actions.

Ensure API Stability with Expert QA Testing

F22 Labs tests your APIs for speed, reliability, and scalability. Our QA experts make sure your system performs smoothly under real traffic.

An e-commerce journey might authenticate, browse products, search, open product details, update a cart, and occasionally check out. A SaaS user might load a dashboard, request reports, update records, and poll a job-status endpoint.

These sequences affect the system differently from isolated traffic. Authentication may access an identity service, catalogue reads may hit caches, search may query a separate index, and checkout may create database locks or call payment providers.

Use separate k6 scenarios or functions for important user groups. Assign their workload proportions using production evidence rather than splitting traffic equally by default.

Keep the model focused. Simulating every rare action makes scripts difficult to understand without necessarily improving the test.

Add Realistic Think Time Carefully

Human users pause between actions. The following example introduces a random delay:

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

export default function () {
  http.get('https://your-api-endpoint.com');
  sleep(Math.random() * 5); // Random think time between 0 to 5 seconds
}

This produces a uniform random pause between zero and five seconds. Real user timing may follow a different distribution, but variation is generally more representative than forcing every VU to pause for exactly the same duration.

Think time is appropriate in VU-based user journeys. Do not add it simply to achieve a target request rate. Arrival-rate executors already schedule iterations according to rate and timeUnit; Grafana advises against adding sleep() merely to pace those scenarios.

Use Representative Test Data

Sending identical requests can create unrealistic cache behaviour, lock contention, or duplicate-record failures.

Parameterize values such as:

  • User accounts
  • Product and record IDs
  • Search terms
  • Pagination
  • Request payload sizes
  • Geographic or tenant identifiers
  • Valid and invalid inputs

Test data must also support concurrency. If several VUs attempt to update the same account or create the same resource, the test may measure artificial conflicts rather than production behaviour.

Decide whether data can be reused, must be partitioned by VU, or needs to be created and cleaned up around the test.

Avoid generating all data through the system under test during the measured period unless data creation is itself part of the workload. Setup traffic can distort the results.

Handle Authentication Without Testing Only Login

If every iteration logs in before calling the target endpoint, authentication can become the dominant workload even when production clients normally reuse tokens.

Model the actual session lifecycle. Depending on the system, this may mean authenticating once per VU, using pre-generated test tokens, refreshing tokens at realistic intervals, or modelling login as a separate traffic segment.

Never place production credentials directly in the script. Pass secrets through an appropriate environment or secret-management mechanism and ensure that logs do not expose tokens or personal data.

Validate Responses With Checks

Fast error responses can make latency metrics look excellent while the API is failing.

A meaningful test should confirm that responses are functionally valid. k6 checks can verify status codes, response fields, headers, or other expected conditions.

Checks record success and failure rates, but a failed check does not automatically fail the complete k6 run. To enforce a limit, combine the check metric with a threshold.

This distinction is important:

  • A check asks whether an individual response behaved correctly.
  • A threshold asks whether the aggregate test result remained within an acceptable limit.

Define Performance Thresholds

Thresholds turn expectations into pass-or-fail criteria.

export let options = {
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% of requests should be below 500ms
  },
};

This threshold passes only when the 95th-percentile HTTP request duration is strictly below 500 milliseconds.

It does not mean the test fails only when 95% of requests exceed 500 milliseconds. It means that approximately 95% of observed request durations must fall at or below the percentile boundary represented by a value under 500 milliseconds.

Grafana defines thresholds as pass-or-fail conditions evaluated against test metrics. They can be used to encode service-level objectives and CI performance requirements.

Latency should rarely be the only threshold. A useful test may also enforce limits for request failure rate, check success, dropped iterations, or endpoint-specific latency.

Choose thresholds from product expectations and existing service objectives. A universal p(95)<500 target is not appropriate for every API operation.

Segment Results by Endpoint and Operation

A global latency percentile can hide important failures.

If 95% of traffic reaches a fast health endpoint and 5% reaches a slow checkout endpoint, the combined p95 may appear acceptable even though checkout users experience severe delays.

Use tags, groups, or separate scenarios to distinguish:

  • Endpoint
  • Operation
  • User journey
  • Expected response
  • Region
  • Cache state
  • API version

k6 allows requests, checks, thresholds, and custom metrics to be categorized with tags.

Keep tag values bounded. Using unique user IDs or request IDs as metric tags can create excessive time-series cardinality in monitoring systems.

Running the Load Test

Execute the script from the command line:

k6 run your-test-script.js

Begin with a small smoke test. Confirm that authentication works, requests are correct, test data remains valid, and the load generator is not already resource-constrained.

Increase traffic incrementally after validating the script and environment. A large test with an incorrect request model can waste resources or damage shared data without producing useful results.

Read the Right k6 Metrics

k6 records several built-in HTTP measurements.

MetricWhat it reveals
http_req_durationTotal request duration
http_req_waitingTime waiting for the first response byte
http_req_connectingTime spent establishing TCP connections
http_req_tls_handshakingTLS negotiation time
http_req_failedRate of failed HTTP requests
http_reqsTotal requests and request rate
iteration_durationDuration of the complete user iteration
vusCurrently active VUs
dropped_iterationsScheduled iterations k6 could not start
http_req_duration
What it reveals
Total request duration
1 of 9

Response averages are often insufficient because a small number of very slow requests can be hidden. Examine percentiles such as p90, p95, and p99 alongside error rates and throughput.

For arrival-rate scenarios, dropped iterations deserve special attention. They may indicate that k6 lacks enough VUs or load-generator resources to maintain the requested arrival rate. They can also appear when the system slows enough that available VUs remain occupied.

Correlate k6 Results With System Telemetry

k6 can show when response time increased, but backend telemetry explains why.

Monitor the application and its dependencies during the same test window:

LayerUseful signals
ApplicationRequest duration, errors, thread or event-loop pressure
ContainersCPU throttling, memory, restarts and replica scaling
DatabaseQuery latency, connections, locks and slow queries
CacheHit rate, memory, evictions and command latency
QueuesDepth, publish rate and consumer lag
NetworkConnection errors, saturation and cross-region latency
DependenciesRate limits, timeouts and downstream failures
Application
Useful signals
Request duration, errors, thread or event-loop pressure
1 of 7

k6 metrics can be streamed to compatible backends and visualized in Grafana. Combining load-generator and system metrics on the same timeline helps connect a latency spike with events such as database saturation or cache eviction.

Without backend telemetry, the result may be limited to “the API became slow at 800 requests per second.” With telemetry, the team may learn that the database connection pool saturated first.

Run the Right Type of Performance Test

One large load test cannot answer every question.

Test typePurpose
Smoke testConfirms the script works under minimal traffic
Average-load testEvaluates expected day-to-day demand
Stress testRaises load beyond normal levels to find degradation
Spike testExamines sudden traffic increases and recovery
Soak testRuns sustained load to reveal leaks or resource accumulation
Breakpoint testContinues increasing pressure to identify system limits
Smoke test
Purpose
Confirms the script works under minimal traffic
1 of 6

Run a smoke test before every larger execution. It is much cheaper to detect an invalid token or destructive data mistake with one VU than with hundreds.

Test in a Production-Representative Environment

A small staging environment cannot be expected to produce production capacity numbers. Its results may still be useful for regression testing, but they should not be presented as production limits.

Match the factors that materially influence performance:

  • Application configuration
  • Database engine and schema
  • Cache topology
  • Network path
  • Container limits
  • Autoscaling policy
  • Data volume
  • External dependencies
  • Observability agents

If exact parity is impossible, document the differences. This allows readers to understand what the result proves and what remains uncertain.

Ensure API Stability with Expert QA Testing

F22 Labs tests your APIs for speed, reliability, and scalability. Our QA experts make sure your system performs smoothly under real traffic.

Run Production Tests Safely

Production testing provides realistic infrastructure and data distribution, but it also carries the highest risk.

Before sending load to production, establish:

  • Explicit authorization and ownership
  • A controlled traffic ceiling
  • Test accounts and identifiable traffic
  • Data cleanup procedures
  • Monitoring coverage
  • Abort criteria
  • Incident communication
  • Protection for real users
  • Awareness of third-party usage charges

Avoid destructive endpoints unless their effects are isolated and reversible. Coordinate with infrastructure, database, security, and support teams before execution.

Never treat “the test tool can generate this load” as permission to send it.

Common k6 Load-Testing Mistakes

1. Choosing VUs Without Knowing the Target Traffic

VU count is not equivalent to requests per second. Iteration duration, think time, and the number of requests per journey determine the resulting throughput.

2. Repeating One Endpoint

A single repeated GET request often creates an unrealistically cache-heavy workload and ignores database writes, authentication, and dependent services.

3. Ignoring Functional Failures

Latency looks artificially low when the API quickly returns errors. Validate important responses and enforce an error-rate threshold.

4. Using Only Average Response Time

Averages hide long-tail latency. Study percentiles and endpoint-specific results.

5. Testing the Load Generator Instead of the API

CPU, memory, file descriptors, or network limits on the k6 machine can cap traffic. Monitor the load generator and consider distributed execution for larger tests.

6. Making Every Request Unique

Unlimited random values can prevent realistic caching and create unmanageable metrics. Data diversity should resemble production, not eliminate every cache hit.

Comparing Uncontrolled Test Runs

Results cannot be compared confidently when the environment, data state, deployment version, or traffic model changes between runs.

A Practical Testing Workflow

A sustainable k6 process moves from evidence to repeatable tests.

Start by collecting production traffic and service objectives. Convert the most important user journeys into scenarios and select executors that match how work arrives.

Validate the script with a smoke test, then establish an average-load baseline. Add stress, spike, or soak tests according to the risks of the system.

During each run, collect k6 and backend telemetry together. Record the application version, infrastructure configuration, dataset state, scenario settings, and threshold results so future tests remain comparable.

Finally, automate smaller regression tests in CI while reserving large capacity tests for controlled environments and planned execution windows.

Frequently Asked Questions

What is k6 used for?

k6 is used to generate controlled workloads and measure the performance of APIs, web applications, browser journeys, WebSockets, and other supported protocols. It can enforce pass-or-fail thresholds in automated pipelines.

What is the difference between VUs and requests per second?

VUs represent active execution contexts. Requests per second measure generated throughput. One VU can send several requests per iteration, and slower responses can reduce throughput in a closed workload.

Should I use VUs or an arrival-rate executor?

Use VU-based executors to model a fixed population of users completing journeys. Use arrival-rate executors when work should continue entering the system at a defined rate independently of response time.

Are k6 checks the same as thresholds?

No. Checks record whether individual conditions pass. Thresholds evaluate aggregate metrics and determine whether the complete test passes or fails.

Can k6 test authenticated APIs?

Yes. Tests can authenticate users, reuse tokens, refresh credentials, and send authorization headers. The authentication lifecycle should match production behaviour rather than logging in before every request automatically.

Can k6 run distributed load tests?

Yes, but distributed execution requires appropriate coordination through solutions such as Grafana Cloud k6 or the k6 Operator. Metrics and thresholds must be interpreted correctly across load-generator instances.

Should load tests run in production?

Only with explicit authorization, controlled scope, monitoring, abort conditions, isolated data, and protection for real users. A production-like environment is safer for most routine testing.

Which latency percentile should I monitor?

p95 is commonly used, but p90, p95, and p99 reveal different parts of the latency distribution. Choose percentiles based on service objectives, request volume, and the importance of long-tail user experiences.

Conclusion

Realistic API load testing with k6 is not about producing the largest possible number of requests. It is about creating a workload that reflects how traffic arrives, which journeys users follow, which data they access, and how the system behaves when demand changes.

Start with production evidence, choose an appropriate open or closed workload model, and represent the most important endpoint mix. Add realistic data, authentication, checks, and enforceable thresholds.

During execution, observe the complete system, not only k6 response times. Database connections, cache behaviour, queue depth, container resources, and downstream services often reveal the actual bottleneck.

A useful load test should answer a specific engineering question and be repeatable enough to detect change. When k6 scenarios evolve alongside production traffic, performance testing becomes an ongoing reliability practice rather than a final check before launch.

Author-Goutham
Goutham
LinkedIn

Hey, I’m Goutham - a techie who loves simplifying complex ideas. I design systems by day and break down tech jargon by night, always excited to share how awesome tech can be.

Share this article

Phone

Next for you

8 Best GraphQL Libraries for Node.js in 2025 Cover

Technology

Aug 4, 202613 min read

8 Best GraphQL Libraries for Node.js in 2025

8 Best GraphQL Libraries for Node.js in 2026 Too Long? Read This First - Choose Apollo Server when you need a mature ecosystem, GraphOS integration, plugins, or Apollo Federation. - Choose GraphQL Yoga for a modern, portable server with Fetch API compatibility and built-in support for subscriptions over Server-Sent Events. - Choose Mercurius when your application already uses Fastify and runtime efficiency is a major priority. - Use GraphQL.js when you need the official JavaScript implementati

9 React Native Animation Libraries and Tools Compared Cover

Technology

Aug 4, 202615 min read

9 React Native Animation Libraries and Tools Compared

Too Long? Read This First - Use React Native Reanimated for gesture-driven, interruptible, and performance-sensitive interface animations. - Use the built-in Animated API for simple fades, transforms, and timed sequences without another dependency. - Pair React Native Gesture Handler with Reanimated for swipes, dragging, pinching, rotation, and other touch-driven experiences. - Use Lottie React Native for non-interactive motion graphics supplied by designers. - Choose React Native Skia for cust

9 Critical Practices for Secure Web Application Development Cover

Technology

Aug 4, 202616 min read

9 Critical Practices for Secure Web Application Development

Too Long? Read This First - Define security requirements and model threats before implementation begins. - Treat authentication, account recovery, and MFA as one complete identity system. - Apply server-side authorization to every protected action and object. - Prevent injection with parameterized APIs, structured validation, safe output handling, and restricted outbound requests. - Protect sessions and tokens throughout their complete lifecycle. - Minimise sensitive data and manage encryption