fix(rollups): boot-backfill discovery SQL uses bucket_start as NULL sentinel

v1.4.38.5 referenced `r."id" IS NULL` on the LEFT JOIN — but
`measurement_rollups` has a composite primary key
`(user_id, type, granularity, bucket_start)` and no surrogate `id`
column. Postgres rejected the query with `column r.id does not
exist` (SQL state 42703); the worker boot swallowed the error per
the helper's best-effort contract and the queue stayed empty.

Net effect: v1.4.38.5 deployed cleanly but delivered none of its
promised fast-path recovery. Marc's account stayed on the live
aggregator.

The fix uses `r."bucket_start" IS NULL` — any column from the
unmatched right side of the LEFT JOIN serves the "no matching row"
filter, and `bucket_start` is part of the composite PK so the
planner already has the column. Bumps to v1.4.38.6.
This commit is contained in:
Marc-André Bombeck committed 2026-05-18 06:15:56 +02:00
1 parent 9c13cba3f7
commit 80b1fcb8d0
3 files changed
+36 -6

No files matched your search

+6 -5
View File
@@ -736,10 +736,11 @@ export async function enqueueBootTimeRollupBackfill(): Promise<{
// planner uses the `(user_id, type, measured_at)` index path; the
// LEFT JOIN onto `measurement_rollups` uses the
// `(user_id, type, granularity, bucket_start)` composite primary
// key. `r."id" IS NULL` filters the unmatched partitions. A user
// surfaces here when ANY type is missing — including the brand-
// new-type case that v1.4.35.1's zero-rollup-only shape silently
// stranded.
// key. `measurement_rollups` has no surrogate `id` column —
// `r."bucket_start" IS NULL` (any column from the right side of
// the LEFT JOIN) marks the unmatched partitions. A user surfaces
// here when ANY type is missing — including the brand-new-type
// case that v1.4.35.1's zero-rollup-only shape silently stranded.
const users = await prisma.$queryRaw<Array<{ id: string }>>`
SELECT DISTINCT mt."user_id" AS id
FROM (
@@ -750,7 +751,7 @@ export async function enqueueBootTimeRollupBackfill(): Promise<{
ON r."user_id" = mt."user_id"
AND r."type" = mt."type"
AND r."granularity" = 'DAY'
WHERE r."id" IS NULL
WHERE r."bucket_start" IS NULL
`;
if (users.length === 0) {