Chapter 14Lesson 01~145 minutes

REST and JSON API Performance Testing: Core Concepts and Mental Model

Chapter 13 turned browser traffic into a small reviewed HTTP plan. REST/JSON testing goes one step further: the test is designed directly from the API's resource lifecycle. A fast HTTP 200 is not enough if the returned JSON is wrong, a generated resource ID is not correlated, users mutate the same object, or cleanup leaves thousands of synthetic records behind.

RESTJSONCRUD lifecycleCorrelationCleanup

Learning objectives

  • Model an API scenario as resource state transitions rather than isolated requests.
  • Keep transport success, business correctness, and performance acceptance as separate checks.
  • Identify the JMeter state that controls base URL, headers, JSON body, correlation, assertions, and cleanup.
  • Understand why generated resource IDs belong in thread-local variables.
  • Use JTL/dashboard plus independent service logs/metrics for release-quality evidence.
  • Inspect API state non-destructively before starting a write workload.

1. The practical problem: writes create state

A read-only GET can be repeated safely against an immutable fixture. A POST/PUT/DELETE sequence changes target state. If 50 virtual users all update resource 42, the test can measure lock/contention and lost-update behavior caused by the test data rather than the intended API capacity. If failed iterations skip DELETE, synthetic resources accumulate and later runs begin from a different database state.

Mandatory target boundary: all executable examples use only http://127.0.0.1:8000, synthetic resources, no authentication secrets, maximum 2 threads, short finite loops, and a run-scoped cleanup endpoint. Never substitute a public demo API or production service.

2. Mental model: resource lifecycle plus evidence

REST/JSON resource lifecycle

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
W[Workload + run/user data] --> D[HTTP Request Defaults + Header Manager]
D --> P[POST create JSON]
P --> R1[201 JSON response]
R1 --> X[JMESPath extractor -> ITEM_ID]
X --> G[GET /items/${ITEM_ID}]
G --> A1[Status + JSON assertions]
A1 --> U[PUT /items/${ITEM_ID}]
U --> A2[Version/value assertions]
A2 --> Z[DELETE /items/${ITEM_ID}]
Z --> C[Cleanup verification / run sweep]
P --> J[JTL + jmeter.log]
G --> J
U --> J
Z --> J
P --> S[Fixture event log / metrics]
G --> S
U --> S
Z --> S

The workload supplies a run ID and synthetic user identity. Defaults define the local host/port; Header Manager defines JSON media types. POST creates target state and returns a server-generated ID. A Post-Processor extracts that ID into the current thread's variable map. GET and PUT reuse it; assertions verify both protocol and JSON semantics. DELETE removes the resource. JTL records client-observed samples while the fixture log/metrics independently records server-side operations and cleanup state.

3. Three different meanings of “success”

Layer Example Why separate it?
Transport POST returns HTTP 201; GET/PUT return 200; DELETE returns 204. Protocol contract can fail even when body looks plausible.
Business correctness Created item's owner/run/name/value are correct; update increments version. HTTP 200 can still carry wrong/stale business data.
Performance acceptance p95 GET/PUT/transaction metric meets the stated threshold under valid load. A correct response can still be too slow; a fast error path is not acceptable.

4. REST resource lifecycle for this chapter

The local fixture exposes a deliberately small resource model:

  • POST /v1/items → create a synthetic item and return HTTP 201 with generated item.id.
  • GET /v1/items/{id} → read it.
  • PUT /v1/items/{id} → update selected fields and increment version.
  • DELETE /v1/items/{id} → remove it using an idempotent cleanup contract.
  • GET /v1/items?run_id=... → inspect run-scoped active state.
  • DELETE /v1/runs/{run_id}/items → bounded final cleanup of this synthetic run only.

5. JSON request and response state

JMeter's HTTP sampler can send JSON as raw Body Data. HTTP Header Manager supplies Content-Type: application/json and Accept: application/json. JMeter does not magically JSON-escape arbitrary variable contents; this lab uses controlled alphanumeric/synthetic values so direct interpolation is safe.

For real arbitrary user text, use a deliberate JSON-encoding strategy rather than embedding unknown strings inside quotes.

6. Generated IDs are per-user correlation state

After POST, JSON JMESPath Extractor reads item.id into ITEM_ID. That variable is thread-local, so two virtual users can create and operate on different resources without sharing a process-global property.

A hard-coded ITEM-000001 destroys this isolation and turns a multi-user workload into contention on one mutable object.

7. Scope still matters

Place the ID extractor under Create, JSON assertions under the sampler whose body they validate, and cleanup under the resource lifecycle. A broad JSON assertion such as status=ok at Thread Group scope can wrongly fail Create (created), Update (updated), and cleanup responses.

8. Run ID and ownership prevent destructive cleanup

Each created record carries a synthetic run_id and owner. Final cleanup deletes only records with that exact run ID. This is the API equivalent of Chapter 11's shard/data-ownership contract.

Never write a cleanup step that says “delete all items” against a shared or uncontrolled environment.

9. State to inspect before changing the test

State Read-only inspection
Generator JMeter/Java versions, CPU/memory, listeners, result-save config.
Thread/arrival Threads, loops, timers/rate model, configured versus achieved samples.
Defaults/properties BASE_HOST, BASE_PORT, RUN_ID, timeouts, environment selection.
Variables/data OWNER, ITEM_ID, expected version/value; no cross-thread sharing.
Protocol Methods, status codes, Content-Type/Accept, connection/timeouts.
API resource state Active resources for RUN_ID before the test.
Assertions Which sampler checks status/JSON; failure messages retained in JTL.
Cleanup Per-item DELETE plus bounded run-scoped sweep/verification.
Results Raw JTL, jmeter.log, dashboard, fixture event log/metrics.
Validity Generator headroom and zero unintended shared-resource collisions.

10. Non-destructive preflight

Before a write run:

curl --fail --silent http://127.0.0.1:8000/health
curl --fail --silent http://127.0.0.1:8000/metrics
curl --fail --silent "http://127.0.0.1:8000/v1/items?run_id=RUN-DEMO"

Expected initial active count for a fresh run ID is zero. Record the fixture version, schema examples, and metrics before modifying state.

11. Client and server measurements answer different questions

JTL elapsed measures JMeter's sample timing over the protocol operation. Fixture service_wall_ms measures the synthetic server handler interval. Generator CPU/GC/listener cost can reduce achieved load without increasing server service time. Timers/pacing are intentionally outside endpoint response latency.

12. DevOps connection

API performance evidence is strongest when every created object can be attributed to a run/user, every generated ID is correlated, correctness failures are visible, cleanup is scoped, and server/client evidence can be attached to a release decision. That makes the test reproducible infrastructure rather than disposable traffic.

Knowledge check

Why is HTTP 201 on Create not enough to prove success?

Why should ITEM_ID be a JMeter variable rather than a property?

What makes run-scoped cleanup safer than DELETE all?

Why compare JTL elapsed with fixture service_wall_ms?

What must be checked before making a capacity claim?

Next lesson

Build the local CRUD lifecycle

Lesson 2 creates the dependency-free fixture, authors JSON requests and headers, extracts IDs, verifies fields/statuses, cleans state, and compares bounded read-heavy with write-lifecycle behavior.

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.