Foundation release for the persistent measurement_rollups cache tier. Adds a new additive table, populates it from every write path, and switches two reader surfaces onto it for the linearly-composable stats.
Highlights
- New
measurement_rollupstable keyed on(user_id, type, granularity, bucket_start)carryingcount / mean / min_value / max_value / sd / slope / r2 / computed_at. Additive only — migration0067_v1434_measurement_rollupsis idempotent under re-run. - Write hooks fold the DAY bucket synchronously on every measurement create / update / delete; WEEK / MONTH / YEAR follow on a
pg-bossqueue (rollup-recompute, concurrency = 2, singleton-keyed per bucket so burst writes coalesce). - Apple Health import worker calls
recomputeUserRollupsonce at completion instead of per-row, scoped to the import's date range — a 100k-sample backfill no longer spawns 100k write hooks. - Reader surfaces (
comprehensive-aggregator,summaries-slice) sourcecount / min / max / meanper type from the DAY buckets viaaggregateBuckets, plus the comprehensivedailyByTypecorrelation feed straight from the bucket means. Slope / R² / standard deviation / anomaly counts continue to run against live SQL — they don't compose across DAY buckets. - Defensive parity check: the composed
countis compared against the live aggregate'sCOUNT(*); on divergence the reader transparently falls back to live SQL. Covers cold-mount edge cases and the all-time-window degradation on accounts that never ran the backfill. scripts/backfill-rollups.tsfor the one-shot historical fold (--user <id>or every account). Single-user serial so the Prisma pool stays out of contention. Idempotent.
Tests
- 3 new integration cases in
tests/integration/measurement-rollups.test.tspin the parity contract end-to-end against a real Postgres testcontainer: the aggregator'scount / min / max / meanmatches live SQL byte-for-byte,dailyByTypematches a paralleldate_trunc('day', ...)GROUP BY query, and a freshly-written measurement is reflected on the next read. - Unit suite: 4249 → 4280. Integration suite: 222 → 228.
pnpm typecheck+pnpm lintclean.
Operator notes
- Coolify auto-pulls via the v1.4.34.2
pull_policy: alwaysdirective — no manual retag needed. - Run
pnpm tsx scripts/backfill-rollups.tsonce post-deploy to fold the trailing history into all four granularities. Idempotent under re-run.
A full read-swap across every analytics surface is planned for a follow-up release once the partial swap has proven itself in production.