Files
0pt/evidence/bi-p105/materialization-drift.json
T
Claude Opus 5 96ff601248
CI (SIAX Cloud) / security (push) Successful in 49s
Security Scan (SIAX Cloud) / security (push) Successful in 50s
CI (SIAX Cloud) / integration (push) Successful in 55s
CI (SIAX Cloud) / quality (push) Successful in 1m20s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m21s
CI (SIAX Cloud) / security (pull_request) Successful in 42s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 37s
CI (SIAX Cloud) / integration (pull_request) Successful in 1m10s
feat(analytics): BI-P105 — a pre-aggregate serves only while it is provably undrifted
A slow query is a complaint; a drifted pre-aggregate answers instantly, with
confidence, and is wrong. So the router is fail-closed on eight named reasons,
and the one people leave out — drift_never_verified — is the one that matters
on launch day: a brand-new aggregate is exactly as unproven as a drifted one.

Proved against ClickHouse 26.3.10.60, 2M rows, a real materialized view
(evidence/bi-p105/materialization-drift.json):

  after backfill      match, 48 buckets, 801 571 002 both sides
  GDPR erasure        ALTER TABLE ... DELETE on the source. An MV is an INSERT
                      trigger and never sees a mutation, so the aggregate kept
                      the deleted revenue: it would have reported 801 571 002
                      against a true 761 580 554 — 39 990 448 overstated,
                      5.251%, with NOTHING stale. Staleness at the moment of
                      the decision: 1 second.
  guarded router      base_table (state_not_serving)
  counterfactual      a staleness-only router SERVES it — measured, not assumed
  after rebuild       match, and routing returns to the materialization

Drift detection treats a MISSING bucket as drift even when every value present
is correct (late-arriving rows are the sneakiest failure), and an empty
comparison as insufficient_data rather than match — the same false green
BI-P025 found in a quality check over an empty table.

The measured speed-up is reported as a distribution, not a figure: p50 3.4x,
but the run-to-run range spans 0.6x-7.5x on one idle host, so quoting "3.4x"
as a property of the system would be misleading (BI-P104 doctrine).

19 unit tests + 4 against live ClickHouse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 03:56:32 +02:00

76 lines
2.3 KiB
JSON

{
"clickhouse_version": "26.3.10.60",
"recorded_at": "2026-09-07T01:55:42.821Z",
"source_rows": 2000000,
"after_backfill": {
"verdict": "match",
"buckets_compared": 48,
"revenue_total": {
"source": 801571002,
"aggregate": 801571002,
"delta": 0
}
},
"performance": {
"note": "ClickHouse caches marks and pages between repetitions, so these are WARM numbers on one host with nothing else running. They describe this shape of query on this data, not a system property.",
"repetitions": 9,
"base_table_ms": {
"n": 9,
"min": 48.70366200000012,
"p50": 76.67811500000016,
"p95": 115.04416399999991,
"max": 115.04416399999991,
"spread_ratio": 2.36
},
"pre_aggregate_ms": {
"n": 9,
"min": 15.281603999999788,
"p50": 22.727642999999944,
"p95": 84.41279499999973,
"max": 84.41279499999973,
"spread_ratio": 5.52
},
"speedup_p50": 3.4,
"speedup_range_min_max": [0.6, 7.5]
},
"gdpr_erasure_drill": {
"mutation": "ALTER TABLE sales DELETE WHERE tenant_id='t_alpha' AND customer_id < 250",
"verdict": "drifted",
"buckets_compared": 48,
"revenue_true_after_erasure": 761580554,
"revenue_pre_aggregate_would_report": 801571002,
"revenue_overstatement": 39990448,
"revenue_overstatement_pct": 5.251,
"worst_bucket": {
"key": "2026-08-28|FI",
"metric": "revenue",
"source_value": 15830779,
"aggregate_value": 16699145,
"absolute_delta": 868366,
"relative_delta": 0.05485301765630106
},
"guarded_router_decision": {
"source": "base_table",
"refusal": "state_not_serving",
"detail": "materialization state is \"drifted\""
},
"staleness_only_router_would_serve": true,
"staleness_seconds_at_decision": 1,
"note": "The aggregate was NOT stale by any clock: it had just been refreshed, and every freshness check passed. Only the source comparison caught it. This is why staleness alone is not a correctness guarantee."
},
"after_rebuild": {
"verdict": "match",
"revenue_total": {
"source": 761580554,
"aggregate": 761580554,
"delta": 0
},
"routing": {
"source": "materialization",
"materialization_id": "mv_sales_daily",
"target_ref": "opt_p105_mtql9p2f.sales_daily",
"staleness_seconds": 0
}
}
}