ETL and Data Pipelines · Running Pipelines in Production
Publishing Data Atomically
Why the morning dashboard sometimes showed half a day of sales, and how staging tables, validation and atomic swaps make readers see either the old data or the new data, never a mix.
Scenario
Example company: the sales_summary table is rebuilt each morning by deleting it and inserting fresh rows in batches over 20 minutes. A finance analyst opens the dashboard at 07:10, during the rebuild. On another day, the rebuild crashes halfway.
What you see in production
- The 07:10 dashboard shows half of yesterday's sales; refreshing at 07:30 shows the full number.
- After the crash, the table stays half-empty until someone notices and reruns the job.
- Exports scheduled during the rebuild send partial data to a partner.
The naive code
DELETE FROM sales_summary;
INSERT INTO sales_summary SELECT ... ; -- many batches over 20 minutes, readers see each step
Reproduce it
Readers query the table at different points of a rebuild that fails at 60%. The naive load writes directly into the live table; the fix builds a staging table, validates it and swaps it in one step.
old_rows = 1000
new_rows = 1040
def in_place(progress, failed_at=None):
if failed_at is not None and progress > failed_at:
return int(new_rows * failed_at) # stuck at the failure point
return int(new_rows * progress) # readers see a growing partial table
def swap(progress, failed_at=None):
building_done = progress >= 1.0 and failed_at is None
return new_rows if building_done else old_rows # live table changes only on a successful swap
for label, failed_at in [("successful run", None), ("run fails at 60%", 0.6)]:
print(label)
for progress in [0.0, 0.3, 0.6, 1.0]:
print(f" at {progress:>4.0%}: in-place readers see {in_place(progress, failed_at):>4} rows | swap readers see {swap(progress, failed_at):>4}")
Output
successful run at 0%: in-place readers see 0 rows | swap readers see 1000 at 30%: in-place readers see 312 rows | swap readers see 1000 at 60%: in-place readers see 624 rows | swap readers see 1000 at 100%: in-place readers see 1040 rows | swap readers see 1040 run fails at 60% at 0%: in-place readers see 0 rows | swap readers see 1000 at 30%: in-place readers see 312 rows | swap readers see 1000 at 60%: in-place readers see 624 rows | swap readers see 1000 at 100%: in-place readers see 624 rows | swap readers see 1000
Root cause
Readers could see the table during the rebuild, and a failure left it in a half-built state. The load wasn't atomic from the reader's point of view.
The fix
Build, validate, then publish atomically:
- Write the new data to a staging table (or a new partition, or new files).
- Run data tests on the staging table.
- Publish in one atomic step: a transaction that swaps or renames tables (
ALTER TABLE ... RENAME,CREATE OR REPLACE TABLE ... AS, Snowflake'sALTER TABLE ... SWAP WITH), a partition exchange, or a table-format commit (Iceberg and Delta commit a new snapshot atomically). - If anything fails, the live table is untouched; alert and retry.
Many tools do this for you: dbt builds table models into a temporary relation and swaps them in, and lakehouse table formats make every write an atomic snapshot.
For views over several tables that must change together, publish a new version of all of them and switch a pointer (a view or a "current version" setting) in one step.
Trade-offs
- Staging needs extra storage during the build.
- Swapping whole tables is simplest for small or medium tables; large tables use partition-level overwrite instead.
- Dependent views or grants may need recreating after a rename on some databases; test the swap.
Detect it in production
- Watch for dashboards queried during load windows showing unusual dips.
- Alert when a run fails between "build" and "publish", and verify the live table still matches the last good version.
Key points
- Readers should see old data or new data, never a mix.
- Build in staging, validate, then swap atomically.
- Failed runs must leave the live table untouched.
Practice questions
How do you publish a rebuilt table without readers seeing partial data?
Answer
Build it in a staging table or new partition, run validation tests, then publish in one atomic step: a transactional rename or swap, a partition exchange, or a table-format snapshot commit.
What happens to the live table if an atomic publish fails?
Answer
Nothing: the swap never happened, so readers keep seeing the last good version. The failed staging data can be inspected, and the run retried.
How do lakehouse table formats provide atomic writes?
Answer
Each write produces new data files plus a new table snapshot, and the commit atomically switches the table's metadata pointer to it. Readers see either the previous snapshot or the new one.
Open in the app to mark lessons complete, take notes and track your progress.