sql.sbEnglish resources

Data warehouse · pipelines

How to make an ETL job idempotent

An ETL job is idempotent when running it again with the same input for the same business date leaves the same business state. Retries and backfills stop being dangerous only when the write is idempotent, not when the scheduler says “rerun”.

Quick answer

Make the target partition addressable — for example by (branch_id, business_date) — and write it idempotently: overwrite the partition, or merge/upsert on that key. A plainINSERT APPEND is not idempotent: the same run twice produces two rows for one business date and every downstream sum is inflated.

Run the same input twice, change only the write policy

The lab loads one row for branch HZ-01 on 2026-02-01. Run the job once, then rerun it with the same input and switch between append, overwrite and merge-upsert. Watch the partition row count and SUM(balance) after each execution.

Same input · run twice

The target partition is empty. Run the job once, then rerun it with the same input and compare write policies.

Rows for the business date0
SUM(balance)$0
Expected balance$1,000,000
Duplicate rows0
target table · branch_daily_balancepartition: HZ-01 · 2026-02-01

No rows yet. Run the job to load the partition.

run log0 executions

No executions recorded yet.

What “idempotent” means precisely

Idempotency is a property of the write, not of the scheduler. Three conditions define it:

  • Same logical input. The same source rows for the same business date, even if the execution timestamp or attempt number differs.
  • Same target. The same partition, key or row set is addressed each time.
  • Same business state. After the second run, the target holds one correct version of the result — not two.
A successful run is not proof of idempotency

Both executions can return success while the second one appends a duplicate row. Check the target state, not the run status.

Why reruns create duplicates

  • Append writes. The job inserts a new row every time and never removes the previous result.
  • Retry after a partial failure. The first attempt wrote some rows before failing; the retry writes them again.
  • Overlapping backfills. Two runs process the same business date range at the same time.
  • Non-deterministic input. The query reads NOW(), a moving window or an unpinned source snapshot, so the “same” run is not the same input.

The failure is observable: one business date has more than one row in the target partition, and any measure summed from that partition is too high.

Write policies compared

Idempotent and non-idempotent write strategies
PolicySecond run with the same inputTrade-off
INSERT APPENDTwo rows for the same business date; sums inflatedSimple and fast, but not idempotent
OVERWRITE PARTITIONOne row for the business dateNeeds an atomic replace; briefly exposes an empty partition if it is not atomic
MERGE / UPSERTOne row for the business date, updated if the value changedRequires a reliable unique key and correct update semantics

Overwrite and merge both make the partition addressable by business date. The choice depends on the table format, the cost of a full partition rewrite and whether deletes must be propagated. In all cases the key that defines “the same row” must be explicit.

Retry, rerun and backfill are different scopes

Retry

The same run, another attempt

  • Business date and target do not change.
  • Should be safe even if the first attempt partially wrote.
  • Depends on the write policy, not on the retry count.
Rerun

Re-execute one existing business date

  • Used for corrections and recovery.
  • Must replace the previous result, not add to it.
  • Keep the run history so the correction is auditable.
Backfill

Re-execute a range of business dates

  • Idempotency must hold per partition, not just for the range.
  • Overlapping ranges must not write the same date twice.
  • Consider concurrency limits and source read load.
Anti-pattern

“Just rerun it”

  • Reruns an append job and doubles the partition.
  • Treats a duplicate as a scheduler problem.
  • Hides the failure until an aggregate is visibly wrong.

What SQL idempotency does not cover

Making the table write idempotent does not automatically make the whole pipeline idempotent. Side effects still run once per execution unless they are made idempotent too:

  • Files written to object storage, and their partitions or manifests.
  • Notifications, emails and webhook calls.
  • Rows inserted into audit or event tables.
  • External systems updated through an API.

For those, use a deterministic output path or an idempotency key. The task graph view of the pipeline is a separate concern — seedata lineage vs task dependency.

Edge cases and trade-offs

  • Failure in the middle of an overwrite. If delete and insert are not in one transaction, a failed run can leave an empty or partial partition. Prefer an atomic replace or write to a staging table first.
  • Corrected input. With overwrite or merge, a rerun with corrected input replaces the old value. With append, both versions remain and the sum is wrong.
  • Late-arriving data. The business date of the row does not change; the target partition must be recomputed for that date.
  • Unique constraints. A database unique key can reject duplicates, but it turns a silent duplication into a hard failure — useful, but not a substitute for an idempotent write.
  • Cost. A full partition overwrite can be more expensive than a merge, while a merge needs a reliable key. Measure both against the real table format.