Skip to content

chore(warehouse): remove the always-on auto-coarsen rollout flag - #95006

Open
posthog[bot] wants to merge 2 commits into
masterfrom
posthog-self-driving/chorewarehouse-remove-the-always-on-96e63a
Open

chore(warehouse): remove the always-on auto-coarsen rollout flag#95006
posthog[bot] wants to merge 2 commits into
masterfrom
posthog-self-driving/chorewarehouse-remove-the-always-on-96e63a

Conversation

@posthog

@posthog posthog Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Problem

  • Nobody sees this one. It clears a rollout flag that stopped deciding anything three weeks ago, so the next person in this controller reads two gates instead of four.
  • The data-warehouse-auto-coarsen flag sits at 100% for the catch-all group and 100% for the team group, and PostHog reports it as stale with a full rollout.
  • Every coarsening decision still evaluated it. That is a Team fetch plus a PostHog API call, on a path that runs for every within-budget table on every sync.
  • The dead branch also cost a decline reason (flag_disabled) and a release path in the repartition activity that could never fire.

Reported by a PostHog inbox scout. Flag definition: 798825.

Warning

This removes the only runtime kill switch on coarsening. Read "Reversibility" below before approving.

Changes

  • Coarsening now runs on its policy gates alone: partition count, partition size, OOM history, and layout age. No flag can hold it back.
  • WAREHOUSE_AUTO_COARSEN_FLAG and is_auto_coarsen_enabled are gone, with the two call sites that read them.
  • maybe_flag_for_coarsening drops its last gate, so the remaining gates end at the database and no longer reach a feature flag.
  • The repartition activity's release check now runs only when the repartition flag is off. A queued coarsening rewrite always proceeds, which is what the always-on flag already produced.
  • DELTA_COARSEN_DECLINE_TOTAL loses the flag_disabled label. The other reasons are unchanged.
  • The rest is mechanical: comments, the README gating notes, and the test patches that forced the flag on.

Nothing user-visible changes. The controller made the same decision before this PR.

Reversibility

A reviewer should weigh this before approving. The trade-off is real and this PR does not remove it.

  • A flag at 100% is still a working kill switch. Dropping its rollout to 0% stopped coarsening within a flag poll, with no deploy.
  • After this PR, no such switch exists. Stopping coarsening needs a revert and a deploy.
  • is_auto_repartition_enabled does not cover the gap. Coarsening is evaluated on the healthy branch of maybe_flag_for_repartition, which is reached before enabled is resolved, so the repartition flag never gated coarsening.
  • The blast radius is bounded by the gates that remain, not by a switch. A table is coarsened only at 16 or more partitions, with its largest under an eighth of the budget, no blocking merge OOM in 14 days, and a layout at least 7 days old. select_coarsen_target still refuses any target that would not fit the budget.
  • Ordering still matters: land this before the flag is disabled in PostHog. Turning the flag off while the current code is live would stop coarsening.

Keeping a kill switch is a defensible alternative to this PR. It is a call for the owning team, not for the agent that opened it.

How did you test this code?

  • products/warehouse_sources/backend/temporal/data_imports/pipelines/core/test_repartition_controller.py and .../workflow_activities/tests/test_repartition_table.py both pass against a local dev stack.
  • No test was added. This PR removes a branch, so the regression to guard is that the enabled path still works, and the existing coarsening and flag-gate cases already cover it.
  • Two parameterized cases lost their flag_disabled variant, and one activity case (coarsen_flag_off_releases_a_queued_coarsen) asserted behavior that no longer exists.
  • Not run: the e2e suite in tests/e2e/test_end_to_end.py. It needs a live Postgres source and object storage that this sandbox does not provide. Its coarsening tests were edited, so CI is the check on them.

Automatic notifications

  • Publish to changelog?

Docs update

products/warehouse_sources/backend/temporal/data_imports/README.md is updated in this PR. No external docs mention the flag.

🤖 Agent context

Autonomy: Fully autonomous

Written by Claude Opus 5 through PostHog Desktop, from an inbox report on stale feature flags.

  • Skills invoked: /writing-pr-descriptions, /writing-simplified-technical-english.
  • No duplicate: gh pr list --state open --search "repartition" and a search on the flag name found no PR covering this cleanup. One open PR touches this area (#92380) on an unrelated corrupt-table path.
  • Decision along the way: the activity's release check first kept a per-family release variable with coarsening always resolving to false. Collapsing the whole block behind not enabled says the same thing in fewer branches.
  • The "Reversibility" section answers a review escalation. The claim there was checked against the code rather than accepted, and it held. No code was changed in response, because restoring a lever under a new mechanism is a wider change than this cleanup and belongs to the owning team.
  • Public artifact: everything here comes from the repository and the linked flag. No customer or session material reached the diff.

Created with PostHog Desktop from this inbox report.

The `data-warehouse-auto-coarsen` flag is at 100% for every team, so it always
evaluates to true. Every coarsening decision still paid for a Team fetch and a
PostHog API call to learn that.

Drop `WAREHOUSE_AUTO_COARSEN_FLAG`, `is_auto_coarsen_enabled`, and the two gates
that read it: the detection gate in `maybe_flag_for_coarsening` and the queued
rewrite release in the repartition activity. The `flag_disabled` decline reason
goes with them.

Generated-By: PostHog Desktop
Task-Id: 428318a9-ebb2-4839-ae9a-5a28acfe33e3
@trunk-io

trunk-io Bot commented Sep 4, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@posthog posthog Bot added the skip-agent-review Save $$$, skip auto agent reviews (Greptile) — use for trivial or chore PRs label Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 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.

ClickHouse migration SQL — none

No ClickHouse migrations in the latest push.

@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 — escalated to a human reviewer.

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

This removes the always-on auto-coarsen feature flag (and the rollback lever it provided) from the warehouse repartitioning/coarsening controller that actively rewrites customer data table layouts in production; the diff logic checks out against the description, but this is production data-pipeline behavior with reduced reversibility and it has zero human or agent review, and the bot author isn't on the owning team.

  • No independent review (0 top-level/inline reviews, only an unrelated trunk-merge bot comment)
  • Removes the feature flag that previously let ops disable auto-coarsening if it misbehaves, reducing the rollback path for a change that governs live data rewrites
  • Author (posthog[bot]) is not on the owning team @PostHog/team-warehouse-sources and familiarity signals don't apply to this machine author
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list no deny categories matched
size 44L, 2F substantive, 106L/6F incl. docs/generated/snapshots — within ceiling
tier T1-agent / T1c-medium (106L, 6F, single-area, chore)
stamphog 2.0.0b4 .stamphog/policy.yml @ 1b0adb4 · reviewed head 1b0adb4

@stamphog stamphog Bot added the reviewhog ($$$) Reviews pull requests before humans do label Sep 4, 2026
@posthog

posthog Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

🦔 PostHog Review reviewed this pull request

Nothing worth raising this time, so here's a calming picture instead:

Someone relaxing in a sunny garden

@posthog

posthog Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PostHog Review

Found 1 should fix.

Comment on lines +322 to +327
# such a table, and it does nothing here if it only gates detection. Only the triggers this flag
# stages answer to it; every other reason fails open, because that work was queued knowing that
# syncing on the old layout is the worse option, so it must never dead-end on a rollout flag. A
# staged swap is always driven to completion because temp is the source of truth in that window
# and live may already be deleted.
if pending is not None and swap is None and not enabled:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Queued coarsening loses its release control

should_fix

Why we think it's a valid issue
  • Checked: the pre-PR and post-PR forms of the release block, every caller of maybe_flag_for_coarsening, the trigger_reason values the controller writes, the attempt cap, the activity timeout, and the admin views for an alternative operator lever.
  • Found: the new gate at products/warehouse_sources/backend/temporal/data_imports/workflow_activities/repartition_table.py:327 binds enabled to is_auto_repartition_enabled, and the inner test only accepts proactive_threshold and oom_history. The controller writes "coarsening" for an automatic stage at products/warehouse_sources/backend/temporal/data_imports/pipelines/core/repartition_controller.py:250, so that reason now reaches no release branch.
  • Found: the repartition flag is not a substitute switch for coarsening. maybe_flag_for_coarsening runs at repartition_controller.py:356 and :386, both before the flag read at :406. Coarsening detection therefore continues with the repartition flag off, and a coarsening rewrite already queued still runs.
  • Found: the queued rewrite runs ahead of extraction with start_to_close_timeout=dt.timedelta(hours=6) (products/warehouse_sources/backend/temporal/data_imports/external_data_job.py:680), on every sync while the marker is set.
  • Found: MAX_REPARTITION_ATTEMPTS = 3 does not bound a rewrite that advances. repartition_table.py:672 writes attempts: 0 after any forward progress, so a rewrite that advances a little each run never reaches _give_up.
  • Found: no other supported release exists. The admin only stages a target (products/warehouse_sources/backend/admin/external_data_schema_admin.py:265); no admin action or command clears repartition_pending.
  • Found: the comment kept at repartition_table.py:319-326 still states that the flag is the only lever support has, and it justifies the fail-open path with work "queued knowing that syncing on the old layout is the worse option". That reason describes operator-staged work. The deleted comment named exactly that set (admin, coarsening nominations), and coarsening_requested already covers the nomination. The automatic coarsening family now fails open under a reason that does not apply to it.
  • Impact: a large or pathological automatic coarsening rewrite can hold that schema's pre-extraction slot for up to six hours per sync, for an unbounded number of syncs, and an operator has no supported way to release it short of a code change or a manual database edit. The change is dead today because the flag sits at 100%, but it removes the documented stop for a state the surrounding code was built to escape.
Issue description

This condition never stops a queued coarsening rewrite. The rewrite runs before extraction and can consume the six-hour activity budget. Progressing rewrites reset the failure counter, so they can delay many syncs. The removed flag was the supported release control for this state.

Suggested fix

Retain an emergency stop for queued automatic coarsening. Evaluate it only when trigger_reason is coarsening and no swap exists. This preserves the release path without adding a flag call to every sync. Add a test for an existing queued coarsen when this control is disabled.

Prompt to fix with AI (copy-paste)
## Context
@products/warehouse_sources/backend/temporal/data_imports/workflow_activities/repartition_table.py#L322-327

<issue_description>
This condition never stops a queued coarsening rewrite. The rewrite runs before extraction and can consume the six-hour activity budget. Progressing rewrites reset the failure counter, so they can delay many syncs. The removed flag was the supported release control for this state.
</issue_description>

<issue_validation>
- **Checked:** the pre-PR and post-PR forms of the release block, every caller of `maybe_flag_for_coarsening`, the trigger_reason values the controller writes, the attempt cap, the activity timeout, and the admin views for an alternative operator lever.
- **Found:** the new gate at `products/warehouse_sources/backend/temporal/data_imports/workflow_activities/repartition_table.py:327` binds `enabled` to `is_auto_repartition_enabled`, and the inner test only accepts `proactive_threshold` and `oom_history`. The controller writes `"coarsening"` for an automatic stage at `products/warehouse_sources/backend/temporal/data_imports/pipelines/core/repartition_controller.py:250`, so that reason now reaches no release branch.
- **Found:** the repartition flag is not a substitute switch for coarsening. `maybe_flag_for_coarsening` runs at `repartition_controller.py:356` and `:386`, both before the flag read at `:406`. Coarsening detection therefore continues with the repartition flag off, and a coarsening rewrite already queued still runs.
- **Found:** the queued rewrite runs ahead of extraction with `start_to_close_timeout=dt.timedelta(hours=6)` (`products/warehouse_sources/backend/temporal/data_imports/external_data_job.py:680`), on every sync while the marker is set.
- **Found:** `MAX_REPARTITION_ATTEMPTS = 3` does not bound a rewrite that advances. `repartition_table.py:672` writes `attempts: 0` after any forward progress, so a rewrite that advances a little each run never reaches `_give_up`.
- **Found:** no other supported release exists. The admin only stages a target (`products/warehouse_sources/backend/admin/external_data_schema_admin.py:265`); no admin action or command clears `repartition_pending`.
- **Found:** the comment kept at `repartition_table.py:319-326` still states that the flag is the only lever support has, and it justifies the fail-open path with work "queued knowing that syncing on the old layout is the worse option". That reason describes operator-staged work. The deleted comment named exactly that set (admin, coarsening nominations), and `coarsening_requested` already covers the nomination. The automatic `coarsening` family now fails open under a reason that does not apply to it.
- **Impact:** a large or pathological automatic coarsening rewrite can hold that schema's pre-extraction slot for up to six hours per sync, for an unbounded number of syncs, and an operator has no supported way to release it short of a code change or a manual database edit. The change is dead today because the flag sits at 100%, but it removes the documented stop for a state the surrounding code was built to escape.
</issue_validation>

## Task
Investigate the issue and solve it

<potential_solution>
Retain an emergency stop for queued automatic coarsening. Evaluate it only when trigger_reason is coarsening and no swap exists. This preserves the release path without adding a flag call to every sync. Add a test for an existing queued coarsen when this control is disabled.
</potential_solution>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Checked this against the current head and the finding holds — I'm escalating rather than fixing, because every way to address it is a call the flag's owner should make, not something I should decide unattended.

What I confirmed:

  • The release check now reads only the repartition flag, and only lets proactive_threshold and oom_history through. An automatic coarsening rewrite writes the trigger reason coarsening, so it matches no release branch.
  • The repartition flag is not a stand-in switch. Coarsening detection runs before that flag is ever read, so turning the repartition flag off neither stops coarsening from being staged nor releases one already queued.
  • The queued rewrite does run ahead of extraction on every sync, with a six-hour activity budget, and the attempt cap does not bound it: any forward progress resets the attempt counter to zero, so a rewrite that advances a little each run never gives up.
  • There is no other supported release. The admin can only stage a target, and no admin action or management command clears the pending marker — clearing it happens only from inside the activity itself.

So the concern is real: after this PR, a large or pathological automatic coarsening rewrite has no supported off switch short of a code change or a manual database edit.

Why I'm not fixing it here. Keeping a flag read just for the queued-release path is the obvious minimal patch, but it conflicts with this PR's own release note — the flag is about to be turned off in PostHog, and a retained read would then release every queued coarsening rewrite the moment that happens. It would also leave a flag that must never be disabled, which is the stale-flag problem this PR exists to remove. The alternatives — a new settings kill switch, or an admin action / management command that clears the pending marker — are new operator surface area beyond this PR's scope.

What a human needs to decide, one of:

  1. Accept the trade-off. Automatic coarsening rewrites join admin and coarsening_requested as work that always runs to completion — those already had no release lever before this PR. If that's the call, the comment above the release check should be corrected too: its fail-open justification says the work "was queued knowing that syncing on the old layout is the worse option", which describes operator-staged work and no longer covers the automatic coarsening family.
  2. Keep a coarsening switch, scoped to the queued-release path only, and leave the flag enabled in PostHog rather than turning it off.
  3. Land the removal and follow up with a general release lever — an admin action or management command that clears a pending repartition — which covers admin, coarsening_requested, and coarsening in one place instead of one flag per family.

Happy to implement whichever is chosen.

@posthog posthog Bot removed the reviewhog ($$$) Reviews pull requests before humans do label Sep 4, 2026
…-the-always-on-96e63a

Generated-By: PostHog Desktop
Task-Id: 428318a9-ebb2-4839-ae9a-5a28acfe33e3
@posthog

posthog Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

On the escalation, taking the three points in turn.

Rollback lever. The point is correct, and it is now stated in the description under "Reversibility" rather than left for a reviewer to find. Checked rather than assumed: is_auto_repartition_enabled does not cover the gap, because coarsening is evaluated on the healthy branch of maybe_flag_for_repartition, which is reached before enabled is resolved. So after this PR, stopping coarsening needs a revert and a deploy.

No code changed in response. Restoring a lever under a new mechanism is a wider change than a flag cleanup, and which lever is worth carrying is a call for the owning team.

No independent review and author not on the owning team. Neither is something this PR can resolve, which is why the stamphog label is not re-applied. The escalation asked for a human on the owning team, and that is the right outcome here.

The PR is out of draft so the full backend matrix runs. Under draft the Django and product test matrices were skipped while their aggregate checks still reported green, so the change had no backend test behind it in CI.

@posthog
posthog Bot marked this pull request as ready for review September 4, 2026 10:20
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team September 4, 2026 10:22
@stamphog stamphog Bot added the reviewhog ($$$) Reviews pull requests before humans do label Sep 4, 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.

Not approved — this change needs a human reviewer.

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

The PR's own agent reviewer flagged a substantive, unresolved concern — removing the coarsen flag also removes the only remaining kill switch for a queued automatic coarsening rewrite (a six-hour, self-resetting activity) — and explicitly declined to fix it, deferring to a human. That's an unaddressed substantive review finding on a data-rewrite path, which blocks auto-approval regardless of size tier.

  • Unresolved inline review thread on repartition_table.py: after this change, a queued 'coarsening' rewrite has no supported way to be released/stopped (previously the coarsen flag could be disabled to release it); the reviewer confirmed the issue and chose to escalate rather than patch it.
  • Author is posthog[bot] (machine user) with no owning-team membership signal usable for assurance, and files fall under @PostHog/team-warehouse-sources — the change needs a human from that team to decide whether losing this operational lever is acceptable.
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list no deny categories matched
size 44L, 2F substantive, 106L/6F incl. docs/generated/snapshots — within ceiling
tier T1-agent / T1c-medium (106L, 6F, single-area, chore)
stamphog 2.0.0b4 .stamphog/policy.yml @ bacd9d5 · reviewed head bacd9d5

@trunk-io

trunk-io Bot commented Sep 4, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

Failed Test Failure Summary Logs
test_operator_nomination_coarsens_a_table_the_automatic_path_refuses[v3] An assertion failed because the nomination data was not cleared after being evaluated, indicating it was not consumed as expected. Logs ↗︎

View Full Report ↗︎Docs

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

Labels

reviewhog ($$$) Reviews pull requests before humans do skip-agent-review Save $$$, skip auto agent reviews (Greptile) — use for trivial or chore PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants