feat(managed-warehouse): duckling schema prep for streamed persons - #76997
Draft
fuziontech wants to merge 2 commits into
Draft
feat(managed-warehouse): duckling schema prep for streamed persons#76997fuziontech wants to merge 2 commits into
fuziontech wants to merge 2 commits into
Conversation
Adds the duckling-side pieces for moving persons off the daily batch top-up onto millpond/viaduck streaming replication: - DDL for the two raw current-state tables viaduck full_cdc upserts into (persons with is_deleted tombstones, distinct-id mappings), plus the denormalized view that reproduces the batch persons table's exact column shape for readers. - persons_distinct_ids_table_name(), rule-for-rule identical to the duckgres control plane's discovery derivation so viaduck and the view agree on table names. - ensure_persons_streaming_schema(), wired to a create_persons_streaming_schema config flag on duckling_persons_backfill so operators can prep a duckling from Launchpad with a scratch view name, then swap the view onto the canonical name at cutover. - PERSONS_STREAMING_MIGRATION.md: the per-team cutover runbook, rollback, and open decisions. Batch behavior is unchanged unless the flag is set. Co-authored-by: Shelley <shelley@exe.dev>
|
Merging to
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 |
Contributor
|
Hey @fuziontech! 👋 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 |
…ing runbook The CP override repoint was the wrong cutover mechanism: the batch backfill resolves its write target from the same persons_table_name override, so repointing would redirect the batch export into viaduck's raw table. Cutover now adds static destinations to the two viaduck persons pipelines instead; discovery stays off until the fleet is migrated. Also fills in the cross-repo PR table (millpond persons consumers already run in prod). Co-authored-by: Shelley <shelley@exe.dev>
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
The duckling persons backfill (
duckling_persons_backfill) is batch: a daily sensor re-exports yesterday's persons from ClickHouse (person FINALjoinperson_distinct_id2 FINAL), deletes the day in DuckLake, and re-registers fresh parquet. Data is up to a day stale and every top-up re-reads full person state. We want persons on the same streaming path events already use (millpond Kafka to DuckLake, then viaduck CDC fanout), but the batch table's denormalized shape (one row per distinct_id, properties stamped on) can't be produced by either tool, and deletes/merges arrive on the changelog topics as versioned upserts, not CDC deletes.This PR is the duckling-side prep for that migration. It does not change any existing behavior.
Changes
PERSONS_STREAMING_TABLE_DDL+PERSONS_DISTINCT_IDS_TABLE_DDL: the two raw current-state tables a viaduck full_cdc pipeline upserts into (keys(team_id, id)and(team_id, distinct_id)). Unlike the batch table, the raw persons table carriesis_deleted: tombstones arrive as upserts, never deletes.PERSONS_DENORMALIZED_VIEW_DDL: a view over the two raw tables reproducing the batch persons table's exact column shape (EXPECTED_DUCKLAKE_PERSONS_COLUMNS), tombstones filtered. Reader compatibility after cutover.persons_distinct_ids_table_name(): the name derivation, rule-for-rule identical to the duckgres control plane'sdistinctIDsTableName(see linked duckgres PR), so viaduck's discovery-served write target and this view's join target can never drift apart.ensure_persons_streaming_schema()wired to a newcreate_persons_streaming_schemaconfig flag on the persons backfill asset: an operator preps a duckling from Launchpad with a scratch view name for validation, then swaps the view onto the canonical name at cutover. Raw tables take_rawnames so the batch table keeps the canonical name until then.posthog/dags/PERSONS_STREAMING_MIGRATION.md: the per-team cutover runbook (prep, repoint the control-plane override, final top-up, validate, swap, disable backfill), rollback, and the open decisions (view naming, the viaduck rowid-reuse flush-window issue, update fan-out read cost).Topology after cutover:
flowchart LR K1[clickhouse_person topic]:::phYellow --> M{{millpond}}:::phBlue K2[clickhouse_person_distinct_id topic]:::phYellow --> M M --> L[(shared changelog DuckLake)]:::phGray L --> V{{viaduck full_cdc x2 pipelines}}:::phBlue V --> R1[(persons_<suffix>_raw)]:::phGray V --> R2[(persons_distinct_ids_<suffix>_raw)]:::phGray R1 --> VW[(persons_<suffix> view)]:::phGray R2 --> VW VW --> Q[SQL editor / HogQL readers]:::phRed classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff; classDef phRed fill:#f54e00,stroke:#f54e00,color:#fff; classDef phYellow fill:#f9bd2b,stroke:#f9bd2b,color:#000; classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000;The full historical backfill stays batch (Kafka retention bounds millpond; viaduck discovery seeds at source head). Streaming replaces only the daily top-up.
Cross-repo companions: duckgres PR serving
persons_distinct_ids_tablein discovery (PostHog/duckgres#1026), and PostHog/viaduck#67 addingdiscovery.table_fieldso a persons pipeline routes to the persons table fields instead ofevents_table.How did you test this code?
Agent-run testing, no manual testing claimed:
TestPersonsStreamingSchemainposthog/dags/test_events_backfill_to_duckling.py, following the file's existing in-memory duckdb DDL pattern. The regressions each catches: (1) the parameterized derivation cases lock the name rule against drift from the duckgres control plane's derivation (drift means viaduck writes one table while the view joins another); (2) the view test catches a droppedis_deletedfilter, a wrong join key, or column drift from the batch shape, by inserting live + tombstoned persons/mappings and asserting the projected rows and column set.ruff checkandruff formatpass on both changed files. Full pytest runs in CI.Automatic notifications
Docs update
Runbook added at
posthog/dags/PERSONS_STREAMING_MIGRATION.md;README_DUCKLINGS.mdlinks to it.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Shelley (Claude) on an exe.dev VM, directed by the assignee. Skills invoked:
/writing-tests(gated the new tests to the three named regression shapes above) and/writing-user-facing-copy(runbook voice: no em-dashes, sentence case).Decisions worth knowing: the two-tables-plus-a-view design is forced, since neither millpond (insert-only) nor viaduck (row-level routing, no joins) can emit the batch's denormalized join shape. Raw tables take
_rawnames pre-cutover specifically so the batch table keeps the canonical name and the cutover is a view swap rather than a reader migration; the alternative (leave the canonical name on the raw table, migrate readers to a new view name) is documented as an open decision in the runbook. The view-name-as-config approach lets operators validate streamed counts against the batch table before any reader-visible change. The deployment side lives in PostHog/charts#13846 (viaduck persons pipeline ApplicationSets, shipped disabled).