Chapter 14Lesson 05~235 minutes

Checkpoint Lab — REST and JSON API Performance Testing

The checkpoint validates the full operating discipline: generated IDs are correlated, each virtual user owns its resource, protocol/business assertions stay meaningful, one deliberate server failure is preserved rather than retried away, and cleanup succeeds even though the lifecycle contains that failure.

CheckpointPer-user CRUDInjected failureCleanup sweepEvidence packet

Learning objectives

  • Create/read/update/delete one synthetic resource per user/iteration.
  • Correlate server-generated IDs and assert ownership/version/value.
  • Inject one controlled local failure without retrying it.
  • Continue to per-item DELETE and then run an exact RUN_ID cleanup sweep.
  • Predict request/resource/result changes before execution and verify them independently.
  • Produce client/server/generator/cleanup evidence appropriate to a release decision.

1. Exact assumptions and hard ceilings

Item Checkpoint baseline
JMeter Apache JMeter 5.6.3.
Java Java 17 JDK lab baseline; JMeter 5.6.3 requires Java 8+.
Plugins None.
API fixture prompt14-api-fixture-v1, Python standard library.
Target http://127.0.0.1:8000 only.
Load 2 threads × 2 lifecycle loops = 4 created resources maximum.
Thread error action Continue, so the deliberate 500 does not skip cleanup.
Failure injection Exactly one controlled failure per thread or one selected opportunity; never retried.
Final cleanup Exact RUN_ID sweep, then list/metrics verify zero leftovers.
Secrets None; synthetic owner/name data only.
Abort: non-loopback host, >2 threads, >2 lifecycle loops, active_resources above 4 before expected cleanup, repeated uncontrolled 5xx, cleanup targeting any run ID other than the current synthetic RUN_ID, or unsafe generator pressure.

2. Setup and target authorization/preflight

$RunId = "RUN-CH14-CHECKPOINT"
(Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8000/health).Content
(Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8000/metrics).Content
(Invoke-WebRequest -UseBasicParsing "http://127.0.0.1:8000/v1/items?run_id=$RunId").Content

Before the run, require the run-scoped item count to be zero. If it is not, archive the listing and clean only that RUN_ID before starting.

3. Checkpoint JMeter tree

Test Plan
├── UDV / properties: BASE_HOST, BASE_PORT, RUN_ID
├── HTTP Request Defaults
├── HTTP Header Manager: Accept + Content-Type application/json
└── Thread Group — 2 threads × 2 loops
    Action after Sampler error: Continue
    └── Transaction Controller — Resource Lifecycle
        ├── Create Item
        │   ├── status/JSON business assertions
        │   └── JMESPath Extractor item.id -> ITEM_ID
        ├── Read Item
        │   └── ownership/value assertions
        ├── Update Item
        │   └── version=2/value=2 assertions
        ├── Throughput Controller — Inject Failure
        │   Total executions=1; Per User=ON
        │   └── Inject Synthetic Failure
        │       POST /v1/items/${ITEM_ID}/inject-failure
        │       Expected to fail with 500; preserve as deliberate failure evidence
        └── Delete Item
            DELETE /v1/items/${ITEM_ID}
            Assert 204

The Throughput Controller controls how many branch opportunities execute; it is not an RPS controller. Per User=ON with Total Executions=1 produces at most one injected failure per thread (two total) in this 2-thread checkpoint.

4. Required predictions before execution

Prediction A — resource state: each of four lifecycle iterations creates a unique ID owned by that JMeter thread; normal per-item DELETE removes every item even though each thread encounters one deliberate 500.

Prediction B — error evidence: the run contains two intentional failed Inject Synthetic Failure samples (one per thread), while Create/Read/Update/Delete should pass.

Prediction C — cleanup: immediately after normal run, active resources for the RUN_ID should already be zero; final run-scoped cleanup should report deleted=0. If it reports >0, that is valuable evidence that per-item cleanup failed/skipped.

Prediction D — target/JTL distinction: transaction/reporting samples may add JTL rows, but server event counts include only actual HTTP operations.

5. Exact request contracts

Create JSON:

{
  "run_id": "${RUN_ID}",
  "owner": "user-${__threadNum}",
  "name": "checkpoint-${__threadNum}-${__time()}",
  "value": 1
}

Read: GET /v1/items/${ITEM_ID}

Update body:

{"value":2,"name":"updated-${__threadNum}"}

Inject: POST /v1/items/${ITEM_ID}/inject-failure

Delete: DELETE /v1/items/${ITEM_ID}

6. Preserve the injected failure rather than making it green

The injected endpoint returns HTTP 500 and leaves the resource intact. Do not use Response Assertion Ignore Status, a script calling prev.setSuccessful(true), or retries. The 500 is deliberately part of this checkpoint's error taxonomy.

Thread Group continues after sampler error so the subsequent DELETE still executes. This is a cleanup-control choice for the synthetic lab, not a general recommendation to continue after every production error.

7. Run checkpoint in CLI mode

jmeter.bat -n `
  -t plans\checkpoint-api.jmx `
  -JBASE_HOST=127.0.0.1 `
  -JBASE_PORT=8000 `
  -JRUN_ID=RUN-CH14-CHECKPOINT `
  -l results\checkpoint\results.jtl `
  -j results\checkpoint\jmeter.log `
  -e -o results\checkpoint\report

Record generator CPU/memory during the short run. If the injector is unexpectedly saturated, the run is not valid for target-performance interpretation.

8. Verify normal per-item cleanup first

(Invoke-WebRequest -UseBasicParsing `
  "http://127.0.0.1:8000/v1/items?run_id=RUN-CH14-CHECKPOINT").Content

(Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8000/metrics).Content

Expected count=0. If count>0, save the listing before the final sweep so the cleanup failure remains diagnosable.

9. Run bounded run-scoped cleanup even after failure

This is a safety-net operation that can be run independently of the failed JMeter threads:

PowerShell:

Invoke-WebRequest -UseBasicParsing `
  -Method DELETE `
  "http://127.0.0.1:8000/v1/runs/RUN-CH14-CHECKPOINT/items"

Bash:

curl --fail --silent -X DELETE   http://127.0.0.1:8000/v1/runs/RUN-CH14-CHECKPOINT/items

The endpoint can delete only resources tagged with that exact run ID. It cannot delete other runs by accident unless the operator supplies another run ID.

10. Verify zero leaked state

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

Checkpoint is not complete until the run-scoped list reports count 0.

11. Analyze result and service evidence

python tools/analyze_api_jtl.py results/checkpoint/results.jtl
python tools/analyze_api_events.py results/api-events.jsonl

Required error taxonomy:

  • intentional synthetic 500 injections;
  • unexpected protocol errors (must be zero);
  • business/assertion failures (must be zero outside deliberate injected sampler);
  • cleanup/resource leaks (must be zero after final verification).

12. Performance interpretation

Report Create/Read/Update/Delete latency/throughput separately. The synthetic fixture deliberately makes writes slightly slower, so operation mix affects aggregate throughput. The injected 500 path does minimal work and must not be counted as a “fast successful write.”

State configured load (2×2) and achieved valid samples, plus generator headroom and server event timing. This tiny loopback checkpoint validates mechanics; it cannot establish production capacity.

13. Required evidence packet

Artifact Required content
Fixture version/schema prompt14-api-fixture-v1, request/response examples.
Resolved run config BASE_HOST/PORT, RUN_ID, 2 threads ×2 loops, error action.
Correlation proof Four generated ITEM_ID values tied to correct owner/run.
Assertion evidence Status + key JMESPath business checks; failure messages retained.
Error taxonomy Two intentional 500s; zero unexpected protocol/business failures.
Operation metrics Per-label JTL p50/p95/throughput + generator CPU/memory.
Server evidence Operation/status counts + service_wall_ms.
Cleanup evidence Post-run RUN_ID listing, sweep result, final count=0.
Raw artifacts JTL, matching jmeter.log, HTML report, exact JMX/properties.

14. Verification checklist

  • Target resolves exactly to 127.0.0.1:8000.
  • Maximum four resources can be created.
  • Each Create returns unique ITEM_ID and correct owner/run.
  • Read and Update operate on that thread's correlated ITEM_ID.
  • Exactly two deliberate 500 samples are preserved; no retry/Ignore Status hides them.
  • Delete executes after injected failure because Thread Group action is Continue.
  • Run-scoped cleanup targets only the checkpoint RUN_ID.
  • Final RUN_ID item count is zero.
  • JTL/jmeter.log/server events/generator state are preserved.

15. Validity statement

Example: “Using Apache JMeter 5.6.3 and Java 17 against the Python-standard-library prompt14-api-fixture-v1 on 127.0.0.1:8000, two threads executed two isolated resource lifecycles each. Create returned server-generated IDs that were extracted into thread-local variables; Read and Update assertions verified owner/run/value/version. One controlled HTTP 500 was injected per thread and preserved as a failure; the Thread Group continued so per-item idempotent DELETE still executed. A final cleanup endpoint scoped to RUN-CH14-CHECKPOINT was run independently and the subsequent list verified zero leaked resources. Operation-level JTL metrics, matching jmeter.log, generator utilization, and server event timing were preserved. This proves REST/JSON correlation, correctness, failure classification, and cleanup mechanics on a tiny loopback fixture—not production capacity, public-API authorization, or retry safety for arbitrary writes.”

16. Cleanup / rollback

  1. Verify current RUN_ID count=0.
  2. Run the exact RUN_ID sweep again if needed; its lab contract is idempotent.
  3. Stop api_fixture.py.
  4. Retain JTL/log/report/event evidence until review completes.
  5. Delete disposable synthetic artifacts according to local policy.
  6. No public API, real credential, recorder CA, remote engine, database, container, paid service, or system-wide JVM/OS configuration was changed.

17. What Chapter 14 adds to the operating model

The performance-testing operating model now has an API resource-lifecycle contract: base URL authorization, request/response schema, media headers, per-user resource ownership, correlated generated IDs, transport/business assertions, read/write metric populations, explicit error taxonomy, cleanup/sweep ownership, and zero-leak verification are reviewed before a result becomes release evidence.

Chapter 15 moves to Web Services, SOAP, Authentication, Tokens, and Session Workflows. The resource and JSON discipline from this chapter carries forward, while Chapter 15 adds authentication state, token/session renewal, credential boundaries, and SOAP/web-service protocol concerns.

Knowledge check

Why does the checkpoint keep the deliberate 500 as a failed sample?

What allows cleanup to continue after that 500?

Why is the final RUN_ID list stronger than trusting DELETE samples alone?

Why must the injected 500 not be included as a fast successful write?

What is the Chapter 15 bridge?

Next chapter

Web Services, SOAP, Authentication, Tokens, and Session Workflows

Chapter 15 adds credential/token/session state, authentication renewal, and SOAP/web-service testing while retaining the authorization, correlation, assertion, evidence, and cleanup discipline established here.

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.