What is the difference between a full-refresh and an incremental extract from a source table?
answer
- one reads everything, one reads changes
- cost tracks size versus change volume
- stateless and self-healing versus cheap
- the cheap one inherits four problems
- deletes vanish only in the snapshot
basics
~20 sA full-refresh extract reads every source row each run and replaces the target. An incremental extract reads only rows changed since a stored watermark and merges them: far cheaper, but it must handle boundary rows, deletes and reruns itself.
solid answer
~50 sA **full refresh** re-reads the entire source table on every run and replaces the target with that snapshot. It is stateless, self-correcting and idempotent by construction: any row the previous run got wrong is fixed, and rows deleted at the source simply stop appearing. Its cost grows with table size rather than change volume, so eventually the scan hurts the source, blows the schedule window and burns target compute for a handful of changed rows. An **incremental extract** stores a watermark — typically the highest `updated_at` or id seen — and pulls only rows above it. Cost then tracks change volume, but you inherit four new problems: a change column the source must maintain honestly, a boundary condition that can drop rows, deletes that never show up, and reruns that can duplicate. Many teams run incremental nightly with a periodic full reconcile to get both.
code
sql · 9 lines-- full refresh: stateless, self-correcting, cost tracks table size
SELECT order_id, customer_id, status, amount, updated_at
FROM orders;
-- incremental: cost tracks change volume, needs a bookmark and a keyed write
SELECT order_id, customer_id, status, amount, updated_at
FROM orders
WHERE updated_at >= :watermark
AND updated_at < :run_upper_bound;go deeper
Be ready to state the difference in one sentence and write both queries: one that selects the whole table, one that filters on a stored watermark value.
Explain the mechanics of the tradeoff — cost proportional to size versus change volume, statelessness versus a stored bookmark — and name the four things incremental makes you own: change column, boundary, deletes, idempotency.
Expect to justify a conversion on a real table: what you measured before moving, how you protected the source, and what reconcile you kept so the incremental path cannot drift silently.
Own the policy across the estate: which classes of table are allowed to be incremental at all, what reconcile cadence is mandatory, and how you stop each team from reinventing a slightly wrong watermark.
## The two shapes of a batch extract Every scheduled pull from a source system is one of two things. A **full-refresh extract** reads the whole source table on each run — `SELECT * FROM orders` — and replaces whatever the target held. An **incremental extract** reads only the rows the source has touched since the previous run — `SELECT * FROM orders WHERE updated_at >= :watermark` — and merges that slice into the target. The words are ordinary, so be precise about what "replaces" and "merges" mean. A full refresh normally lands into a staging table and then swaps or overwrites the target atomically, so readers never see a half-loaded table. An incremental load applies its slice with a keyed write — an upsert on the primary key, or a delete-and-reinsert of the affected partition — because a blind append would duplicate rows the moment anything reran. ## What full refresh buys **Self-correction.** The target is a pure function of the source at read time. If last week's run had a bug, dropped rows at a boundary, or missed an update that the application made without bumping its timestamp column, tonight's snapshot silently repairs all of it. This is worth more than teams expect. **No state.** There is no bookmark to store, no boundary to reason about, no question of when to advance anything. The job has no memory, so it cannot have a corrupt memory. **Deletes come free.** A row deleted in the source is simply absent from the next snapshot, so it disappears from the target with no tombstone mechanism. **Rerun safety.** Running it twice produces the same table, so retries after a timeout are harmless. ## What full refresh costs **Read amplification.** Cost is proportional to table size, not to change volume. Re-reading 400 million rows to capture 20 thousand changes is the normal steady state of a mature table. **Source impact.** A full scan against an OLTP primary competes with production traffic and holds a long read snapshot, which on MVCC engines keeps old row versions alive. Teams commonly move full refreshes onto a read replica for exactly this reason. **Target churn.** Rewriting a large table every night consumes warehouse compute, rewrites every partition, and invalidates downstream caches and incremental models even when nothing changed. **A latency floor that keeps rising.** The job takes longer every month until it no longer fits the window it is scheduled in. That day is when most pipelines convert to incremental — under time pressure, which is why the conversion is so often buggy. ## What incremental buys and what it charges Incremental makes cost proportional to change. In exchange you now own four problems, and interviewers ask about all four: 1. **A trustworthy change column.** The extract is only as correct as the column it filters on. A bulk `UPDATE` or a backfill script that does not bump `updated_at` is invisible forever, because a watermark filter can never look backwards. 2. **A boundary.** "Since last time" is ambiguous at the edge: rows written inside a transaction that commits after your read carry a timestamp inside the window you already closed, and ties at the exact boundary value are dropped or duplicated depending on whether you use `>` or `>=`. 3. **Deletes.** A deleted row cannot satisfy any filter, so hard deletes are structurally invisible and the target keeps stale rows forever. 4. **Idempotency.** The target is now assembled from many partial writes, so the write must be keyed and the run must take explicit window bounds rather than "whatever is new right now." ## Choosing between them The inputs to the decision are table size and growth, the change ratio (changed rows per run divided by total rows), the source's tolerance for scans, the freshness requirement, whether the source maintains a change column you actually trust, and whether deletes matter to the consumers. A workable rule of thumb: stay on full refresh while it comfortably fits the schedule and the source tolerates the read — the operational simplicity and self-healing are real money. Move to incremental when scan time, source impact or target cost stops fitting, and pay the four taxes above deliberately rather than discovering them in production. ## The hybrid that most mature pipelines run You rarely have to choose forever. Common combinations: incremental nightly plus a full reconcile weekly or monthly, so drift and missed rows have a bounded lifetime; full refresh for small reference and dimension tables where the scan is trivial, incremental only for the few large ones; and a routine replay of the last N days incrementally, which self-heals recent damage cheaply because the keyed write makes re-reading harmless.
- Why do many teams keep a periodic full reconcile after moving a table to incremental loading?Because incremental loads drift. Missed boundary rows, updates made without bumping the change column, and hard deletes all accumulate silently. A weekly or monthly full read bounds the lifetime of any such error to one reconcile interval, which is far cheaper than proving each night's slice was complete.
- When is a full refresh still the right answer for a table in 2026?When the table is small or slow-growing, when the scan does not disturb the source, and when correctness matters more than compute — reference data, configuration, small dimensions, and any source with no trustworthy change column. If there is no reliable updated_at or version column, incremental is not cheaper, it is just wrong faster.
- Why is a blind INSERT of the incremental slice not an acceptable write pattern?Because nothing makes it idempotent. Any retry, overlapping window, or lookback re-reads rows already landed and appends them again, so counts and sums inflate. The slice must be applied with an upsert keyed on the row's identity, or by atomically replacing the whole partition it belongs to.
A full refresh is rephotographing the whole room every night; an incremental extract is noting only what moved. The second is far cheaper until someone quietly removes a chair, which no note ever records.
saying these in an interview costs you the question
- Claiming incremental loads are strictly better than full refresh
- Assuming the source always maintains updated_at correctly
- Appending the incremental slice without any key or upsert
- Believing hard deletes show up in a watermarked extract
- Treating full refresh cost as proportional to changed rows