Idempotent Reruns: Beginner
What you will be able to do
The Retry That Doubled the Rows
Recognize the duplicate-row failure mode that occurs when an append-only pipeline is retried after a partial failure.
The Anatomy of the Failure
| Stage | What Happened | State After |
|---|---|---|
| First attempt starts | Job extracts 250,000 rows from Postgres | Local file holds the day's orders |
| First attempt halfway | Job writes 180,000 rows to Snowflake then crashes on row 180,001 | Snowflake holds 180,000 partial rows; the run is marked failed |
| Retry triggered | On-call hits retry; job reads 250,000 rows from Postgres again | Local file now holds the same 250,000 orders |
| Retry completes | Job appends all 250,000 rows to Snowflake; run is marked success | Snowflake holds 430,000 rows for a day that had only 250,000 orders |
- ▸The duplicates pass all schema checks because the rows are individually valid
- ▸Aggregates downstream silently double, but the dashboards keep rendering
- ▸Detection is delayed by hours or days because nothing fails loudly
Why It Looks Right
- Each run adds rows on top of whatever is already there
- A retry after a partial failure produces duplicates
- Backfill of an old date duplicates that day's data
- The bug is invisible until something downstream notices
- Each run produces the same end state regardless of how many times it is invoked
- Retries are safe; the destination ends up correct
- Backfills replay history without corrupting it
- Partial failures are recoverable by rerunning the job
Most data quality incidents are not caused by bad data arriving from upstream. They are caused by correct data being processed incorrectly downstream. Idempotency is the most common failure mode in that category.
Idempotency in One Sentence
Define idempotency in one sentence and apply the two-run test to any piece of pipeline code.
The Definition, with Examples
| Operation | Idempotent? | Why |
|---|---|---|
| Setting a light switch to off | Yes | Off after one flip; off after two flips. State is the same. |
| Pressing a doorbell | No | One press makes the bell ring once; two presses make it ring twice. |
| Locking a car with the remote | Yes | Locked after one click; locked after two. The end state is locked. |
| Adding sugar to coffee | No | One spoonful is sweet; two spoonfuls are sweeter. State accumulates. |
| Setting a thermostat to 68 degrees | Yes | Target is 68 after one set; 68 after two. Outcome is identical. |
Why the Word Matters
The Pipeline Version of the Definition
- ▸Running the pipeline once and running it three times produce the same final state
- ▸A partial failure followed by a retry leaves no trace of the failed attempt
- ▸A backfill of an already-processed date does not duplicate or corrupt that date's data
The Test
Idempotency is a property of the write, not a property added on top. The choice between INSERT, MERGE, and partition overwrite determines the property. Wrapping a non-idempotent write in retries does not make it idempotent.
Replace, Do Not Append
Apply partition overwrite as the default idempotent write pattern for daily batch pipelines.
The Pattern: Partition Overwrite
| Operation | First Run Result | Second Run Result | Idempotent? |
|---|---|---|---|
| INSERT INTO orders SELECT ... WHERE order_date = '2026-04-25' | Day's rows added | Day's rows added again on top | No |
| INSERT OVERWRITE orders PARTITION (order_date='2026-04-25') SELECT ... | Partition contains day's rows | Partition replaced with same rows; identical state | Yes |
| DELETE WHERE order_date='2026-04-25'; INSERT SELECT ... WHERE order_date='2026-04-25' | Day's rows present | Same day's rows; old ones deleted, new ones inserted | Yes (when in a transaction) |
The SQL Form
Why the Date Partition Is the Right Unit
- ▸Partition the destination table by the same time grain as the pipeline's run cadence
- ▸Each run computes the rows for its own partition only
- ▸The write is OVERWRITE or CREATE OR REPLACE, never INSERT
- ▸Other partitions are untouched, so concurrent runs on different dates do not interfere
The Failure Case Walked Through
- INSERT INTO orders SELECT ... WHERE order_date = today
- Each retry adds rows on top
- Crashes mid-write leave partial garbage in the table
- Backfilling April 1 today appends April 1 again
- INSERT OVERWRITE orders PARTITION(order_date) SELECT ...
- Each retry replaces the partition; final state is identical
- Crashes mid-write are recovered by retry; the next run fully replaces
- Backfilling April 1 today replaces April 1; no duplication
- Partition every batch destination table by the pipeline's run cadence (day, hour, fifteen minutes)
- Use INSERT OVERWRITE or CREATE OR REPLACE rather than INSERT for the final write
- Make every intermediate step idempotent on its own; the chain is only as safe as its weakest link
- Use INSERT INTO followed by 'maybe DELETE the old rows' as the strategy; the maybe is the bug
- Partition by a grain coarser than the run cadence (hourly job writing to daily partition)
- Assume retries are safe because they have not caused trouble yet; trouble arrives on partial failure
Idempotency = replace, do not append. Write by MERGE or partition-overwrite keyed on a business key, so re-running the job (a retry or a backfill) produces the same rows instead of doubling them.
Why Retries Need Idempotency
Explain why automatic retries are unsafe without idempotency and trace the duplicate accumulation that occurs when the property is missing.
What Retries Are For
| Failure Mode | Typical Frequency | What a Retry Does |
|---|---|---|
| Network timeout to upstream API | Several times per week per pipeline | Second attempt usually succeeds; the network blip has passed |
| Warehouse query timeout under load | A few times per month per heavy job | Second attempt runs when load has cleared |
| Spot instance preempted mid-execution | Daily on a large fleet | Job restarts on a different instance and completes |
| Source database temporarily unreachable | Occasional, depending on infra reliability | Second attempt connects after the source recovers |
What a Retry Looks Like Under the Hood
Retries on a Non-Idempotent Pipeline
- ▸Attempt one writes 100 rows then fails. The 100 rows persist.
- ▸Attempt two writes the same 100 rows plus the next 200, then fails. 300 partial rows persist.
- ▸Attempt three writes all 300 rows. Total: 100 + 300 + 300 = 700 rows for what should be 300.
- ▸The orchestrator marks the run successful because the final attempt did not raise.
The Cost of Disabling Retries
- Every transient failure becomes a manual page
- On-call rotation degrades; retention drops
- The bug is hidden, not fixed; the next non-transient failure still corrupts data
- Backfills still cause duplicates because they are retries by another name
- Transient failures are absorbed automatically without paging
- On-call only wakes for genuine problems with named owners
- The bug is structurally impossible; partial failures self-heal on retry
- Backfills are safe because the pipeline replaces rather than appends
The Two-Way Contract
Idempotency is a precondition, not a feature. Every other operational property of a pipeline (safe retries, backfills, reprocessing, recovery) builds on top of it. A non-idempotent pipeline cannot be operated; it can only be hoped at.
Side by Side: Idempotent vs Not
Compare a non-idempotent pipeline against its idempotent fix line by line and explain which lines carry the property.
Pipeline A: The Bug
Pipeline B: The Fix
| Property | Pipeline A | Pipeline B |
|---|---|---|
| Aggregation logic | Correct | Correct (identical) |
| Write strategy | INSERT only | DELETE then INSERT inside a transaction |
| Result of running once | One row per customer for the date | One row per customer for the date |
| Result of running twice | Two rows per customer for the date | One row per customer for the date |
| Result of partial failure plus retry | Some customers duplicated, others not | Identical to a clean run |
| Safe to use with orchestrator retries | No | Yes |
Why the Transaction Matters
- ▸Both statements run inside the same transaction
- ▸The DELETE filters by the same partition key the INSERT writes to
- ▸The transaction commits only when both statements succeed
Even Simpler: CREATE OR REPLACE
What the Diff Costs
| Cost Dimension | Pipeline A (Append) | Pipeline B (Replace) |
|---|---|---|
| Lines of code | 13 | 16 |
| Compute per run | One INSERT per row | One DELETE plus one batched INSERT |
| Operational reliability | Brittle under retry | Safe under retry and backfill |
| Cost of a partial failure incident | Hours of investigation, possible data correction | Hit retry; the next run cleans up |
- Read every batch pipeline as either Pipeline A or Pipeline B; convert all the As to Bs
- Wrap DELETE-then-INSERT in a transaction; never run the two statements separately
- Default to CREATE OR REPLACE or INSERT OVERWRITE when the destination supports it
- Add INSERT-only writes to a destination that already accepts other writes; coexistence multiplies bugs
- Skip the transaction wrapper because 'the DELETE and INSERT are right next to each other'
- Treat the three extra lines as boilerplate to remove; they are the property
> A junior engineer at a Series B startup writes the company's first nightly pipeline. It reads new orders from Postgres and inserts them into a Snowflake summary table. The pipeline runs successfully every night for two months. On the sixty-third night, the warehouse query times out at row 180,000 of 250,000. The orchestrator retries automatically. The next morning the finance team reports revenue is up 41 percent. The CTO asks the data team to prevent this failure mode from ever recurring.
Running the same pipeline twice should produce the same result, not double the rows
- Category
- Pipeline Architecture
- Difficulty
- beginner
- Duration
- 25 minutes
- Challenges
- 0 hands-on challenges
Topics covered: The Retry That Doubled the Rows, Idempotency in One Sentence, Replace, Do Not Append, Why Retries Need Idempotency, Side by Side: Idempotent vs Not
Lesson Sections
- The Retry That Doubled the Rows (concepts: paIdempotency)
Pipelines fail. Networks blink, instances die, upstream APIs return 500s, the warehouse runs out of memory, a credential expires, a Spark executor gets evicted from a spot pool, an S3 bucket policy changes overnight, a DNS record propagates slowly. Failure is not the exception in production data pipelines; it is the background hum. The right response to a failed run is almost always to run it again. The wrong response is to run it again on a pipeline that does not handle being run twice. The wro
- Idempotency in One Sentence (concepts: paIdempotency)
Idempotency is one of those words that sounds harder than the idea it names. The mathematical definition is short: an operation is idempotent if applying it twice gives the same result as applying it once. The data engineering definition is even shorter, because the operation in question is always 'run the pipeline.' Running an idempotent pipeline twice produces the same end state as running it once. That is the entire concept. The word entered software engineering through HTTP, where GET, PUT,
- Replace, Do Not Append (concepts: paIdempotency)
The simplest way to make a pipeline idempotent is to make it replace rather than append. Instead of writing 'add today's orders to the orders table,' the pipeline writes 'set the orders for today to exactly this set of rows.' Set is idempotent; add is not. The change is small and the implications are large, because nearly every batch pipeline can be expressed as a partition replace if the data is partitioned by run date. The mental shift is from thinking about the pipeline as something that cont
- Why Retries Need Idempotency (concepts: paIdempotency)
Retries are how pipelines survive the noisy reality of distributed systems. A network blip, a brief warehouse contention spike, an upstream rate limit triggered by a sudden traffic surge, a transient AZ outage, a Spark task failing because its executor lost a heartbeat. None of these are bugs; they are weather. A retry the next minute almost always succeeds. Orchestrators ship with retry support built in because retries are that fundamental to operations; turning them off would mean paging a hum
- Side by Side: Idempotent vs Not (concepts: paIdempotency)
The clearest way to internalize the property is to read two short pipelines side by side. One is non-idempotent. The other does the same job idempotently. The diff is small. The behavioral difference under retry is enormous. The exercise below walks through both, line by line, and names what changes. Reading two side-by-side examples is faster than reading a hundred lines of explanation because the diff is the explanation. The asymmetry is the lesson: idempotency is a small change in code that p