Back to HomeHigh Concurrency

High Concurrency Testing Guide: JMeter, Locust, k6 Tool Comparison & Tutorial | 2025

12 min min read
#High Concurrency Testing#Load Testing#JMeter#Locust#k6#wrk#Performance Testing#CI/CD#Capacity Planning

High Concurrency Testing Guide: JMeter, Locust, k6 Tool Comparison & Tutorial | 2025

High Concurrency Testing Guide: JMeter, Locust, k6 Tool Comparison & Tutorial

Introduction: Going Live Without Testing is Gambling

"Did you test before going live?" "Yes, all features work." "Load testing?" "..."

Then on the big sale day, the system crashed.

Functional testing only ensures "it works"—load testing ensures "it can handle the load." Going live with a high concurrency system without load testing is like driving blindfolded.

This article will take you from "why load test" through comparing four major tools, teaching you to write test scripts, interpret metrics, and finally how to integrate with CI/CD for continuous load testing.

If you're not familiar with high concurrency basics, we recommend first reading What is High Concurrency? Complete Guide.


1. Why Load Testing is Needed

1.1 Find System Bottlenecks

Load testing exposes system weaknesses:

  • Database connections insufficient
  • Certain API particularly slow
  • Memory leaks
  • Network bandwidth bottleneck

These problems don't appear at low traffic—only load testing can find them.

1.2 Validate Architecture Design

How much QPS can your architecture design handle? Theoretical calculations aren't accurate—real testing tells you.

  • Is read-write separation effective?
  • Is cache hit rate high enough?
  • Is load balancing distributing evenly?

Load testing is the validation method for architecture design. For detailed architecture design principles, see High Concurrency Architecture Design.

1.3 Capacity Planning Basis

Big sale expects 1 million users online simultaneously. How many machines are needed?

Without load test data, you can only guess. With load test data, you can plan precisely.

1.4 Establish Baseline

Load testing isn't a one-time thing. Every new version release might change performance.

Regular load testing establishes performance baseline. If new version performance drops, you'll know immediately.


2. Load Testing Tool Comparison

There are many load testing tools on the market. Here are four most commonly used.

2.1 Apache JMeter

Introduction: Veteran load testing tool written in Java, most comprehensive features.

Pros:

  • GUI interface, easy to start
  • Supports multiple protocols (HTTP, JDBC, LDAP, JMS)
  • Rich plugin ecosystem
  • Distributed testing support

Cons:

  • Written in Java, higher resource usage
  • GUI lags during large tests
  • Scripts are XML, hard to maintain

Suitable for:

  • Need to test multiple protocols
  • Team unfamiliar with programming languages
  • Enterprise applications

2.2 Locust

Introduction: Modern load testing tool written in Python.

Pros:

  • Python scripts, high flexibility
  • Lightweight, low resource usage
  • Native distributed architecture support
  • Web UI real-time monitoring

Cons:

  • Only supports HTTP
  • Python syntax has learning curve
  • Slightly weaker for extreme performance scenarios

Suitable for:

  • Python developers
  • Web API testing
  • Complex test logic needed

2.3 k6

Introduction: Modern load testing tool using JavaScript scripts, maintained by Grafana.

Pros:

  • Written in Go, excellent performance
  • JavaScript/TypeScript for scripts
  • Great developer experience (CLI first)
  • Cloud service integration (Grafana Cloud)

Cons:

  • Only supports HTTP/WebSocket/gRPC
  • Relatively new, smaller community
  • Advanced features need paid version

Suitable for:

  • Frontend/fullstack developers
  • DevOps teams
  • Need CI/CD integration

2.4 wrk

Introduction: Minimalist load testing tool written in C.

Pros:

  • Extremely lightweight, lowest resource usage
  • Single machine can generate extremely high QPS
  • Lua script extension

Cons:

  • Simple features, only tests HTTP
  • No GUI
  • Weak reporting features

Suitable for:

  • Quick benchmark tests
  • Scenarios requiring extreme performance
  • Simple API testing

Tool Comparison Table

ToolLanguageScriptsGUIDistributedResource UsageUse Cases
JMeterJavaXMLYesSupportedHighEnterprise, multi-protocol
LocustPythonPythonWebNativeLowPython teams, Web API
k6GoJavaScriptNoPaid versionLowDevOps, CI/CD
wrkCLuaNoNoVery LowQuick benchmarks

3. Understanding Test Metrics

After running load tests, you see lots of numbers. What do they mean?

3.1 Throughput Metrics

QPS (Queries Per Second)

Queries per second. For Web APIs, this is the most common throughput metric.

  • 100 QPS: Small website
  • 1,000 QPS: Medium website
  • 10,000+ QPS: Large website

TPS (Transactions Per Second)

Transactions per second. One transaction might include multiple queries.

Example: One purchase = check product + deduct inventory + create order + charge payment = 1 TPS, but might be 4 QPS.

RPS (Requests Per Second)

Requests per second. Usually synonymous with QPS.

3.2 Response Time Metrics

Average Response Time (Avg)

Average of all request response times. Looks intuitive but easily affected by extreme values.

P50 / P95 / P99

Percentiles, better reflect real experience.

  • P50: 50% of requests complete within this time (median)
  • P95: 95% of requests complete within this time
  • P99: 99% of requests complete within this time

Why care about P99? Because 1% of slow requests might affect lots of users.

Example:

  • Average response time: 100ms
  • P50: 80ms
  • P95: 200ms
  • P99: 1000ms

This means although average looks okay, 1% of users wait over 1 second. That 1% might be your VIP customers.

3.3 Error Metrics

Error Rate

Proportion of failed requests. Includes HTTP 5xx errors, timeouts, connection failures.

  • < 0.1%: Excellent
  • 0.1% - 1%: Acceptable
  • 1%: Needs attention

Timeout Rate

Proportion of timed-out requests. Usually set 30 or 60 second timeout.

3.4 Resource Metrics

During load testing, also monitor server resources:

  • CPU usage: Over 80% needs attention
  • Memory usage: Watch for continuous increase (leak)
  • Network I/O: Is bandwidth maxed
  • Disk I/O: Is there heavy read/write

These metrics help locate where bottlenecks are.


4. Load Test Script Tutorial

Talk without practice is fake. Here are actual examples for three tools.

4.1 JMeter Example

JMeter mainly uses GUI to design test plans, core components:

  1. Thread Group: Set concurrent user count
  2. HTTP Request: Define API to test
  3. Listener: Collect and display results

<ThreadGroup>
  <numThreads>100</numThreads>
  <rampTime>10</rampTime>
  <duration>60</duration>
</ThreadGroup>

<HTTPSampler>
  <domain>api.example.com</domain>
  <path>/products</path>
  <method>GET</method>
</HTTPSampler>

Actual operation flow:

  1. Open JMeter GUI
  2. Add Thread Group, set 100 users, 10 second ramp-up, run 60 seconds
  3. Add HTTP Request, fill in API info
  4. Add Summary Report to view results
  5. Click run

4.2 Locust Example

Locust uses Python for scripts, very flexible.

# locustfile.py
from locust import HttpUser, task, between

class WebUser(HttpUser):
    # Wait 1-3 seconds between requests
    wait_time = between(1, 3)

    @task(3)  # Weight 3, higher execution frequency
    def get_products(self):
        self.client.get("/products")

    @task(1)  # Weight 1
    def get_product_detail(self):
        product_id = 123
        self.client.get(f"/products/{product_id}")

    @task(1)
    def create_order(self):
        self.client.post("/orders", json={
            "product_id": 123,
            "quantity": 1
        })

    def on_start(self):
        # Runs when each user starts (e.g., login)
        self.client.post("/login", json={
            "username": "test",
            "password": "test123"
        })

Run commands:

# Start Locust Web UI
locust -f locustfile.py --host=https://api.example.com

# Or run directly from command line
locust -f locustfile.py --host=https://api.example.com \
    --users 100 --spawn-rate 10 --run-time 60s --headless

To learn more about Python handling high concurrency, see Python vs Golang High Concurrency.

4.3 k6 Example

k6 uses JavaScript for scripts, friendly for frontend developers.

// script.js
import http from 'k6/http';
import { check, sleep } from 'k6';

// Test configuration
export const options = {
  stages: [
    { duration: '10s', target: 50 },   // Ramp up to 50 users in 10s
    { duration: '30s', target: 100 },  // Maintain 100 users for 30s
    { duration: '10s', target: 0 },    // Ramp down to 0 in 10s
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],  // 95% requests under 500ms
    http_req_failed: ['rate<0.01'],    // Error rate < 1%
  },
};

export default function () {
  // Get product list
  const productsRes = http.get('https://api.example.com/products');
  check(productsRes, {
    'products status is 200': (r) => r.status === 200,
  });

  sleep(1);

  // Get product detail
  const detailRes = http.get('https://api.example.com/products/123');
  check(detailRes, {
    'detail status is 200': (r) => r.status === 200,
    'detail has name': (r) => r.json('name') !== undefined,
  });

  sleep(1);

  // Create order
  const orderRes = http.post(
    'https://api.example.com/orders',
    JSON.stringify({ product_id: 123, quantity: 1 }),
    { headers: { 'Content-Type': 'application/json' } }
  );
  check(orderRes, {
    'order created': (r) => r.status === 201,
  });

  sleep(1);
}

Run command:

k6 run script.js

5. CI/CD Integration for Continuous Load Testing

Load testing shouldn't be a one-time thing. Integrate with CI/CD, run automatically with every release.

5.1 Why Continuous Load Testing

  • New code might introduce performance issues
  • Catch early, avoid problems after going live
  • Establish performance baseline, track trends
  • Prevent performance regression

5.2 Integration Methods

GitHub Actions + k6 Example

# .github/workflows/load-test.yml
name: Load Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install k6
        run: |
          sudo gpg -k
          sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
          echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
          sudo apt-get update
          sudo apt-get install k6

      - name: Run load test
        run: k6 run --out json=results.json tests/load-test.js

      - name: Check thresholds
        run: |
          if grep -q '"thresholds":{"http_req_duration":\[{"ok":false' results.json; then
            echo "Performance threshold failed!"
            exit 1
          fi

5.3 Threshold Settings

Set clear thresholds to automatically determine pass/fail:

// k6 thresholds
export const options = {
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    http_req_failed: ['rate<0.01'],
    http_reqs: ['rate>100'],  // At least 100 QPS
  },
};

6. Capacity Planning Methods

How to use load test data for capacity planning?

6.1 Estimation Formula

Step 1: Estimate Expected Traffic

Expected QPS = Daily Active Users × Requests Per User ÷ Active Seconds

Example:

  • Daily active users: 100,000
  • Requests per user: 50
  • Active time: 8 hours = 28,800 seconds
  • Expected QPS = 100,000 × 50 ÷ 28,800 ≈ 174

Step 2: Consider Peak

Peak is usually 2-5x average.

Peak QPS = Expected QPS × Peak Factor

Assuming peak is 3x average:

  • Peak QPS = 174 × 3 ≈ 522

6.2 Safety Factor

Don't run systems to their limits. Leave room for unexpected situations.

Target Capacity = Peak QPS × Safety Factor

Safety factor usually 1.5-2x:

  • Target Capacity = 522 × 1.5 ≈ 783 QPS

6.3 Scaling Plan

Knowing target capacity, use load test data to calculate needed resources.

Assuming load test results:

  • Single 4C8G machine can handle 200 QPS
  • Target capacity 783 QPS
  • Machines needed = 783 ÷ 200 ≈ 4

Considering high availability (N+1 redundancy), need at least 5 machines.


Unsure how much traffic your system can handle? Book a load testing consultation and let experts help plan your capacity.


7. Common Problem Troubleshooting

What to do when you encounter problems during load testing?

7.1 Test Client Bottleneck

Symptoms: Test machine CPU spikes, but server is idle.

Cause: Load testing tool itself becomes bottleneck. JMeter has high resource usage, limited QPS from single machine.

Solutions:

  • Switch to lightweight tools (k6, wrk)
  • Distributed load testing (multiple test machines)
  • Disable unnecessary monitoring plugins

7.2 Network Bottleneck

Symptoms: Network traffic maxed, but CPU/memory are fine.

Cause: Insufficient bandwidth, or network equipment becomes bottleneck.

Solutions:

  • Check bandwidth between test machine and server
  • Put test machine in same internal network
  • Use cloud load testing services

7.3 Application Bottleneck

Symptoms: Application CPU spikes, response slows.

Cause: Code performance issues, resource contention, thread exhaustion.

Troubleshooting directions:

  • Profiling to find hot functions
  • Check for lock contention
  • Check thread pool/connection pool settings

7.4 Resource Leaks

Symptoms: Performance drops after running a while, memory continuously rising.

Cause: Memory leak, connection leak, file descriptor leak.

Troubleshooting directions:

  • Use APM tools to track memory usage
  • Check if connections are properly closed
  • Look at system's open file count

FAQ

Q1: Should load testing be done in production or test environment?

Test environment for initial validation, production for final confirmation. But production load testing needs care—recommend doing during off-peak hours with degradation plans in place.

Q2: How long should load tests run?

At least 5-10 minutes. Too short might miss memory leaks and other issues. Stability tests can run hours or even a day.

Q3: How to simulate real user behavior?

Not all users send requests at the same second. Use ramp-up to simulate users gradually joining, think time to simulate user thinking, different weights to simulate different behavior proportions.

Q4: How to do distributed load testing?

JMeter has remote testing, Locust natively supports distributed, k6 paid version has Cloud features. Or manually run on multiple machines and merge results.

Q5: What if load test results are unstable?

Run multiple times and take average. Eliminate environmental interference (other programs, network fluctuations). Ensure starting state is consistent for each test (restart services, clear cache).


Conclusion: Load Testing is a Must for High Concurrency Systems

A system without load testing is like a soldier without battle experience.

Key Takeaways:

  1. Load testing finds bottlenecks, validates architecture, plans capacity
  2. JMeter is full-featured, Locust Python-friendly, k6 modern, wrk minimalist
  3. Focus on P99 response time, not just average
  4. Error rate and resource metrics are equally important
  5. Integrate CI/CD for continuous load testing
  6. Capacity planning should consider peak and safety factors

Extended Reading:


Need a Second Opinion on Architecture?

Good load testing can prevent disasters. If you're:

  • Preparing for a big sale, worried system can't handle it
  • Want to know where your system's performance limits are
  • Need to plan capacity and budget

Book an architecture consultation and let's review your system performance together.

All consultation content is completely confidential, no sales pressure.


References

  1. Apache JMeter Official Documentation (2024)
  2. Locust Official Documentation (2024)
  3. k6 Official Documentation (2024)
  4. Google, "Site Reliability Engineering" (2016)
  5. AWS, "Performance Testing Best Practices" (2024)

Need Professional Cloud Advice?

Whether you're evaluating cloud platforms, optimizing existing architecture, or looking for cost-saving solutions, we can help

Book Free Consultation

Related Articles