Skip to content

fix(data-imports): retry queryable-folder copy after a concurrent vacuum race - #75869

Merged
Gilbert09 merged 1 commit into
masterfrom
posthog-code/fix-data-imports-queryable-copy-race
Jul 31, 2026
Merged

fix(data-imports): retry queryable-folder copy after a concurrent vacuum race#75869
Gilbert09 merged 1 commit into
masterfrom
posthog-code/fix-data-imports-queryable-copy-race

Conversation

@Gilbert09

Copy link
Copy Markdown
Member

Problem

Error tracking issue surfaced a FileNotFoundError during a Postgres sync's post-load step:

FileNotFoundError: .../subscription_acquisition_daily/part-00000-....snappy.parquet

Call path: run_post_load_operations -> _publish_queryable_files -> prepare_s3_files_for_querying -> copy_file -> s3fs's _cp_file.

_publish_queryable_files lists the Delta table's current parquet files with get_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 optional refresh_file_uris callback. If a copy fails with FileNotFoundError, 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 to delta_table_helper.get_file_uris, the same source used for the initial listing.

How did you test this code?

  • Added test_retries_with_fresh_listing_when_source_file_vanishes_mid_copy in test_util.py: _cp_file raises FileNotFoundError once, then a fresh listing is provided via refresh_file_uris — asserts the retry succeeds and copies the refreshed file. Regression: this exact race crashing the sync instead of self-healing.
  • Added test_propagates_vanished_source_file_without_refresh_callback: without refresh_file_uris, the error still propagates — guards against silently swallowing a genuine missing-file bug for callers that don't opt in.
  • Ran the full test_util.py and common/test/test_load.py suites locally — all pass.
  • ruff check / ruff format --check clean on all changed files.
  • Repo-wide uv run mypy --cache-fine-grained . — clean (17284 source files, no issues).

Automatic notifications

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

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_operations to 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 DeltaError signature (a _delta_log/*.json commit file vanishing during vacuum()/optimize.compact() inside delta_table_helper.py) as transient/non-reported. Neither touches util.py, copy_file, or prepare_s3_files_for_querying — this PR fixes a plain FileNotFoundError on 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

…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
@trunk-io

trunk-io Bot commented Jul 31, 2026

Copy link
Copy Markdown

😎 This pull request was merged.

@github-actions

Copy link
Copy Markdown
Contributor

Hey @Gilbert09! 👋

It looks like your git author email on this PR isn't your @posthog.com address (owerstom@gmail.com). Since you're on the PostHog team, it's worth pointing your local git author email at your @posthog.com address. Why it matters:

  • Consistent work identity in git history — internal tooling that attributes commits to team members keys off your @posthog.com address.
  • Keeps team contributions easy to tell apart from external community ones when scanning history.

You can fix it for this repo with:

git config user.email "you@posthog.com"

Or set it globally with git config --global user.email "you@posthog.com". No need to redo this PR — just a nudge for next time. 🙂

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team July 31, 2026 08:22
@Gilbert09 Gilbert09 added the stamphog Request AI approval (no full review) label Jul 31, 2026 — with PostHog

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@Gilbert09
Gilbert09 merged commit 0350fb1 into master Jul 31, 2026
267 checks passed
@Gilbert09
Gilbert09 deleted the posthog-code/fix-data-imports-queryable-copy-race branch July 31, 2026 10:50
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 31, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-31 11:29 UTC Run
prod-us ✅ Deployed 2026-07-31 11:51 UTC Run
prod-eu ✅ Deployed 2026-07-31 11:51 UTC Run

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

Labels

stamphog Request AI approval (no full review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant