Skip to content

fix(data-warehouse): pin duckgres backfill promotions to their run generation - #69101

Merged
EDsCODE merged 6 commits into
masterfrom
fix/duckgres-backfill-generation-pinning
Jul 9, 2026
Merged

fix(data-warehouse): pin duckgres backfill promotions to their run generation#69101
EDsCODE merged 6 commits into
masterfrom
fix/duckgres-backfill-generation-pinning

Conversation

@EDsCODE

@EDsCODE EDsCODE commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Problem

Two related holes in the duckgres backfill state machine, both found in a deep QA session on the consumer:

  1. PRIMED promotions are not pinned to the run generation. mark_primed and the reconciler's chunks-complete promotion CAS on state=BACKFILLING alone. A replan (replan_backfill, the documented prod remediation) can retire run R1 and plan R2 while R1's final chunk is still mid-swap - its duckgres transaction can't be cancelled. R1's late promotion then flips R2's row to PRIMED: live batches unblock and apply onto the pre-R2 table, R2's own swap later replaces that table, and the live rows are gone permanently (their apply markers prevent re-apply). Same exposure for a reconciler pod holding a stale state snapshot across another pod's replan.
  2. Planning during an in-flight replace-head run silently drops the run's tail. _plan_one pins a Delta snapshot mid-run: the run's head batches get pre-applied as snapshot-covered, the replace-head gate bypass admits its post-snapshot tail into the doomed pre-swap table, and the swap discards those rows while their markers block re-apply. The schema ends PRIMED with data missing and no failure signal anywhere.

Changes

  • mark_primed takes the promoting run's run_uuid and both promotion CAS filters add backfill_run_uuid, so a stale generation's evidence can never promote a newer plan
  • _plan_one defers (returns, retried each planner tick) while a replace-head live run is in flight for the schema: batch 0 replace-shaped and delta-succeeded, with neither a duckgres-succeeded final marker nor a duckgres-failed batch for the run

How did you test this code?

I (Claude) added three tests to test_backfill.py, each catching a regression nothing existing covered:

  • mark_primed with a stale run uuid must not flip a row that has moved to a new generation (and must flip with the matching one)
  • The reconciler promotion must not fire when the row's backfill_run_uuid changed after the pass's state snapshot was read
  • _plan_one must defer and stay unplanned while _has_inflight_replace_run is true

Ran the duckgres backfill + processor suites: 27 passed (one existing processor test updated for the new mark_primed signature). The in-flight-run SQL mirrors the proven shape of _reconcile_needs_resync's completion-proof query against the same views.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Deep QA session on the duckgres consumer (Claude Code): two independent review passes (a state-machine adversary and a lease/claim concurrency reviewer) converged on the replan-vs-slow-swap interleaving and the plan-during-replace data-loss path. The fix follows the module's own documented CAS discipline ("every transition is a compare-and-swap") - the promotions were the only transitions not pinned to the generation they had evidence for.

…neration

Both PRIMED promotions (the processor's post-swap mark_primed and the
reconciler's chunks-complete CAS) filtered on state alone. A replan can
retire run R1 and plan R2 while R1's final chunk is mid-swap — the duckgres
transaction cannot be cancelled — so R1's late promotion flips R2's row to
PRIMED, live batches unblock and apply onto the table R2's own swap later
replaces, and those rows are silently dropped forever (their apply markers
prevent any re-apply). Pin both CAS filters to backfill_run_uuid.

Also guard the planner against in-flight replace-head live runs: planning
mid-run pins a snapshot that covers the run's head (pre-applied), while the
replace-head gate bypass lets its post-snapshot tail apply into the doomed
pre-swap table — the swap then drops the tail with no failure signal.
_plan_one now defers while such a run exists in the queue and retries on
the next planner tick.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@EDsCODE EDsCODE self-assigned this Jul 7, 2026
@trunk-io

trunk-io Bot commented Jul 7, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@EDsCODE
EDsCODE marked this pull request as ready for review July 9, 2026 17:50
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team July 9, 2026 17:50
@greptile-apps

greptile-apps Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "Merge remote-tracking branch 'origin/mas..." | Re-trigger Greptile

@EDsCODE
EDsCODE merged commit 5e47e57 into master Jul 9, 2026
176 checks passed
@EDsCODE
EDsCODE deleted the fix/duckgres-backfill-generation-pinning branch July 9, 2026 21:57
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 9, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-09 22:28 UTC Run
prod-us ✅ Deployed 2026-07-09 22:41 UTC Run
prod-eu ✅ Deployed 2026-07-09 22:44 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants