fix(data-imports): retry queryable-folder copy after a concurrent vacuum race - #75869
Merged
Gilbert09 merged 1 commit intoJul 31, 2026
Merged
Conversation
…uum deletes a source file A concurrent compact/vacuum pass on the same Delta table can physically delete a parquet data file between `get_file_uris()` listing it and the copy step reading it for the queryable folder, raising `FileNotFoundError`. `prepare_s3_files_for_querying` now accepts an optional `refresh_file_uris` callback and retries once against a fresh listing when a source file vanishes mid-copy; `_publish_queryable_files` wires it to `delta_table_helper.get_file_uris`. Generated-By: PostHog Code Task-Id: 792b4cce-c400-4082-8b35-693aedc2438e
|
😎 This pull request was merged. |
Contributor
|
Hey @Gilbert09! 👋 It looks like your git author email on this PR isn't your
You can fix it for this repo with: git config user.email "you@posthog.com"Or set it globally with |
There was a problem hiding this comment.
Small, contained retry fix in the warehouse-sources data pipeline (not event ingestion or a data model/schema change), with backward-compatible default behavior, tests covering both the retry and no-callback paths, and authored by an owning-team member.
- Author wrote 0% of the modified lines and has 905 merged PRs in these paths (familiarity MODERATE).
- 👍 on the PR from hex-security-app[bot].
Gate mechanics and policy version
| Gate | Result | |
|---|---|---|
| prerequisites | ✓ | all clear |
| deny-list | ✓ | no deny categories matched |
| size | ✓ | 16L, 2F substantive, 51L/3F incl. docs/generated/snapshots — within ceiling |
| tier | ✓ | T1-agent / T1b-small (51L, 3F, single-area, fix) |
| stamphog 2.0.0b3 | .stamphog/policy.yml @ 3f382b7 · reviewed head ef87567 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Error tracking issue surfaced a
FileNotFoundErrorduring a Postgres sync's post-load step:Call path:
run_post_load_operations->_publish_queryable_files->prepare_s3_files_for_querying->copy_file->s3fs's_cp_file._publish_queryable_fileslists the Delta table's current parquet files withget_file_uris()right after this sync's own compact/vacuum pass, then copies each listed file into a stable, timestamped folder that ClickHouse/DuckDB read via an S3 glob. Between that listing and the copy, a concurrent maintenance pass on the same table — typically a Temporal activity attempt that heartbeat-timed-out but is still running as a "zombie", a scenario already documented elsewhere in this pipeline (RepartitionSupersededError,is_transient_delta_maintenance_error) — can compact/vacuum away one of the listed files before the copy reads it.This is a bug in the shared list-then-copy pattern, not a source-specific or transient-infra issue: the file is gone, so blindly retrying the same list would fail again, but the data isn't lost (compaction always merges forward), so a fresh listing has it.
Changes
prepare_s3_files_for_querying(util.py) now accepts an optionalrefresh_file_uriscallback. If a copy fails withFileNotFoundError, it re-fetches the current file list and retries the copy once. Callers that don't pass it keep today's behavior (the error still propagates, so Temporal's existing retry is the fallback)._publish_queryable_files(common/load.py) wires this todelta_table_helper.get_file_uris, the same source used for the initial listing.How did you test this code?
test_retries_with_fresh_listing_when_source_file_vanishes_mid_copyintest_util.py:_cp_fileraisesFileNotFoundErroronce, then a fresh listing is provided viarefresh_file_uris— asserts the retry succeeds and copies the refreshed file. Regression: this exact race crashing the sync instead of self-healing.test_propagates_vanished_source_file_without_refresh_callback: withoutrefresh_file_uris, the error still propagates — guards against silently swallowing a genuine missing-file bug for callers that don't opt in.test_util.pyandcommon/test/test_load.pysuites locally — all pass.ruff check/ruff format --checkclean on all changed files.uv run mypy --cache-fine-grained .— clean (17284 source files, no issues).Automatic notifications
Docs update
N/A — internal pipeline reliability fix, no user-facing or API surface change.
🤖 Agent context
Autonomy: Fully autonomous
Triaged from a live PostHog error-tracking issue (webhook-delivered) using Claude Code. Pulled the stack trace and
warehouse_sources_*event properties via the PostHog MCP error-tracking tools, confirmed the exact failing frame (util.py:copy_file->s3fs.core._cp_file->_info), and traced the call path back through_publish_queryable_files/run_post_load_operationsto identify the list-then-copy race.Searched open PRs (by exception type, message phrase, module path, and the maintainer's own open PRs) before starting. Found two related-but-distinct open PRs: #75240 and #75242, both classifying a different
DeltaErrorsignature (a_delta_log/*.jsoncommit file vanishing duringvacuum()/optimize.compact()insidedelta_table_helper.py) as transient/non-reported. Neither touchesutil.py,copy_file, orprepare_s3_files_for_querying— this PR fixes a plainFileNotFoundErroron a data parquet file in a different code path (the queryable-folder publish step), so they don't overlap.Skills invoked:
/writing-tests.Created with PostHog Code