26.11. Performance Testing
Table of Contents
Why Performance Testing Matters
Performance testing checks how your backend behaves under load, not whether it is functionally correct. You want to know:
- How fast is it for users?
- How many users or requests can it handle?
- What breaks first, and where are the bottlenecks?
Functional tests answer “Does it work?”
Performance tests answer “Does it still work when it is busy?”
Typical goals:
- Detect slow endpoints and database queries.
- Set realistic SLAs (Service Level Agreements), for example “95% of requests under 300 ms”.
- Prevent regressions when you release new versions.
- Understand capacity: “This server handles about 500 requests per second.”
Key rule: Never performance test directly against production for experiments that can overload the system. Use a separate environment with the same configuration and similar data volume whenever possible.
Key Performance Metrics
For backend systems you usually care about four main classes of metrics.
Response Time and Latency
Response time (or latency) is how long it takes to handle one request. You will typically look at:
- Average latency, for example 120 ms
- Percentiles:
- P50: 50 percent of requests are faster than this (the “median”)
- P95: 95 percent are faster
- P99: 99 percent are faster (shows the worst tail)
Example:
| Metric | Meaning |
|---|---|
| P50 | Typical user experience |
| P95 | Experience during small spikes |
| P99 | Edge cases, worst situations |
Important: Always look at percentiles (P95, P99), not only averages. A low average can hide very slow outliers that users still feel.
Throughput
Throughput is how much work your system processes in a time unit, for example:
- Requests per second (RPS)
- Jobs per minute
- Database queries per second
Example:
If your API handles $600$ requests in $60$ seconds, throughput is:
$$\text{throughput} = \frac{600}{60} = 10 \text{ RPS}$$
Resource Usage
You also need to watch:
- CPU usage
- Memory usage
- Network usage (bandwidth, packets)
- Disk usage (IOPS, read/write MB/s)
- Database connections and pool usage
These help you answer:
- Is the CPU the bottleneck?
- Are we running out of memory?
- Is the database saturated?
Error Rate and Timeouts
Under load, errors can appear only when the system is stressed:
- HTTP 5xx errors (500, 502, 503, 504)
- Timeouts, for example client waits more than 5 seconds
- Connection errors, for example “connection refused”
You want:
- Error rate close to 0, even at target load.
- If errors start at 200 RPS, and your goal is 150 RPS, you are probably safe.
Types of Performance Tests
Different tests answer different questions. You rarely need only one type.
Load Testing
Question: “How does the system behave under expected normal and peak load?”
You choose a realistic number of users or requests, then see:
- Response times
- Error rates
- Resource usage
Example:
- Normal traffic: 50 RPS
- Daily peak: 120 RPS for 1 hour
You run a test that holds 120 RPS for 1 hour and check if the system stays healthy.
Stress Testing
Question: “What happens when we push the system beyond its limits?”
You increase load until you see:
- Error rates spike
- Response times explode
- CPU or database becomes saturated
Goal:
- Discover the breaking point, for example “at about 300 RPS, the database maxes out”.
- Observe how the system fails. Does it crash, or does it degrade gracefully?
Soak / Endurance Testing
Question: “Can the system handle expected load for a long time?”
You keep a realistic load for many hours, sometimes days, and watch for:
- Memory leaks (memory usage keeps growing)
- Resource leaks (open file descriptors, connections)
- Performance degradation over time
Example:
- Run 50 RPS for 24 hours and check that memory usage stabilizes instead of growing without bound.
Spike Testing
Question: “What if traffic suddenly jumps?”
You simulate sharp increases in traffic, for example:
- Jump from 10 RPS to 200 RPS in 5 seconds.
- Then drop back to 10 RPS.
Checks:
- Does the system handle it without many errors?
- How quickly does it recover?
- Are autoscaling rules fast enough?
Designing a Performance Test Plan
Before you start any tool, define a clear plan.
Define Goals and SLAs
Decide what “good enough” means. Examples:
- P95 latency < 300 ms for
/api/orders - Error rate < 0.5 percent at 100 RPS
- System should run at 70 percent CPU or less at peak load
Write these down. Your tests will check if you meet these conditions.
Understand Realistic Workloads
You need realistic traffic shapes, not only “one endpoint at full speed”.
Questions:
- Which endpoints are used most?
- What is a typical user flow? For example:
- Login
- List products
- View product
- Add to cart
- Checkout
- At peak time, how many concurrent users do you expect?
Example user behavior mix:
| Endpoint | Share of traffic |
|---|---|
| GET /products | 50% |
| GET /product/ID | 30% |
| POST /cart | 10% |
| POST /checkout | 10% |
Your test scenario should reflect this mix, not hammer a single endpoint that is rarely used.
Choose Scenarios
Typical scenarios:
- Single critical endpoint under load, for example
/api/search - Full user journey, for example login + browse + purchase
- Background tasks load, for example “process 1000 jobs per minute”
For an absolute beginner, it is usually helpful to:
- Start with 1 or 2 critical endpoints.
- Then add more realistic, multi-step user flows.
Popular Tools and Simple Examples
You can do performance testing with many tools. Most use the same idea: simulate many virtual users or requests and measure results.
Using `ab` (ApacheBench) for a Quick Test
ab is simple and often preinstalled on Linux.
Example: 1000 requests with up to 50 concurrent requests:
ab -n 1000 -c 50 https://example.com/api/itemsImportant flags:
-n 1000total number of requests-c 50number of concurrent requests
ab outputs statistics like:
- Requests per second
- Time per request
- Percentiles of response time
It is useful for quick, small experiments, but limited for complex scenarios.
Using `wrk` for More Realistic Load
wrk is a popular HTTP benchmarking tool.
Example: run for 30 seconds with 100 threads and 400 connections:
wrk -t100 -c400 -d30s https://example.com/api/itemsYou can also write Lua scripts to control request bodies or mix endpoints.
Using Locust for User Flows (Python Friendly)
Locust is written in Python and is great for modeling user behavior.
- Install:
pip install locust- Create
locustfile.py:
from locust import HttpUser, task, between
class WebsiteUser(HttpUser):
wait_time = between(1, 3) # seconds between tasks
@task(3) # weight 3
def list_products(self):
self.client.get("/products")
@task(1) # weight 1
def view_product(self):
self.client.get("/products/1")
@task
def add_to_cart(self):
self.client.post("/cart", json={"product_id": 1, "quantity": 1})- Run:
locust -f locustfile.py- Open the Locust web UI, for example
http://localhost:8089, and set:
- Number of users to simulate
- Spawn rate, how fast users appear
Locust will show:
- Current RPS
- Response time percentiles
- Failure rate
This is very useful for backend developers working in Python.
Running a Basic Load Test Step by Step
Here is a simple process you can follow for a single endpoint.
Step 1: Prepare the Environment
- Use a test or staging environment.
- Ensure configuration (CPU, memory, database) is similar to production.
- Seed the database with realistic data size, for example same number of users or orders.
Step 2: Choose a Target Endpoint and Load
Example:
- Endpoint:
GET /api/tasks - Target: 100 RPS
- Duration: 10 minutes
- SLA: P95 < 300 ms, error rate < 1 percent
Step 3: Configure the Tool
Example using Locust (simple version):
from locust import HttpUser, task, between
class TaskUser(HttpUser):
wait_time = between(0.1, 0.5)
@task
def list_tasks(self):
self.client.get("/api/tasks")Then:
- Run Locust.
- Set users so that throughput moves towards 100 RPS (you may need to increase or decrease).
Step 4: Monitor the System
While the test runs, watch:
- CPU usage on application server and database
- Memory usage
- Database metrics (queries per second, slow queries)
- Application logs for timeouts or errors
Use tools like:
top,htop,vmstaton Linux- Database dashboard (for PostgreSQL, for example
pg_stat_activity)
Step 5: Analyze the Results
Look at:
- P50, P95, P99 latency
- Error rate
- How metrics behave over time. If latency increases slowly, there might be resource saturation or garbage collector pressure.
Example interpretation:
- At 100 RPS:
- P95: 250 ms
- Error rate: 0.2 percent
- CPU: about 60 percent
- At 150 RPS:
- P95: 550 ms
- Error rate: 5 percent
- CPU: 90 percent, database connection pool maxed
Conclusion: Safe limit is around 100 RPS on this hardware. You also discovered that the database pool may be a bottleneck.
Interpreting and Acting on Results
Performance testing only helps if you act on what you learn.
Finding Bottlenecks
Common bottlenecks:
- Slow database queries (missing indexes, too many joins)
- N+1 queries in ORM code
- External API calls that are slow
- Blocking operations in async code
- Insufficient connection pools
You usually:
- Use application logs and metrics to find the slow part.
- Fix or optimize it.
- Run the same performance test again.
- Compare results.
This loop is very similar to unit testing, but focused on performance.
Regression Testing
When you change code or upgrade dependencies, performance can get worse.
To detect that, you can:
- Save previous test results as a baseline.
- After each significant change, run the same test.
- Compare P95, P99, and throughput.
Some teams automate this in CI/CD, for example:
- For every new release candidate, run a short performance test.
- Fail the pipeline if the endpoint is 30 percent slower than baseline.
Good Practices and Common Pitfalls
Good Practices
- Test early, not just before going live.
- Start with small loads, then step up gradually.
- Keep test scenarios under version control, for example commit Locust scripts to Git.
- Use metrics and logging from the beginning of the project so you can understand test results.
Common Pitfalls
- Testing only “hello world” endpoints instead of real logic.
- Using empty or tiny databases, which hides performance problems.
- Focusing only on throughput, not on user-facing latency.
- Running tests from your laptop on poor network and trusting raw numbers.
- Forgetting to reset the environment between tests, for example caches and data.
Connecting Performance Testing to Optimization
Performance testing is not optimization itself. It is the measuring tool.
Typical simple workflow:
- Run a test, discover that
/api/searchhas P95 = 1000 ms at 50 RPS. - Use logs and traces to see that the main time is spent in a slow SQL query.
- Add an index or change the query.
- Run the same test again.
- See P95 = 200 ms at 50 RPS.
Rule: Never optimize without measurement. Performance testing gives you data so you optimize the right part of the system.
In later chapters about database optimization, caching, and load balancing, these techniques will be your main tools to fix the problems that performance tests reveal.
Views: 8
KAHIBARO