Reconcile Before You Recreate: Making a Bulk Job Safe to Run Twice

Date:

Every bulk job gets re-run — after a crash, a false failure, or a nervous operator. Whether that re-run heals the data or doubles it comes down to one question you answer before it starts.

The short answer: A bulk create is only safe to re-run if it reconciles first — checks what already exists and skips it — because re-runs are the norm, not the exception. The design has three parts: a stable deduplication key that identifies the same entity across runs; a local ledger of what you have created, so you are not wholly dependent on the remote system’s readability; and idempotent creation, so attempting a record that already exists is a skip, not a copy. Get these right and a re-run recreates only the genuinely missing items. Get them wrong and every retry is a fresh chance to duplicate your data.

Reconciliation is the step where a job compares what it is about to create against what already exists, so it acts only on the true difference.

Why re-runs are the normal case, not the edge case

A bulk job of any size will be interrupted. A connection drops, a machine sleeps, a batch is stopped by hand, half the items fail a transient check. Every one of those ends with the job partly done and the obvious next move being “run it again.”

So the interesting question is never “does the job work?” It is “what does the second run do to the items the first run already completed?” A job that has no answer to that is not finished — it is a duplicate generator waiting for its first retry.

A bulk job with no reconciliation step is not idempotent; it is a duplicate generator on a delay.

The deduplication key: identity, not position

Reconciliation needs to answer “have I already created this?” — which requires a way to say two records are the same real entity. That is the deduplication key, and choosing it well is most of the work.

A good key is stable (it does not change between runs), unique to the entity, and present in both your source data and the system of record so you can match across them. A natural identifier the entity already owns is ideal. A row’s position in a file is the classic bad key: insert one line and every position shifts, so the same entity now looks new.

The subtle failure is a key that is usually unique. If your key is an attribute that two distinct entities can share, reconciliation will treat them as one and skip a record that should have been created — a false skip, quieter and longer-lived than a duplicate. Choose a key that is unique by construction, not by luck.

The local ledger: do not depend on a system you cannot always read

Reconciling against the system of record assumes you can read the system of record. Sometimes you cannot — it is slow, rate-limited, briefly inconsistent, or its list view is paginated in a way that hides most of it.

A local ledger — your own append-only record of what you have created, keyed the same way — is the hedge. It lets a re-run skip everything you know you made without a fragile full scan of the remote system, and it survives even a remote outage. It is not a replacement for the authoritative read; it is a fast, reliable first pass that shrinks how much you must verify remotely. When the two disagree, the system of record wins — but the ledger is what makes the common case cheap and a genuine loss something you can actually measure against a known baseline.

The system of record is the authority; the local ledger is what keeps you from having to trust it blindly on every pass.

Ghost records: present remotely, missing locally

The gap between the two stores has a name worth knowing. A ghost is a record that exists in the system of record but not in your ledger — created, but not written down as created.

They appear for ordinary reasons: a create that succeeded while your process died before it could log the success, a record someone else made, a run whose result you misread. The danger is that a ledger-only reconciliation will not see them, call them missing, and recreate them — a duplicate, from the very step meant to prevent duplicates.

The defense is to let the system of record settle ties. Before creating, check both: if the ledger has it, skip; if the ledger does not but a keyed read of the store finds it, record the ghost in the ledger and skip. Only create when both agree it is absent. A periodic full reconciliation — crawl the store, adopt every keyed record the ledger is missing — keeps ghosts from accumulating.

The fix, assembled

Put the pieces in order and a re-run becomes boring, which is the goal:

  1. Load the ledger. Everything you already know you created, by key.
  2. For each source item, resolve its key. Stable, unique, present on both sides.
  3. Skip what the ledger knows. The cheap, reliable majority.
  4. For the rest, read the system of record by key. Present → it is a ghost; record it and skip. Absent → create.
  5. Create idempotently, then read back. Confirm the create landed by reading it from the store — the verdict comes from the record, not the interface message — and append to the ledger.

The result recreates exactly the true difference — no duplicates from ghosts, no false skips from a weak key, no dependence on a remote scan that might be lying.

What reconciliation cannot do

Reconciliation keeps a re-run from duplicating data; it does not keep the first run from losing it, and it does not repair records that exist but are wrong. It answers “does this entity exist?” — not “is it correct?” An update path still has to compare fields, because a present record can hold stale values. And a key that is subtly non-unique will defeat the whole scheme silently, so the key choice deserves more scrutiny than the plumbing around it. Reconciliation makes retries safe; it does not make the underlying data true.

Re-creating what already exists has a measurement cousin: a scan that re-counts already-resolved items and inflates the backlog — Your dedup scan is over-counting.

The short version

Every bulk job is re-run eventually, so the design that matters is what the second run does to already-completed items. Make it reconcile first: pick a stable, genuinely-unique deduplication key present on both sides; keep a local ledger of what you created as a fast, outage-proof first pass; and create idempotently so an attempted duplicate is a skip. Watch for ghosts — records present in the system of record but missing from the ledger — and let a keyed read of the store settle ties before you create, so the anti-duplicate step does not itself make duplicates. Reconciliation makes retries safe; it does not make the data correct, so update paths must still compare fields.

Frequently Asked Questions

What does it mean to reconcile before recreating?

It means that before a bulk job creates anything, it compares its intended records against what already exists — in a local ledger and, for ties, in the system of record — and acts only on the genuine difference. Reconciliation turns “create all of these” into “create the ones that are actually missing,” which is what makes a re-run safe.

How do I choose a good deduplication key?

Pick something stable across runs, unique to the entity by construction, and present in both your source data and the system of record. A natural identifier the entity already owns is best. Avoid positional keys (row numbers shift) and avoid attributes that two distinct entities can share, which cause silent false skips that are harder to notice than duplicates.

What is a ghost record?

A ghost is a record that exists in the system of record but not in your local ledger — created, but never written down as created. It happens when a process dies after a successful write, when someone else creates the record, or when a result is misread. A ledger-only check will call a ghost missing and recreate it, so a keyed read of the store must settle the tie.

Why keep a local ledger if the system of record is authoritative?

Because you cannot always read the authoritative store cheaply or reliably — it may be slow, rate-limited, briefly inconsistent, or paginated so it hides most of its contents. A local ledger is a fast, outage-proof first pass that skips what you know you created, shrinking how much you must verify remotely. The system of record still wins ties; the ledger just makes the common case cheap.

How do I make creation idempotent?

Design it so that creating a record which already exists is a no-op or a skip rather than a second copy — often by having the store reject duplicates on the deduplication key, or by checking existence immediately before the write. Combined with reconciliation, idempotent creation means a mistaken re-run costs nothing, because the operation simply declines to duplicate.

Does reconciliation fix incorrect data?

No. Reconciliation answers whether an entity exists, not whether it is correct. A record can be present and hold the wrong values, so an update path must compare fields rather than just existence. Reconciliation makes retries safe against duplication; keeping the data accurate is a separate discipline built on read-back and field comparison.

Share post:

Subscribe

Popular

More like this
Related

How to Merge Spreadsheets by URL (and Why Our Tool Reported 5 Misses That Weren’t)

Matching two spreadsheets on a URL column is the same job as matching a stock list to a price list by product code. It goes wrong in the same two places: how you clean the code, and how you count what didn't match.

Many-to-One: Why a Mapping Backlog Outgrows Its Target Taxonomy

You're mapping a messy source vocabulary onto a clean target taxonomy. The target has about 5,600 slots, so the job must be roughly 5,600 decisions — right? The backlog is already past 8,900, and still climbing.

The Fix That Didn’t Take: When a Content-Hash Gate Skips Your Code Change

You shipped a fix to how one field is computed, re-ran the pipeline across everything, and it reported a clean run. Then the consistency check still flags those records as wrong. The fix is in the code, the rebuild "ran" — and the records never changed. Your own optimisation skipped them.

Your Dedup Scan Is Over-Counting — Because It Forgot What You Already Fixed

The report said 80 duplicate groups still to resolve. You'd been resolving them for days. You run it again after a big batch and it *still* says 80-ish. The backlog isn't stubborn — the scan is counting work you already finished.