Exception Handling
Design your failure modes on purpose
Raise early, catch late, and never swallow errors — production pipelines are as much about deciding what must fail as about writing code that succeeds.
What you will learn
- Write try/except/else/finally without swallowing errors
- Distinguish recoverable errors (retry) from permanent ones (raise)
- Chain and log exceptions with context for on-call debugging
What is this?
Exception handling in data engineering is a policy decision: which failures are recoverable, which must abort, and what evidence each failure leaves behind. Done well, a crashed pipeline tells you exactly why it crashed; done poorly, it 'succeeds' with silent data loss.
Why do you need it?
- A bare `except:` that swallows errors turns a broken extraction into a believable empty table.
- Retries are only safe where idempotency guarantees a retry can't double-write.
- Logged context (what file, which partition, which row) is what makes a 3am page actionable.
Mental model
A smoke detector that stays silent
A swallowed exception
Silence implies safety. If your code catches every failure and keeps going, nobody knows the house is burning — until the data is gone.
How it works
- `try` guards the risky operation; `except` handles the specific exception types you can actually recover from.
- `else` runs only on success; `finally` always runs for cleanup (closing connections, releasing locks).
- `raise ... from err` chains the original exception, preserving the full traceback.
- Retry wrappers distinguish transient errors (ConnectionError, TimeoutError) from permanent ones (ValueError on bad data).
- An outer boundary logs the final failure with structured context and re-raises so orchestrators see it.
A resilient extract-with-retry boundary.
Examples
A retry-and-fail-loud extractor
import time, logginglog = logging.getLogger(__name__) def fetch_with_retry(make_request, attempts=3): for n in range(attempts): try: return make_request() except (ConnectionError, TimeoutError) as e: if n == attempts - 1: raise RuntimeError("extract failed permanently") from e time.sleep(2 ** n) raise RuntimeError("unreachable")Only transient errors retry; the last failure re-raises a clear root cause that orchestrators see.
In production
- A Kafka producer retries on retriable send errors but fails the DAG immediately on serialization errors.
- A file loader validates row count and raises if output is empty — empty-but-green is the most dangerous silent failure.
- A quality gate catches assertion failures, logs offending partitions, and aborts the publish step.
Best practices
- Catch the specific exceptions you can handle; never a bare `except:` in long-running pipelines.
- Fail loud at boundaries: extraction, validation, and publish each must raise on any anomaly.
- Attach context (partition, source, row_id) to the exception message or logged fields.
- Retry only idempotent work, with exponential backoff and a max attempt count.
Common mistakes
- `try: ... except: pass` hiding the error and shipping empty data.
- Retrying permanent failures forever, burning API quota and delaying the alert.
- Catching an exception and re-using the variable, dropping the traceback.
- Not wrapping a retry loop in a timeout, so a hung API call stalls the whole DAG.
Common issues
Pipeline green but table emptyA swallowed exception made the run complete with zero output
Add row-count validation that raises on empty output, and replace bare except with specific handlers.
Retry storm hits the upstream APIRetrying permanent errors (400s) without distinguishing status codes
Retry 5xx and timeouts only; treat 4xx as permanent and fail fast.
Debugging tips
- Read the full traceback chain (`from ... import err`) — the root cause is almost always the deepest frame.
- Add run_id/partition fields to every log line so a failed run can be replayed exactly.
- Temporarily set `PYTHONUNBUFFERED=1` to see logs live while debugging a stuck worker.
Interview checkpoint
Hands-on lab
- Implement a retry wrapper for transient failures
- Add a fail-fast empty-check after extraction
- Prove permanent failures are visible, not swallowed
Bulletproof an Extraction with Retries and Gates
25 min · intermediate
Quick quiz
Which pattern is the correct production default for an unexpected exception?
Retrying a non-idempotent operation is safe if you retry quickly.