Relationships
⊘ blocked by #927#929 Incremental activity projector + swamp.events username bloom index (spun off #925, stages 5+7)
Opened by keeb · 7/2/2026· Shipped 8/15/2026
Spun out of #925's approved plan — the shadow-critical slice, kept separate so its parity gate gets undivided attention. Prerequisite: #927 (single-runner gate for the projector; both prod replicas currently run it and can double-grant same-day chunks — that race would poison this issue's parity validation).
Projector: full rescan → touched-set incremental (was #925 stage 5)
Every 60s tick, activity-asof-projector.sql (a) groups the entire all-history cli_daily rollup, (b) does FROM swamp.score_grants FINAL over every post-cutover grant (grant_type not pruned by the ORDER BY), (c) runs window CTEs over every user's full history. Cost grows linearly forever; will eventually blow the tick's 10-min timeout.
Plan:
- CH migration: add
last_seen SimpleAggregateFunction(max, DateTime)toswamp.cli_daily, populated bycli_daily_mvwithmax(now())— ingest time, deliberately NOTmax(created_at): the projector supports late-arriving events for old days ("late arrivals mint one more chunk"), and an event-time watermark would put a late arrival before the watermark and skip its chunk forever. Add a projector test for exactly this case. Ships via the clickhouse-migrations Job in giga-swamp (content-hash ConfigMap, Lab #859);infrastructure/clickhouse/init/02-score-grants.sqlkept in lockstep. activity-asof-projector.sql: add atouchedCTE (distinct_ids withlast_seen >= {WATERMARK}); scope thecliCTE todistinct_id IN touched(full backward history only for touched users — required for correct as-of streak/rolling windows) and thegrantedCTE the same way. Keep FINAL but scoped —distinct_idis the first ORDER BY column ofscore_grants, so this prunes by primary key. Do NOT build the SummingMergeTree granted-units table floated in #925:score_grantsis a ReplacingMergeTree precisely so re-inserted deterministicgrant_ids dedup; an insert-fed SummingMergeTree would double-count re-inserts and corruptunits_before.activity-projector.ts:{WATERMARK}substitution with the same strict-validation pattern as{CUTOVER}; per-process watermark = previous tick start time; epoch on first tick (full pass, idempotent by design — a restart just does one expensive tick).- Parity gate before merge: as-of harness (mirror :8123 vs run DB :8133) — full-history pass and incremental pass must produce identical ledgers (same grant count, equal sums, matching grant_ids). Post-deploy: watch the prod shadow comparison board for drift.
swamp.events username bloom index (was #925 stage 7)
clickhouse-active-days-repository.ts filters swamp.events by username, but the only bloom filter is on distinct_id → full-table scan per user; backfill-score-grants.ts multiplies that by N operatives per pass.
Plan: ALTER TABLE swamp.events ADD INDEX on username (bloom_filter, same granularity as the distinct_id index) + MATERIALIZE INDEX over history, riding the same clickhouse-migrations batch as the projector change (a skip index is result-neutral, so it cannot muddy the parity gate). Leave the admin shadow board's three FINAL-over-ledger queries as-is (low frequency, admin-only); pre-aggregated owner-totals MV is the escape hatch if the board slows.
Full staging rationale, DDD analysis, and adversarial-review trail live in the #925 lifecycle.
Shipped
No activity in this phase yet.