| name | k6-and-locust-load-testing |
| description | Production-grade load, stress, spike, and endurance testing using Grafana k6 and Python Locust. Use when designing performance test suites, defining SLAs/SLOs, simulating distributed user traffic, and automating performance regression gates in CI/CD pipelines. |
k6 & Locust Load Testing Architecture & Best Practices
This skill provides comprehensive patterns, architectural guidelines, SLA validation thresholds, anti-patterns, and enterprise-grade code examples for high-throughput load and performance testing using Grafana k6 (JavaScript/TypeScript ES6) and Python Locust.
1. Core Concepts & Framework Selection
| Capability | Grafana k6 | Python Locust |
|---|
| Runtime / Engine | Go VM with JavaScript (Goja) | Python (Gevent async co-routines) |
| Resource Efficiency | Extremely High (~1,000s of VUs per CPU core) | High (~100s-1000s VUs per worker node) |
| Scripting Language | ES6 JavaScript / TypeScript | Native Python |
| Protocol Support | HTTP/1.1, HTTP/2, WebSockets, gRPC, Redis | Any protocol via Python SDK (HTTP, gRPC, WebSockets, Kafka, SQL) |
| CI/CD Integration | CLI native, exit codes on threshold breaches, k6 Cloud | CLI native, Locust Web UI / Headless mode |
| Best Used For | Protocol-level performance testing, high-concurrency benchmarks, CI/CD automated gates | Complex user flows, Python ecosystem integration (ML models, Custom protocols, DB validation) |
2. Grafana k6 Implementation Standard
Architectural Principles
- Separation of Concerns: Split scenarios, test data generators, API client helpers, and SLA threshold definitions into modular files.
- Deterministic Stages: Model ramp-up, steady-state (plateau), and ramp-down using
scenarios with specific executors (ramping-arrival-rate, ramping-vus).
- Strict Thresholds: Map metrics to strict Service Level Agreements (SLAs) so CI pipelines automatically fail when p95/p99 latency or error rates exceed budgets.
Production k6 Framework Example
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';
const errorRate = new Rate('custom_error_rate');
const apiLatency = new Trend('api_transaction_latency');
const totalOrders = new Counter('total_orders_created');
export const options = {
scenarios: {
checkout_load_test: {
executor: 'ramping-arrival-rate',
startRate: 10,
timeUnit: '1s',
preAllocatedVUs: 50,
maxVUs: 500,
stages: [
{ duration: '2m', target: 50 },
{ duration: '5m', target: 50 },
{ : , : },
{ : , : },
{ : , : },
],
: ,
},
},
: {
: [],
: [, ],
: [],
: [],
},
};
= __ENV. || ;
= __ENV. || ;
() {
res = http.(, {
: { : },
});
(res, { : r. === });
{ : ().() };
}
() {
payload = .({
: ,
: ,
: ,
});
params = {
: {
: ,
: ,
: ,
},
: { : },
};
startTime = .();
res = http.(, payload, params);
duration = .() - startTime;
apiLatency.(duration);
success = (res, {
: r. === ,
: r.() !== ,
: r.. < ,
});
(success) {
totalOrders.();
errorRate.();
} {
errorRate.();
}
( + .() * );
}
() {
.();
}
3. Python Locust Implementation Standard
Architectural Principles
- User Behavior Modeling: Group API flows into task sets with weights representing real user path distributions.
- State Management: Maintain user session state (auth tokens, basket contents) on the
User instance.
- Event Hooks: Use
events.request and events.quitting for customized SLA verification, metric shipping, or telemetry injection.
Production Locust Framework Example
import time
import uuid
import logging
from locust import HttpUser, task, between, events, SequentialTaskSet
from locust.exception import StopUser
logger = logging.getLogger("locust.loadtest")
class UserCheckoutJourney(SequentialTaskSet):
"""Sequential tasks simulating an e-commerce purchasing journey."""
def on_start(self):
"""Executed when a Virtual User initiates this TaskSet."""
self.client.headers.update({
"Content-Type": "application/json",
"User-Agent": "LocustLoadTest/2.0",
})
self.auth_token = self._authenticate()
def _authenticate(self) -> str:
with self.client.post(
"/api/v1/auth/login",
json={"username": "load_user", "password": "secure_password"},
catch_response=True,
name="/api/v1/auth/login"
) as response:
if response.status_code == 200:
token = response.json().get("access_token")
self.client.headers["Authorization"] = f"Bearer "
token
:
response.failure()
StopUser()
():
.client.get(
,
catch_response=,
name=
) response:
response.status_code != :
response.failure()
response.elapsed.total_seconds() > :
response.failure()
():
payload = {: , : }
.client.post(, json=payload, name=)
():
idempotency_key = (uuid.uuid4())
headers = {: idempotency_key}
.client.post(
,
json={: },
headers=headers,
catch_response=,
name=
) response:
response.status_code == :
response.success()
:
response.failure()
():
wait_time = between(, )
tasks = [UserCheckoutJourney]
host =
():
stats = environment.stats.total
fail_ratio = stats.fail_ratio
p95 = stats.get_response_time_percentile()
p99 = stats.get_response_time_percentile()
logger.info()
sla_failed =
fail_ratio > :
logger.error()
sla_failed =
p95 > :
logger.error()
sla_failed =
p99 > :
logger.error()
sla_failed =
sla_failed:
environment.process_exit_code =
4. SLA Verification & Threshold Metrics
When designing enterprise performance tests, define SLAs based on standard 4 Golden Signals (Latency, Traffic, Errors, Saturation):
| Metric | Target SLA Standard | k6 Threshold Expression | Locust Verification logic |
|---|
| Http Failure Rate | < 0.5% | 'http_req_failed': ['rate<0.005'] | stats.fail_ratio < 0.005 |
| p95 Latency | < 250 ms | 'http_req_duration': ['p(95)<250'] | stats.get_response_time_percentile(0.95) < 250 |
| p99 Latency | < 500 ms | 'http_req_duration': ['p(99)<500'] | stats.get_response_time_percentile(0.99) < 500 |
| Throughput (RPS) | > 500 RPS | 'http_reqs': ['count>300000'] | stats.total_rps >= 500 |
5. Anti-Patterns & Pitfalls to Avoid
1. Closed Model vs Open Model Misunderstanding
- Anti-Pattern: Using VU-based ramping (Closed Model) when simulating public HTTP endpoints. As response latency increases under load, VUs spend more time waiting for responses, decreasing throughput (RPS) and hiding performance degradation.
- Solution: Use
ramping-arrival-rate in k6 or Constant Throughput Timer models in Locust to enforce RPS regardless of system latency.
2. Lack of Pacing / Think Time
- Anti-Pattern: Executing infinite tight loops without think times (
sleep()), causing unrealistically high request rates per VU and overloading load generator network cards before target system limits are reached.
- Solution: Apply realistic Poisson or uniform random think times (
sleep(1 + Math.random() * 2) in k6 or between(1, 3) in Locust).
3. Hardcoded Test Data & Shared Session IDs
- Anti-Pattern: Re-using the same user ID or authentication token across 500 VUs, causing DB lock contention on a single row or hitting single-user rate limits.
- Solution: Parameterize test data using JSON data files, synthetic UUID generation, or unique VU iteration identifiers (
__VU, __ITER).
6. Logging Overhead During High Load
- Anti-Pattern: Using
console.log() or print() inside the main test function for every request during a 10,000 VU load test, maxing out CPU I/O.
- Solution: Log only on failure conditions (
if (!success) { ... }).