Failure Handling: Beginner
What you will be able to do
Transient vs Permanent Failures
Distinguish transient, permanent, and ambiguous failures and choose the response category for each.
The Two Buckets
| Failure Type | What Causes It | Correct Response |
|---|---|---|
| Transient | Temporary network blip, downstream restart, brief rate limit, transient resource contention | Wait, then retry; the next attempt usually succeeds |
| Permanent | Wrong credentials, malformed input row, schema mismatch, missing required field | Stop, alert, route the problem to a human or to a quarantine bucket called a dead letter queue (DLQ for short, covered fully in the intermediate tier) |
| Ambiguous | Generic 500 errors, undocumented HTTP responses, unclear exceptions | Retry a small bounded number of times, then escalate to permanent |
Concrete Examples From Real Pipelines
| Error | Bucket | Reasoning |
|---|---|---|
| HTTP 503 Service Unavailable | Transient | By specification, 503 means the server is temporarily unable to handle the request |
| HTTP 401 Unauthorized | Permanent | The credential is wrong; retrying with the same credential will keep failing |
| HTTP 429 Too Many Requests | Transient (with mandatory backoff) | Rate limit; retry after the Retry-After interval, never sooner |
| JSON parse error on a single row | Permanent for that row | The row will not parse on the next attempt; route it to a quarantine path |
| Connection reset by peer | Transient | The TCP connection dropped; the next attempt opens a new connection |
| Foreign key violation on insert | Permanent | The referenced row does not exist; retrying does not change that |
- ▸The retry mechanism is the same for every transient error; only the wait time varies
- ▸The escape hatch is the same for every permanent error: stop and report
- ▸Code that mixes the two responses ends up retrying credential errors forever and giving up on network blips after one try
A First Code Sketch
The 200 case looks wrong because the classifier was given a status code outside of any error category. Real code calls the classifier only after determining a request failed. The example shows the unknown bucket falling back to transient on purpose.
Classify the failure first: transient errors (timeout, lock) get retried with exponential backoff; permanent errors (bad schema) go straight to a dead-letter queue. Retrying a permanent error just wastes time.
The Retry: Easy to Misuse
Apply a bounded retry that catches specific transient errors, sleeps between attempts, and gives up when the budget is exhausted.
A retry only produces the same answer as a single run when the work is idempotent (Lesson 5). Without that property, two attempts double the rows. The retry mechanics described here all assume the underlying write is safe to repeat.
What a Retry Does
Three Numbers Every Retry Defines
| Parameter | What It Controls | Typical Value |
|---|---|---|
| Maximum attempts | Total number of tries including the first | 3 to 5 for most pipeline operations |
| Wait between attempts | How long to sleep before retrying | 1 to 5 seconds for fixed delays; longer for backoff |
| Which errors retry | The list of exception classes considered transient | Specific exception types, never a bare except |
The Retry That Looks Right But Is Not
- Catches all exceptions, retries permanent errors forever
- No upper bound on attempts
- No sleep between attempts; hammers the downstream
- No logging; failures and recoveries are invisible
- Catches only specific transient exception classes
- Bounded attempts; raises after the budget is spent
- Sleep between attempts grows with each failure
- Logs every retry with attempt number and elapsed time
Why Specificity Matters
- ▸It catches a named transient error type, not Exception
- ▸It has a maximum attempt count and gives up when the budget is spent
- ▸It sleeps between attempts and the sleep grows over time
- ▸It logs each attempt so the recovery is visible
Naive Retries and Thundering Herd
Recognize the thundering herd failure mode and apply backoff plus jitter to prevent retry storms from amplifying an outage.
The Anatomy of a Thundering Herd
| Step | What Happens | Effect on Downstream |
|---|---|---|
| 1 | Downstream service hits a brief CPU spike; latency rises | Some requests time out; clients see transient errors |
| 2 | Hundreds of clients see errors at roughly the same second | Hundreds of clients enter their retry path simultaneously |
| 3 | Every client retries after exactly one second | Hundreds of new requests arrive at the downstream in the same second |
| 4 | Downstream is now under double the original load | Latency rises further; more requests time out |
| 5 | Failed retries trigger their next retry one second later | Load doubles again; downstream collapses entirely |
A Real Numbers Example
Suppose 500 worker processes each call a downstream API once per minute. A 200-millisecond latency spike at second 30 causes all 500 to time out simultaneously. Each retries one second later. The downstream now sees 500 retry requests at second 31 in addition to whatever organic traffic was scheduled. Original requests-per-second went from 8 to 508 in a single second.
The Fix Has Two Parts
| Mechanism | What It Does | Why It Helps |
|---|---|---|
| Backoff | Wait longer between each successive retry | Reduces total request volume during the recovery window |
| Jitter | Add a random offset to each retry's wait time | Spreads the retry wave across time so it does not arrive as a single spike |
- ▸Wait between attempts grows with each failure (exponential is the standard)
- ▸A random offset is added to the wait so retries do not synchronize
- ▸There is a hard cap on the wait so a single retry never sleeps for hours
Exponential Backoff in One Sentence
Compute exponential backoff wait times by hand and explain why the cap and jitter are required, not optional.
The Formula
The Numbers In a Real Example
| Attempt | Computed Wait | Capped Wait |
|---|---|---|
| 1 | 1 second | 1 second |
| 2 | 2 seconds | 2 seconds |
| 3 | 4 seconds | 4 seconds |
| 4 | 8 seconds | 8 seconds |
| 5 | 16 seconds | 16 seconds |
| 6 | 32 seconds | 32 seconds |
| 7 | 64 seconds | 60 seconds (capped) |
| 8 | 128 seconds | 60 seconds (capped) |
Adding Jitter to the Backoff
- Same wait between every attempt
- Total wait grows linearly with attempts
- Synchronizes retries across many clients
- Acceptable for very small attempt counts and very small fleets
- Wait doubles with each successive attempt
- Total wait grows quickly; budget is naturally bounded
- Pairs with jitter to spread retries across time
- The standard for any pipeline that runs with more than a single worker
Why the Cap Matters
- Pick a base of 1 second and a multiplier of 2 unless there is a specific reason to deviate
- Cap the wait so the seventh retry never sleeps longer than the operational tolerance
- Pair backoff with jitter so retries from many clients do not synchronize
- Use exponential backoff without a cap; the runaway is silent and expensive
- Use a base shorter than the round-trip time to the downstream; the first retry will hit before the failure has time to clear
- Treat the maximum-attempts number as an unbounded knob; if the workload needs more than ten attempts, the design needs more than retries
When NOT to Retry
Identify the failure categories that should not be retried and route them to quarantine, alerting, or credential rotation instead.
Three Categories That Should Not Be Retried
| Category | Example | Why Retrying Fails |
|---|---|---|
| Validation failures | A required field is missing from an event | The next attempt sees the same missing field; nothing changes |
| Authentication failures | A 401 from an API because the token expired or is wrong | Retrying with the same credential keeps failing; the credential has to be replaced |
| Poison pills | A specific row that crashes the parser every time it is processed | The row will keep crashing the parser; retrying loses progress on every other row |
Validation Failures
Authentication and Authorization Failures
Poison Pills
- ▸The data is the same on attempt two as it was on attempt one
- ▸The credentials are the same on attempt two as on attempt one
- ▸The schema is the same on attempt two as on attempt one
What To Do Instead
| Failure | Action Instead of Retry | Where the Action Lives |
|---|---|---|
| Validation failure on a single row | Quarantine the row, continue processing the rest | Quarantine table or dead letter queue |
| Authentication failure | Stop the pipeline, page on-call, rotate the credential | Alerting and runbook |
| Poison pill on a streaming consumer | Skip the message after N failed attempts, route it to a DLQ | Consumer DLQ configuration |
| Schema mismatch on a downstream insert | Stop the pipeline, alert the producer team | Schema validation stage and on-call |
- Permanent failures retry until the budget runs out
- Bad credentials trigger account lockouts after lockout policies kick in
- Poison pills block streaming pipelines while attempting to be reprocessed
- On-call gets paged after the budget burns instead of at the first sign of trouble
- Transient failures retry; permanent failures stop immediately
- Bad credentials surface within seconds and trigger rotation
- Poison pills move to a DLQ after a small bounded retry budget
- On-call gets paged on the genuine signal, not the retry aftermath
> A small data team runs a nightly job that pulls invoice events from a payment processor's API. The job has been failing once or twice a week for months. The on-call engineer manually restarts it each time. A new engineer is asked to make the failures self-healing without making the system worse. The current code retries every exception forever with no sleep.
Some failures heal themselves and some never will; the pipeline must tell the difference
- Category
- Pipeline Architecture
- Difficulty
- beginner
- Duration
- 25 minutes
- Challenges
- 0 hands-on challenges
Topics covered: Transient vs Permanent Failures, The Retry: Easy to Misuse, Naive Retries and Thundering Herd, Exponential Backoff in One Sentence, When NOT to Retry
Lesson Sections
- Transient vs Permanent Failures (concepts: paRetryHandling)
Every pipeline failure falls into one of two buckets. A transient failure is something that goes wrong because of a temporary condition: a network hiccup, a downstream service rebooting, a momentary rate limit. A permanent failure is something that will never succeed no matter how many times the pipeline tries: a bad credential, a row whose schema does not match, a malformed JSON document. The two buckets demand opposite responses. Treating a transient as permanent gives up too early; treating a
- The Retry: Easy to Misuse (concepts: paRetryHandling)
The retry is the most basic failure handling primitive. The mechanism is two lines of code: catch the exception, run the operation again. That simplicity is what makes the retry both the first reach and the most common source of subtle production bugs. A retry done correctly absorbs nearly all transient failures. A retry done carelessly amplifies an outage, runs forever, or quietly produces duplicate writes. The mechanics that distinguish the two are not complicated; they are unforgiving. A retr
- Naive Retries and Thundering Herd (concepts: paRetryHandling)
The thundering herd is the most cited failure mode in distributed systems and the most overlooked by engineers writing their first retry. The shape is straightforward. A downstream service slows down. Many clients fail at roughly the same moment. Each client retries on the same fixed schedule. The retries arrive at the downstream in a synchronized wave that is larger than the original load that caused the slowdown. The downstream goes from slow to dead. The retries then double in size again. A m
- Exponential Backoff in One Sentence (concepts: paRetryHandling)
Exponential backoff is the standard way to choose how long a retry should wait. The rule fits in one sentence: each successive attempt waits roughly twice as long as the previous one, capped at a maximum. The mechanism is everywhere because it solves two problems at once. It gives the downstream more time to recover with each failure. It bounds the total number of retries that can fit in a given time window. The cap prevents a runaway exponential from sleeping for days on the seventh retry. The
- When NOT to Retry (concepts: paRetryHandling)
Retry as a tool is so often correct that engineers begin to apply it reflexively. The reflex causes outages of its own. Some failures will never succeed on a second attempt, and retrying them wastes compute, fills up logs, and hides the underlying problem. Knowing the categories where retrying is wrong is as important as knowing how to retry properly. The pipeline that retries correctly on transient errors and refuses to retry on permanent ones is the pipeline that operates predictably. Three Ca