Skip to content

chore(data-warehouse): remove the deltalite write rollout flag - #106874

Merged
trunk-io[bot] merged 4 commits into
masterfrom
tom/dwh-remove-deltalite-flag
Sep 27, 2026
Merged

trunk-io[bot] merged 4 commits into
masterfrom
tom/dwh-remove-deltalite-flag

Conversation

@Gilbert09

Copy link
Copy Markdown
Member

Problem

deltalite is the write path for every keyed incremental merge, so the per-schema rollout flag that chose it no longer chooses anything — it just costs.

The gate ran inside _write_via_deltalite, on the hot path before every merge: Team.objects.get, then ExternalDataSchema.objects.select_related("source").get to resolve source_type, then posthoganalytics.feature_enabled(..., only_evaluate_locally=False). Two Postgres queries and a flags evaluation per batch, to reach the same answer every time.

That matters because per-batch fixed cost is what governs how fast a backlogged schema drains — batches for one (team_id, schema_id) are processed strictly serially. Measured in prod-us over 600 samples, the deltalite write averages 3.8s per batch, and half of all writes move fewer than 30 rows. Anything paid once per batch is paid again for every batch in a backlog.

Changes

  • Deleted deltalite_write.py and its test module. _write_via_deltalite no longer evaluates a flag, so every keyed incremental merge reaches deltalite directly.
  • Removed two docstring references to is_deltalite_write_enabled in unrelated modules that cited it as the pattern to copy.

The delta-rs MERGE fallback is unchanged. deltalite failing before commit still falls back to the MERGE, so this removes a choice that was already always the same — it does not remove the safety net. Removing the MERGE fallback is a separate, larger change.

How did you test this code?

Automated only. No manual run against a live pipeline.

  • core/delta/ suite: 201 passed, 9 failed. All 9 failures are pre-existing, in TestGetDeltaTableUnrecoverableErrors in test_table.py, a file this PR does not touch. I confirmed that by running the same file in a separate worktree that has none of these changes and seeing the identical 9 failures.
  • test_writer.py in full: 58 passed.
  • ruff check / ruff format --check clean; the pre-commit hook's ty check passed.

One existing test needed a real change rather than a mechanical one. test_merge_fallback_raises_reset_signal_on_null_in_non_nullable reached the delta-rs MERGE by relying on the flag evaluation failing closed in the test environment — an implicit dependency, not a stated one. With the flag gone, deltalite handled the batch and the guard correctly did not fire, so the test failed.

The coverage is still worth having: the MERGE fallback still exists, and it would still silently store nulls under a non-nullable schema without that guard. So the test now forces the fallback explicitly by patching _write_via_deltalite to return False, which is what its sibling test_deltalite_write_bypasses_the_reset_signal already does in the opposite direction. The regression it catches is unchanged; only the way it reaches the path is now stated rather than incidental.

Not checked: behaviour under a real deltalite failure in production. The fallback path is exercised by tests, not by an observed live failure.

Release status

  • This change makes a previously flagged feature available to everyone

The data-warehouse-deltalite-write flag can be deleted from the flag UI once this is deployed.

Automatic notifications

  • Publish to changelog?

Docs update

None. No user-facing behaviour, API, or documented workflow changes.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: Claude Code, Opus 5

Skills invoked: /writing-pr-descriptions, /writing-tests.

This came out of profiling why V3 batches take ~8s each. A phase breakdown from 600 production log samples showed the deltalite write dominating, which made the per-batch flag evaluation in front of it worth deleting rather than caching.

No duplicate: gh pr list --state open --search "deltalite flag" found nothing related.

CodeRabbit CLI is not installed on this machine, so this PR opens without a local review pass.

Production metrics and structured logs informed this. No customer identifiers, team ids, or table contents appear in the code, the tests, or this description.

deltalite is the write path for every keyed incremental merge now, so the
per-schema rollout gate only costs. It was evaluated inside _write_via_deltalite
on the hot path before every merge: two Postgres queries (Team, then
ExternalDataSchema to resolve source_type) plus a feature_enabled call with
only_evaluate_locally=False.

Production measurement puts the deltalite write at ~3.8s average per batch with
half of all writes moving under 30 rows, so per-batch fixed cost is what governs
drain time for a backlogged schema.

Delete the flag module and its gate. The delta-rs MERGE fallback is unchanged:
deltalite failing still falls back, so this only removes a choice that was
already always the same. Removing the MERGE fallback itself is a separate change.
Copilot AI lite review requested due to automatic review settings September 25, 2026 16:04
@Gilbert09 Gilbert09 added the stamphog Request AI approval (no full review) label Sep 25, 2026
@trunk-io

trunk-io Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

😎 Merged successfully - details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@pr-assigner-resolver-posthog

Copy link
Copy Markdown

👀 Auto-assigned reviewers

These soft owners were skipped because they only have minor changes here. Nothing blocks merge, so self-assign if you'd like a look:

  • @PostHog/team-managed-warehouse (products/data_warehouse/product.yaml)

Soft owners come from each directory's owners.yaml and each product's product.yaml (resolved nearest-file-wins). The locator after each owner is the file that decided it. Generated files and lockfiles are ignored when deciding ownership.

stamphog[bot]

This comment was marked as outdated.

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Trunk lane — backend Python lane

This PR is assigned to the backend Python lane. It runs backend Python tests and may merge in parallel with PRs in other lanes.

✅ Duplication (Python) — clean

New Python code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

✅ Duplication (TypeScript) — clean

New TypeScript code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 422e63b9-02dc-428a-aed3-c20ce46ceb18

📥 Commits

Reviewing files that changed from the base of the PR and between c146704 and 0ef03a1.

📒 Files selected for processing (1)
  • products/warehouse_sources/backend/temporal/data_imports/tests/e2e/test_end_to_end.py

Included review availability: Your plan provides up to 12 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Keyed incremental merges now attempt deltalite without checking a feature flag. If the upsert fails before commit, the writer returns False so the caller can use the delta-rs MERGE fallback. The flag helper and its tests were removed. Writer and end-to-end tests now cover behavior without flag mocks.

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to 0ef03

Google Ads rows with NULL components in configured keys can be duplicated on repeated imports, making warehouse results incorrect and growing storage. Keep those batches on the NULL-safe MERGE path before merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 0ef03

Keyed incremental merges now try deltalite without a per-schema switch. Failed attempts can still fall back, but that fallback cannot redirect a write that succeeds and produces incorrect data. The previous rollout state and an alternative way to disable deltalite have not been established.

Retained concerns

  • Medium · reliability · inferred: The removed per-schema selector leaves exception fallback, but no selector in the inspected write path for routing a schema away from deltalite after a successful yet incorrect commit. Whether another operational control exists, or every affected schema was already enabled, is unverified.
Security review details

Security Blast Radius

  • inferred — The routing change can affect keyed incremental merges handled by this writer across schemas; the evidence does not establish the historical enabled population or a tenant-specific exposure.

Trust Boundaries and Controls

  • observed — The removed team- and schema-aware check selected a write engine. The inspected replacement routes by write outcome, not by that flag; this evidence does not establish an authorization bypass.

Resilience and Maintainability Implications

  • inferred — Commit tagging and the existing MERGE retry path support replay and conflict handling, but mocked writer tests do not prove that a real deltalite upsert can never raise after committing or establish concurrent-upsert behavior.

Hardening Proposals

  • proposed — Confirm the previous flag state for affected schemas, identify a flag-independent way to disable deltalite if necessary, and verify the real upsert's commit/error and concurrency guarantees before relying on exception fallback as the recovery boundary.
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description is complete and self-contained. It explains the problem, changes, fallback behavior, testing results and limitations, release status, documentation impact, and agent context. It does n…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
products/warehouse_sources/backend/temporal/data_imports/pipelines/core/delta/writer.py-159-179 (1)

159-179: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Keep post-commit handle refresh failures out of the fallback path.

DeltaLiteTable.upsert commits with CommitBuilder.build(...).await and then calls self.reload(py)?. A reload error after the durable commit reaches DeltaWriter._write_via_deltalite's fallback handler, which returns False and runs the delta-rs MERGE. This can duplicate nullable primary-key rows: deltalite treats NULL keys as non-matching and inserts them, while the fallback predicate uses IS NOT DISTINCT FROM. The already-committed extra row cannot be removed by that MERGE.

Make the handle reload best-effort after the commit.

Suggested fix
-        self.reload(py)?;
+        // The upsert is already durable. A handle-refresh failure must not report
+        // the committed write as a failure to the caller.
+        let _ = self.reload(py);

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: a7dc9a61-f572-4a98-a614-855e51823a8c

📥 Commits

Reviewing files that changed from the base of the PR and between 2b7029a and 3f583c0.

📒 Files selected for processing (6)
  • products/data_warehouse/backend/s3_proxy.py
  • products/warehouse_sources/backend/temporal/data_imports/pipelines/core/auto_widen_resync.py
  • products/warehouse_sources/backend/temporal/data_imports/pipelines/core/delta/test/test_writer.py
  • products/warehouse_sources/backend/temporal/data_imports/pipelines/core/delta/writer.py
  • products/warehouse_sources/backend/temporal/data_imports/pipelines/core/deltalite_write.py
  • products/warehouse_sources/backend/temporal/data_imports/pipelines/core/test/test_deltalite_write.py
💤 Files with no reviewable changes (2)
  • products/warehouse_sources/backend/temporal/data_imports/pipelines/core/test/test_deltalite_write.py
  • products/warehouse_sources/backend/temporal/data_imports/pipelines/core/deltalite_write.py

Included review availability: Your plan provides up to 12 included reviews per hour; 7 remain after this review.

@trunk-io

trunk-io Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

@veria-ai

veria-ai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

…llback

`_write_via_deltalite` no longer gates on a rollout flag, and `deltalite` is a real
dependency in CI (unlike the old flag, which failed closed under test), so every
primary-keyed `write()` call in this file was actually driving the real `deltalite`
package instead of the delta-rs MERGE path most of these tests assert on.

Default `_write_via_deltalite` to the MERGE fallback for every test in the module,
except `TestDeltaliteWritePath` (drives the real method / fakes the `deltalite`
module itself) and `TestNullabilityDriftGuardOrder` (already sets this mock
explicitly in both directions).
@stamphog
stamphog Bot dismissed their stale review September 25, 2026 17:36

A new stamphog review started for this PR — the fresh verdict replaces this approval.

@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.

Not approved — this change needs a human reviewer.

Re-add the stamphog label to request another review once you have addressed this.

This removes the rollout flag so deltalite becomes the write path for 100% of keyed incremental merges (previously a partial, killable rollout) — a production data-integrity change. A bot reviewer (@veria-ai) flagged an unresolved, substantive concern on writer.py: deltalite treats NULL primary-key components as never matching while the delta-rs MERGE fallback is NULL-safe, so once deltalite is universal, NULL-keyed source rows can accumulate as unbounded duplicate inserts. The PR description doesn't address this, and the review summary explicitly reports 0 issues fixed.

  • Author wrote 71% of the modified lines and has 35 merged PRs in these paths (familiarity STRONG).
  • Unresolved inline comment from @veria-ai on writer.py:169 — NULL primary keys become an unbounded insert path now that deltalite (which is not NULL-safe on primary-key matching) handles every keyed merge instead of a partial rollout; not addressed in the diff or description.
  • Cross-team change (touches team-managed-warehouse's s3_proxy.py) in a risky, production write-path area; independent assurance would otherwise come from author's STRONG familiarity on the warehouse-sources side, but the open correctness concern blocks approval regardless.
Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✓ no deny categories matched
size ✓ 98L, 4F substantive, 221L/6F incl. docs/generated/snapshots — within ceiling
tier ✓ T1-agent / T1c-medium (221L, 6F, single-area, chore)
stamphog 2.1.0 .stamphog/policy.yml @ c146704 · reviewed head c146704

@stamphog stamphog Bot removed the stamphog Request AI approval (no full review) label Sep 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
products/warehouse_sources/backend/temporal/data_imports/pipelines/core/delta/test/test_writer.py-120-120 (1)

120-120: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Annotate the generator fixture return type.

This fixture yields only None, and the project requires annotations on every Python signature. Import Generator from collections.abc; Generator[None] is the repository-supported form.

Suggested fix
+from collections.abc import Generator
...
-def _default_deltalite_to_merge_fallback(request: pytest.FixtureRequest):
+def _default_deltalite_to_merge_fallback(
+    request: pytest.FixtureRequest,
+) -> Generator[None]:

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: b7524afe-05e8-4cfe-a6bf-58504b74fd2c

📥 Commits

Reviewing files that changed from the base of the PR and between 6cb6285 and c146704.

📒 Files selected for processing (1)
  • products/warehouse_sources/backend/temporal/data_imports/pipelines/core/delta/test/test_writer.py

Included review availability: Your plan provides up to 12 included reviews per hour; 10 remain after this review.

…ltalite path

`_write_via_deltalite` no longer gates on a rollout flag, and `deltalite` is a real
dependency in CI (unlike the old flag, which failed closed under test), so tests that
call `write()`/the pipeline with primary keys were driving the real `deltalite` package
instead of the delta-rs MERGE path they actually assert on.

- `delta/test/test_writer.py`: default `_write_via_deltalite` to the MERGE fallback for
  every test in the module, except `TestDeltaliteWritePath` (drives the real method /
  fakes the `deltalite` module itself) and `TestNullabilityDriftGuardOrder` (already sets
  this mock explicitly in both directions).
- `tests/e2e/test_end_to_end.py`: `test_partition_folders_delta_merge_called_with_partition_predicate`
  asserts the delta-rs MERGE was called with a specific predicate; force the fallback there
  too so deltalite doesn't silently take the write instead.

Copy link
Copy Markdown
Member Author

Status update from this pass:

  • Review threads: resolved. The only open thread (veria-ai's NULL-primary-key note on writer.py:169) was verified against the vendored rust/deltalite/core/src/{pkset,upsert}.rs — the underlying claim is accurate, but it's pre-existing deltalite upsert behavior that this PR doesn't introduce or change (the flag was already at fully-available rollout), so it's out of scope for a flag-removal PR. Replied with the specifics and marked the thread resolved.
  • CI: green on the latest commit (0ef03a1e1). The two real failures found along the way (Product tests (warehouse-sources 3/4), and its underlying junit) were a genuine gap this PR's own change opened: _write_via_deltalite no longer fails closed under test the way the old flag did, so deltalite (a real CI dependency) was executing for real in tests that were written against the delta-rs MERGE path, including one e2e test asserting the MERGE call directly. Fixed by pinning those tests back to the MERGE fallback.
  • Merge state: branch is up to date with master (server-side update, verified as a true ancestor merge — no conflicts, no leaked files) and mergeable: true.

The remaining mergeable_state: blocked is this repo's stamphog AI-approval gate, not a code or CI issue: it re-reviewed after my first fix commit and returned "Not approved — this change needs a human reviewer," asking for the stamphog label to be re-added to request another automated pass. This sandbox's GitHub credential can't modify PR labels (the label-write endpoints are explicitly out of reach here), so I can't re-trigger it myself.

Needed from a human: either re-add the stamphog label to request another automated review (its objection was the same NULL-PK point addressed above), or have a maintainer review and approve directly.

🦉 via talyn.dev

Copy link
Copy Markdown
Member Author

/trunk merge

@Gilbert09
Gilbert09 requested a review from a team September 27, 2026 21:14

Copy link
Copy Markdown
Member Author

/trunk merge

@Gilbert09 Gilbert09 added the stamphog Request AI approval (no full review) label Sep 27, 2026

@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.

Approved.

This touches the data-warehouse write path, but the author has STRONG familiarity with these exact files, the deleted flag was already fully rolled out (this just removes dead-weight per-batch overhead), and the current head has a clean CodeRabbit pass plus an explicit "no open concerns" from the security bot — the one substantive data-integrity comment raised earlier concerns pre-existing Rust behavior this PR doesn't touch, and was addressed/resolved. The one cross-team file (s3_proxy.py) is a trivial docstring edit.

  • Author wrote 71% of the modified lines and has 42 merged PRs in these paths (familiarity STRONG).
  • A generator test fixture still lacks the return-type annotation CodeRabbit suggested — cosmetic only, not blocking.
Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✓ no deny categories matched
size ✓ 98L, 4F substantive, 226L/7F incl. docs/generated/snapshots — within ceiling
tier ✓ T1-agent / T1c-medium (226L, 7F, single-area, chore)
stamphog 2.2.0 .stamphog/policy.yml @ 0ef03a1 · reviewed head 0ef03a1

@fuziontech fuziontech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated review generated by an automated review agent on behalf of @fuziontech.

The diff cleanly removes the rollout gate and updates the affected tests. Focused validation passed: uv run pytest -q products/warehouse_sources/backend/temporal/data_imports/pipelines/core/delta/test/test_writer.py (58 passed), plus compile and diff checks. The focused e2e test could not initialize because the configured db hostname was unavailable in this environment.

Non-blocking follow-up: a few comments/docstrings still describe this as a “phase 2 canary” or say fallback occurs when deltalite is “enabled”; they could be refreshed separately for terminology consistency.

APPROVE

@trunk-io
trunk-io Bot merged commit b5218ef into master Sep 27, 2026
315 checks passed
@trunk-io
trunk-io Bot deleted the tom/dwh-remove-deltalite-flag branch September 27, 2026 21:36
@deployment-status-posthog

deployment-status-posthog Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-09-27 22:06 UTC Run
prod-us ✅ Deployed 2026-09-27 22:19 UTC Run
prod-eu ✅ Deployed 2026-09-27 22:19 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.

3 participants