All resources
Performance10 min read

Load testing with realistic workloads and useful conclusions

Plan traffic models, data shape, ramp patterns, and success criteria so a load test predicts production instead of producing vanity numbers.

A practical PingFlow guide for developers working at the boundary between systems.

At a glance

Key takeaways

  • A load test is a hypothesis
  • Model users and dependencies, not just requests
  • Use ramp, steady, and burst phases
In this guide

A load test is a hypothesis

A load test should answer a question such as whether the service can handle a launch-day burst, how many webhook deliveries a worker can process, or when database latency becomes unacceptable. The test is not a contest for the highest requests per second. Write the workload, capacity assumption, target latency, allowed errors, and resource constraints before running it. A number without context cannot guide a release.

Use a production-like environment when the risk justifies it, but protect real users and data. A staging system with a different instance size, database indexes, cache state, or provider quota may answer a different question. Record those differences and avoid presenting the result as a guarantee.

Model users and dependencies, not just requests

A realistic model includes read and write mix, authentication, payload sizes, cache hit rate, tenant distribution, slow operations, retries, and background work. A test that sends the same small GET request to one endpoint can make a service look healthy while production spends most time parsing uploads or waiting on a provider. Use traces and access logs to estimate the actual operation mix.

External dependencies need a policy. Use a sandbox, a controllable stub, or a recorded response when a real provider cannot absorb test traffic. If you stub a dependency, preserve realistic latency, errors, rate limits, and payload sizes. An instant stub can hide connection-pool exhaustion and timeout behavior that dominates production.

Use ramp, steady, and burst phases

A ramp reveals when throughput stops scaling and which resource saturates first. A steady phase shows whether memory, connections, queues, or caches drift over time. A burst tests autoscaling, cold starts, rate limits, and recovery. Keep each phase long enough to observe the system's control loops. A five-second spike cannot tell you whether a queue drains within a useful window.

Warm-up matters for caches and autoscaling, but do not discard the cold-start experience when it affects users. Run a cold test, a warm test, and a scale-out test separately. Record instance count, concurrency, CPU, memory, database sessions, provider calls, and queue age with the load generator's results.

Define success across latency and correctness

A load test passes only if the system remains correct. Assert response schemas, authorization boundaries, idempotency, event counts, and durable writes, not only status codes. Track tail latency rather than average latency. A request that waits five seconds can be a failure even when the average is one hundred milliseconds. Include errors caused by the client or test data in a separate category so they do not distort the service result.

Set resource thresholds and a recovery target. The service may handle the peak but fail to return to normal because a queue or connection pool remains full. A healthy test includes ramp down and a check that backlog, memory, and error rate recover. If the system requires a manual action, document it rather than calling the test successful without it.

Find the first saturation boundary

Use correlated telemetry to identify whether CPU, memory, database locks, connection checkout, network, provider quota, or application code is the first constraint. Adding instances cannot fix a database lock, and adding database connections cannot fix a provider rate limit. Inspect traces for queue time and downstream latency, not only total request time.

Change one capacity variable at a time and rerun the same workload. A larger instance may reduce latency but increase cost; a lower concurrency may protect the database but require more instances. Capture the tradeoff in the result. The conclusion should say what the service can handle, under which assumptions, and what signal tells an operator that the assumption is no longer true.

Protect the test and production data

Use synthetic accounts, recognizable test IDs, and a cleanup plan. Keep load credentials separate and restricted. Do not send real customer messages, charges, or destructive requests. A staging load test can still expose secrets through logs, so use the same redaction and retention controls as production. Rate-limit the generator and include a kill switch that stops the test if the environment behaves unexpectedly.

Coordinate with infrastructure and provider owners before testing. Notify on-call teams, identify the expected time window, and label traffic so it is not mistaken for an attack. After the test, remove temporary data, review costs, and verify that caches and queues do not retain test content.

Turn results into a capacity plan

Store the workload definition, code and image versions, environment shape, data scale, results, anomalies, and conclusion. Compare runs over time rather than comparing only peak throughput. A regression may appear as higher tail latency at the same load or a slower recovery after a burst. Create a follow-up for every unexplained saturation boundary.

Load testing is valuable when it reduces uncertainty. Model real work, include dependencies, run through cold and warm states, assert correctness, find the first bottleneck, and publish assumptions. The output should be a decision about capacity and risk, not a flattering number for a slide.

Publish the assumptions with the result

A capacity result should name traffic mix, payload size, data volume, cache state, instance limits, dependency behavior, and the test duration. State whether the result represents a cold start, warm steady state, or recovery after a burst. Include tail latency, error categories, resource saturation, and backlog age. This makes the result useful to someone planning a release months later.

Repeat the test when the workload or architecture changes. If a result is no longer representative, mark it as historical rather than allowing an old number to guide a new promise. The strength of load testing is not precision to the last request; it is a shared, evidence-backed understanding of where the system stops meeting its contract.

Include the failure boundary

A capacity test should continue far enough to show how the service fails: queue growth, rejected requests, throttled dependencies, memory pressure, or elevated tail latency. Stop before data corruption, but capture the threshold and recovery time. Repeat with warm and cold instances and with the same connection and rate limits used in production. The failure boundary gives planning teams a safer margin than a single peak-throughput number.

Implementation example

Describe traffic mix, payload size, data volume, cache state, instance limits, dependency behavior, and test duration before generating load. Include warm steady state, cold start, burst, recovery, and failure-boundary scenarios. Keep test data synthetic or approved and make destructive operations impossible by default.

javascript
export const options = { stages: [{ duration: '2m', target: 50 }, { duration: '5m', target: 50 }] };
export default function () { http.get(`${__ENV.BASE_URL}/resources`); }

Verify and troubleshoot

Report throughput, p50/p95/p99 latency, error categories, resource saturation, queue age, connection waits, dependency throttles, and recovery time. Compare the result with the production SLO and a previous baseline. Stop before corruption, but capture the threshold where the service rejects, queues, times out, or sheds work.

Operations and recovery

Run tests in an isolated environment or approved low-risk window with owners watching dependencies and aborting safely. Store the workload and environment assumptions with the result and mark old results historical. Use capacity margins rather than peak throughput as the release decision, and repeat when architecture or traffic mix changes.

References and further reading

Use the chosen load tool's scenario and threshold documentation, Google SRE capacity planning, and the service's rate-limit and dependency contracts. Treat test data and generated traffic as operational risk.