Chapter 14Lesson 04~180 minutes

REST and JSON API Performance Testing: Diagnostics, Failure Modes, and Production Practices

API failures are especially easy to misclassify because traffic and data state interact. A 409 may be caused by two test users mutating the same object; a fast HTTP 200 may contain a business rejection; a “cleanup fix” can delete data it does not own. Diagnose the request, correlated state, generator, and target in that order.

DiagnosticsShared resourceHard-coded IDsSafe cleanupWrite retries

Learning objectives

  • Reject public/demo APIs as load targets without explicit authorization.
  • Diagnose hard-coded IDs/tokens and shared mutable resource collisions.
  • Detect business errors hidden behind successful HTTP status.
  • Keep deletion bounded to owned synthetic data.
  • Prevent sensitive response-body retention in result artifacts.
  • Explain why blind retries of POST/PUT can corrupt the workload.

1. Preserve first-failure evidence

All reproductions stay on http://127.0.0.1:8000, ≤2 threads, short loops. Preserve failed JMX, resolved properties/variables, raw JTL, matching jmeter.log, fixture event log/metrics, RUN_ID resource listing, generator CPU/memory, and exact CLI before changing anything.

2. Diagnostic sequence

REST/JSON diagnostic sequence

The resource ID is target-generated and becomes thread-local correlation state. Protocol success, business correctness, cleanup state, and performance metrics are verified independently.

flowchart TD
E[Preserve JMX + JTL + jmeter.log + API events + RUN_ID state] --> V[Confirm JMeter/Java/plugin/tool versions]
V --> C[Confirm CLI + BASE_HOST/PORT + authorized target]
C --> S[Validate tree scope + headers + resolved variables/properties]
S --> D[Inspect ITEM_ID / owner / RUN_ID / JSON body]
D --> P[Inspect method/status/response/business assertion]
P --> G[Inspect generator CPU/GC/listener/save-service]
G --> T[Inspect API event log/metrics/resource state]
T --> X[Distributed/CI/container state if relevant]
X --> F[Least destructive correction]
F --> R[Small bounded rerun + cleanup verification]

3. Failure mode: load testing a public demo API

A public sample service may be rate-limited, shared with other learners, or prohibit load testing. Even “just 10 threads” is traffic you do not control or own. Results are also scientifically weak because target topology/background load is unknown.

Repair: use the local fixture or an explicitly authorized private environment. Public API documentation can teach request syntax, but it is not automatic permission to generate load.

4. Intentionally broken example: hard-coded ID

Broken tree:

Create Item -> extracts ITEM_ID but later samplers ignore it
Read Item   -> GET /v1/items/ITEM-000001
Update Item -> PUT /v1/items/ITEM-000001
Delete Item -> DELETE /v1/items/ITEM-000001

With two threads, both mutate/delete the same old resource while their newly created resources leak. Symptoms: 404s/incorrect owners, unexpected version changes, active_resources grows.

Repair every downstream path to use ${ITEM_ID}, assert owner/run ID, then perform the run-scoped cleanup for leaked synthetic items. Preserve the broken resource listing/event log before cleanup.

5. Failure mode: HTTP status is the only correctness check

Imagine the API returns HTTP 200 with {"status":"rejected","reason":"quota"}. A status-only test reports success and may even show lower latency because the target skipped normal work.

Add narrow JSON business assertions. Never declare a performance improvement while the valid-success path changed.

6. Failure mode: one mutable resource shared by all users

Two threads both update one ID. If the target serializes updates or applies optimistic concurrency, latency/errors now reflect intentional contention on one object. That is valid only when production really has that hot-resource pattern.

For normal per-user workflows, Create per thread, extract ITEM_ID, and keep ownership isolated.

7. Failure mode: cleanup deletes uncontrolled data

A script calls DELETE /v1/items or enumerates every item and deletes it. On a shared environment this can destroy data from other users/tests.

Cleanup must prove ownership. The course fixture allows only DELETE /v1/items/${ITEM_ID} for known created IDs and DELETE /v1/runs/${RUN_ID}/items for the exact synthetic run. Never generalize that endpoint to production without authorization and datastore guarantees.

8. Failure mode: retaining sensitive response bodies

Saving response bodies, tokens, personal data, or headers into JTL/logs can create a new data-exposure surface. Current JMeter CSV output does not save response data by default—keep that safer default for load.

For debugging, use one-thread bounded synthetic data and remove/secure artifacts. Do not enable response-data-on-error globally without understanding what may be captured.

9. Failure mode: blindly retrying write failures

POST times out after the server created a record, so a blind retry creates another. PUT may be safe or unsafe depending on semantics/version checks. Retrying changes offered load and can hide the first failure.

Preserve the failure. If the real API supports idempotency keys or conditional requests, model them explicitly in the appropriate chapter/environment. Do not add blanket retries merely to improve pass rate.

10. Failure mode: malformed JSON or wrong media type

A copied form request sends JSON text but lacks Content-Type: application/json, or variable interpolation produces invalid JSON. The server returns 400/415/422. Inspect exact request headers/body in a one-thread debug run, not under load.

Do not solve a contract error by relaxing target validation.

11. Failure mode: JSON assertion scoped too broadly

A Thread Group-level JMESPath assertion expects item.id on every response, including DELETE 204 with no body. DELETE now fails because an assertion intended for Create/Read was inherited.

Move JSON assertions under the exact JSON-returning sampler or a compatible controller scope.

12. Causal performance separation

Symptom Test/generator cause Target cause to distinguish Evidence
Write p95 rises shared mutable ID contention / extra validation server/database saturation ITEM_ID ownership + server operation timing.
Achieved RPS falls heavy JSON assertions/listeners/generator CPU target latency increase generator CPU/GC + server service_wall_ms.
Many 404/409 stale/hard-coded IDs or cleanup collision real resource consistency defect resolved IDs + event/resource state.
Fast responses, low error % status-only check accepts business rejection real optimization JSON business assertions + target branch.
CI only fails wrong BASE_HOST/fixture missing/runner quota API regression resolved properties + jmeter.log + runner resources.

13. Shortcuts to reject

  • Do not switch to a public demo API because localhost seems “too easy.”
  • Do not hard-code captured IDs/tokens.
  • Do not add blanket retries or long sleeps to writes.
  • Do not raise thread count while resource ownership/correctness is unresolved.
  • Do not use a process-global property as one mutable ITEM_ID for all users.
  • Do not disable TLS/RMI verification or enlarge heap without evidence.
  • Do not delete the failed JTL/jmeter.log/API event evidence after repair.

Knowledge check

Why can a hard-coded item ID make active_resources grow?

Why is an HTTP 200 business rejection dangerous for performance analysis?

What is wrong with deleting every resource during cleanup?

Why are blind POST retries especially risky?

What evidence separates generator JSON-assertion cost from server slowdown?

Next lesson

Checkpoint: fail once, clean anyway

Lesson 5 creates isolated per-user resources, injects a controlled 500 after update, continues to per-item delete, runs a bounded RUN_ID cleanup sweep, and proves zero leaked state.

Official references and version notes

Version and compatibility note

Version-sensitive behavior was rechecked against current Apache JMeter primary documentation on 2026-09-05. The course baseline remains Apache JMeter 5.6.3 with a Java 17 JDK for labs and no third-party plugins; JMeter 5.6.3 requires Java 8+. HTTP Request samplers are used directly for GET/POST/PUT/DELETE. JSON JMESPath Extractor is a built-in Post-Processor for extracting response values into JMeter variables; JSON/JSON JMESPath Assertions parse JSON and fail on missing/invalid paths before optional value comparison. Assertion failure messages are enabled by default in CSV result output and are kept explicit in chapter commands. Meaningful load runs use CLI mode with raw CSV JTL plus matching jmeter.log; HTML dashboard generation uses the same JTL. The mandatory API fixture is Python-standard-library only and runs on loopback, so no container, cloud service, database, external API, driver, or plugin is required.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.