Skip to content
← Free FDE training guides

Free learning / 50-minute HTTP retry proof workshop

Retry amplification: count the calls that actually arrive

Run two actual loopback HTTP hops. Compare nested and single-owner retries, invalid queries and transient recovery using received request records, then change the attempt limit.

No account needed. Use your own copy for writing and keep the original as a reference.

Engineering lab library / Run locally

Count both HTTP hops.

Predict six outcomes, inspect the actual received requests, then change attempts and the supplied recovery condition separately before handing over the retry decision.

Actual GETs between one caller and two owned loopback servers. Sequential bounded attempts; no SDK, external provider, business write, backoff, jitter or performance measurement.

  1. Start here / Python 3.11+ · Standard library

    Actual HTTP retry ownership and amplification

    Count actual GET arrivals, compare nested with single-owner retries, stop unchanged invalid requests and change the recovery threshold.

    Python 3.11+ · Standard library · Two owned loopback servers · No external service

Original LockedIn Labs proof workshop · 50 planned minutes, excluding setup · Python 3.11+ standard library. A caller contacts one local service, which contacts a second local provider fixture. Actual HTTP GETs, query validation and handler observations execute on loopback. There is no SDK, external provider, model, business write or load benchmark.

1. Separate one request from its attempts

A retry sends another attempt after a result the client considers recoverable. An attempt limit includes the initial call: three total attempts allow two retries. When a caller permits three service attempts and that service permits three provider attempts per request, one failed lookup can become nine provider requests.

The number is a bound under the stated behavior, not a universal prediction. A success or a non-retryable result can stop a loop early. Another hidden SDK, proxy or queue retry layer can change the bound. Start by identifying who owns each loop and which event consumes an attempt.

This workshop compares a deliberately broken policy with a bounded single-owner candidate. The broken policy retries every non-200 response in both layers. The candidate lets the caller alone retry a received 503, and each service request makes one provider call. This is a small read-only teaching contract, not a production retry policy to paste into a client.

Illustrative learning artifact

One event. Two transaction boundaries.

Source transactionCase update + outbox eventCommit both or neither

event-42 → delivery may repeat →

Receiver transactionEvent identity + taskDuplicate delivery → existing result

2. Follow the two actual HTTP hops

The runner binds two owned servers to 127.0.0.1 on temporary ports. A normal request carries GET /lookup?item=synthetic. The provider validates that query. Invalid cases omit item, so its actual query check returns 400. Always-unavailable cases return a supplied 503. Transient cases return 503 for the configured number of provider requests, then 200.

Caller-attempt and service-attempt headers correlate the local observations; they are not credentials or a distributed budget. The service forwards the unchanged query and actual status. Each provider handler records the request it received. The servers and their threads are shut down and closed after each independently reset case.

The browser mechanism is an authored explanation. Your runner report supplies the actual request evidence. No request is sent by opening this page.

UNDERSTAND THE SYSTEM

One lookup can arrive nine times downstream

Five authored frames explain actual sequential GETs between one caller and two loopback servers in one Python process. Counts come from handler observations; no browser request, SDK, jitter, deadline or load benchmark is executed here.

FOLLOW THE WORK

Watch one example, step by step.

5 authored stepsYou control the pace
01 / EXPLORE THE PARTSControls loading · written explanations below
Direction of information or workReturn / feedback

WHAT THIS PART DOES

Calling application

The caller can send up to three service attempts. Each is part of the same synthetic lookup, not a new user task.

CONNECTED TO
  • Caller attempts: Local service
Read the full worked example
  1. 01
    Name both loops

    Three total attempts mean one initial attempt and two retries at each configured layer.

    Default limits: Caller 3 · nested service 3

  2. 02
    Count the unavailable arrivals

    Three outer calls each cause three provider GETs. The final 503 accompanies nine actual provider request records.

    nested-unavailable: Caller→service 3 · provider 9 · final 503

  3. 03
    Inspect the unchanged invalid query

    The provider actually checks the missing item and returns 400. The broken nested policy repeats it nine times without repairing the input.

    nested-invalid: Same missing item · nine received 400s

  4. 04
    Set ownership and classification

    The bounded candidate leaves one provider call per service entry. It sends three requests for persistent 503, but stops after the first received 400.

    single-owner-unavailable / invalid: Provider 3 for 503 · provider 1 for 400

  5. 05
    Compare the same successful result

    After two supplied provider failures, both transient candidates reach 200 with three provider calls. Nested ownership uses one service entry; caller ownership uses three. The final status alone does not reveal the path.

    nested-transient / single-owner-transient: Same provider 3 · service entries 1 versus 3

Read the connections in plain English
  1. 01
    Calling application → Local service

    The root loop can enter the service three times for one lookup.

  2. 02
    Local service → Provider fixture

    An independent inner loop can send three requests per caller attempt.

  3. 03
    Provider fixture → Received request records

    Each HTTP handler appends the query, correlated attempt numbers and actual returned status.

  4. 04
    Received request records → Retry ownership and classification

    Nine unchanged missing-item requests still return 400; a final status hides that repeated work.

  5. 05
    Retry ownership and classification → Local service

    With a single caller retry owner, the service no longer resets its own loop.

  6. 06
    Retry ownership and classification → Receiving engineer

    Explain the observed work and the still-untested production contract before adapting a retry policy.

Check your understanding: Does choosing a single retry owner establish successful recovery?

No. It can bound extra work while still returning 503, or stop before a later success. Count actual arrivals, classify the received failure and identify the operation's contract. Backoff, waiting instructions, end-to-end deadlines, authorization and safe side-effect handling require separate evidence.

3. Predict and run six comparisons

Download the standalone Python runner, inspect it and run in a disposable folder:

Code example
python3 --version
python3 fde-http-retries.py > retries-original.json
python3 fde-http-retries.py --case nested-unavailable --attempts 2 > retries-nested-two.json
python3 fde-http-retries.py --case single-owner-transient --attempts 2 --failures 1 > retries-recovered.json

Use py -3 on Windows if that is your launcher. No packages or keys are needed. The runner accepts only named cases, attempts from 1 through 4 and transient failures from 0 through 8. It accepts no user-selected host, URL or port and does not use an inherited HTTP proxy. These deliberately immediate retries stay inside the two owned local servers.

Predict both hop counts before opening the default report:

CaseCaller → service requestsService → provider requestsFinal caller status
nested-unavailable39503
single-owner-unavailable33503
nested-invalid39400; unchanged missing item repeated
single-owner-invalid11400; no second attempt
nested-transient13200 after two provider failures
single-owner-transient33200 after two provider failures

The final two cases have the same provider count, with different retry ownership. A successful final status does not reveal how much work occurred or where it repeated. Reducing attempts bounds extra work; it does not repair an unavailable dependency.

4. Inspect the requests rather than just the final status

Read caller_attempts, service_requests, provider_requests, counts and final_http_status. In nested-unavailable, group the provider records by caller_attempt: each outer attempt contains service attempts 1, 2 and 3. These are actual received GETs, not nine entries generated from multiplication alone.

In nested-invalid, inspect path and status. Every forwarded query still lacks item; repeating it cannot satisfy the provider's unchanged validation rule. The candidate stops on that received 400 because it classifies the status separately from choosing a retry owner. A single owner that still retries every error could continue repeating the same invalid input. This is not a malformed HTTP parser test or a claim that every 4xx status has the same recovery contract.

In transient cases, the provider's availability condition is supplied fixture behavior: after two failed valid requests, its next valid request returns found. Compare the nested service's one incoming request with the single-owner service's three. Do not infer a throughput or latency improvement from those counts; the experiment measures neither production performance nor model cost.

Check owned_servers: both should bind to loopback, have different ports and end with stopped: true. The report is emitted after cleanup. Unexpected transport or handler errors fail the run rather than being silently converted into a retryable 503. The selected cases exercise received statuses; lost responses and in-flight cancellation require separate evidence.

5. Change the limit and the recovery condition separately

With --attempts 2, always-unavailable nested retry produces four provider requests; the single-owner case produces two. With the default two transient failures, two attempts at the caller-owned layer stop at 503 before the third provider call can succeed. The nested two-by-two policy can reach the third provider request during its second outer attempt and return 200. That later success does not erase its different work budget.

Then hold attempts at two and change only --failures 1: the caller-owned transient case returns 200 after two provider requests. With failures zero it succeeds immediately. Keep original and changed reports separate, including the prediction you made before each run.

For logical deadline and shared-budget reasoning, return to the retry-budget model. Its delays and deadlines are supplied logical values. This new HTTP runner does not implement a shared cross-service budget, end-to-end deadline, backoff wait, jitter, Retry-After handling, cancellation or a fleet load test. The two exercises answer different bounded questions.

6. Give another engineer the receiving task

Allow ten minutes for receiving review. One person retains the default report. Another predicts and runs nested-unavailable with two attempts, then locates four actual provider requests. Swap roles for single-owner-invalid and the changed transient condition. Each person keeps their own command, prediction, received evidence, assistance and correction. Solo practice is self-review.

The application owner identifies its own loop and the useful request contract. The service/SDK owner inventories retries already performed by adapters or libraries. The dependency owner supplies retryable failure meaning, safe operation semantics, overload behavior and any waiting instructions. The pod lead retains the bounded work limit and the next authorized environment test. None inherits another owner's permissions or infers client readiness from this exercise.

Use a short receiving note:

Text example
Synthetic participant / exact runner and Python version:
Prediction / command / original or changed condition:
Actual caller-to-service count / provider count / final status:
Provider request records supporting that count:
My own action / assistance / first explanation and correction:
Actual retry owner / limit / error classification still to verify:
Application owner / service or SDK owner / dependency owner:
Next bounded test / receiving role / evidence still missing:

7. Defend the policy in an interview

  • Why can two three-attempt loops send nine provider requests? Each outer attempt starts a fresh inner loop. Both continue under the deliberately broad failure rule in that case.
  • Does a single retry owner guarantee success? No. It can bound extra work while still returning 503, or stop before a later recovery. Its placement and limits need workload evidence.
  • Why not retry the missing-item response? The same invalid query cannot pass the unchanged validation rule. Correct the request under its actual contract; do not generalize all error statuses from this one case.
  • Does backoff remove retry amplification? Waiting can change arrival timing, but it does not erase requests already sent. Backoff, jitter, budgets and ownership need separate design and tests.
  • Can this be used for an uncertain payment or tool action? This exercise performs only synthetic reads. For side effects, use the operation's authorization, idempotency and reconciliation contract before another attempt; start with the HTTP outcome workshop.

Primary references

Amazon's retry/backoff guidance discusses layered amplification and retry placement. Google SRE overload handling distinguishes request and client retry budgets. RFC 9110 defines the HTTP status semantics; the fixture supplies the availability condition. Checked 29 October 2026. The code, cases and receiving exercise are original LockedIn Labs materials, not a vendor SDK or performance claim.

Before you move on

Keep the evidence of your work.

  • Individual predictions beside actual caller/service/provider request records.
  • Changed attempt and transient-failure comparisons with received statuses intact.
  • A receiving note naming application, service/SDK and dependency responsibilities.

Keep these in your own working copy. Reading this page does not save work or record completion.

Suggested next free session

Enterprise integration & safe change

Connect actual HTTP retry ownership with the existing logical deadline/shared-budget model and safe integration boundaries. This is a suggested sequence; choose the practice that addresses your current gap.

Open the next free guide

Free account

Give your practice a home.

Try a role exercise with account drafts and a downloadable worksheet. Keep the files you made in this guide in your own working copy.

Create a free learning account

Guided training · by application

Build toward a program.

Request structured lessons and a training seat. Scope, schedule, feedback arrangements and tuition are confirmed in writing before you commit.

Explore a training application
Have Academy access?

Use your existing training seat to open worked explanations and practice in the related lesson. Creating a free account does not grant a seat.

Open the related Academy lesson →
Back to top ↑