Skip to content

fix(bulk-submit): lease heartbeats through files, renewed by the claimed duration - #740

Merged
smunini merged 3 commits into
mainfrom
fix/448-lease-heartbeat-mid-file
Aug 28, 2026
Merged

fix(bulk-submit): lease heartbeats through files, renewed by the claimed duration#740
smunini merged 3 commits into
mainfrom
fix/448-lease-heartbeat-mid-file

Conversation

@angela-helios

Copy link
Copy Markdown
Contributor

Closes #739. Found live during #448's Synthea sanity pass — writeup on the issue.

The livelock

run_job heartbeated the lease only between files. A single output file whose ingestion outlives the lease expires mid-stream; claim_next_manifest sees an abandoned processing manifest and hands it to a rival worker, which restarts from the first file. Entries upsert idempotently, so counts freeze while CPU burns; every re-claim refreshes lease_expiry, so the #646/#711 stall detection sees a healthy lease forever. Observed: all 896 Encounters of a 20-file submission re-indexed 7× in 7 minutes, poll stuck at processing 0%.

Compounding it, all four backends' heartbeat() renewed by a hardcoded 60 s, ignoring the claimed lease_duration — so no configuration could buy a slow file more time.

The fix

  • run_job drives each file's ingest (and the deleted-files pass untouched — it heartbeats per file and those files are refs, not bulk data) under a tokio::select! with a periodic heartbeat at a third of the remaining lease (clamped 1–60 s), aborting cleanly on LeaseLost.
  • ManifestLease now carries lease_duration; sqlite/postgres/mongodb/s3 heartbeat() renew by it.

Regression test

a_file_slower_than_the_lease_stays_leased_to_completion: one file trickled over ~6 s under a 2 s lease. Mid-file, a rival claim_next_manifest must return None; the run must complete with every line ingested exactly once. Verified both ways — with the periodic heartbeat neutralized the test fails at the rival-claim assert with the exact production symptom.

… its own duration

Closes #739.

One output file whose ingestion outlives the lease expired mid-stream:
claim_next_manifest saw an abandoned processing manifest, a rival worker
claimed it, and the manifest restarted from its first file - an unbounded
silent loop, invisible in the counts because re-ingested entries upsert
idempotently, and invisible to the #646 stall detection because every
re-claim refreshes the lease. Found live in #448's Synthea pass: all 896
Encounters of a 20-file submission re-indexed seven times in as many
minutes while the poll answered processing 0%.

run_job now drives each file's ingest under a select with a periodic
heartbeat at a third of the remaining lease, and ManifestLease carries
the duration it was claimed with so the four backends' heartbeat renews
by it rather than a hardcoded 60 seconds.

The regression test trickles a single file over six seconds under a
two-second lease: a rival claim mid-file must come back empty, and every
line lands exactly once.
@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/persistence/src/core/bulk_submit_worker.rs 87.50% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

Covers the LeaseLost arms of the heartbeated ingest loop (codecov
patch): after a legitimate takeover, the stale claimant's first beat
answers LeaseLost and run_job returns without ingesting anything under
the old fencing token.
The four backends each carried the same duration-to-expiry arithmetic;
ManifestLease::renewed_expiry owns it now (and the sqlite ring covers
it), leaving each heartbeat one call. Mongo still rounds to BSON
precision on its side.
@smunini
smunini merged commit d180ce2 into main Aug 28, 2026
18 checks passed
@smunini
smunini deleted the fix/448-lease-heartbeat-mid-file branch August 28, 2026 00:46
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.

bulk-submit: a file slower than the lease livelocks the manifest — silent restart loop from the first file

2 participants