feat(mood-rollups): boot-time backfill queue for legacy accounts

Registers mood-rollup-recompute (per-bucket WEEK/MONTH/YEAR fold)
and mood-rollup-full-backfill (boot discovery) onto pg-boss. The
worker boot now fires a discovery query that enqueues one full-fold
job per user with mood entries but zero mood_entry_rollups rows —
idempotent across reboots and singleton-keyed so a fast restart
while a backfill is queued doesn't double up. Legacy accounts
pre-dating the v1.4.39 deploy auto-converge on the persistent tier
without operator action.
This commit is contained in:
Marc-André Bombeck committed 2026-05-18 12:25:45 +02:00
1 parent 1c6264dfd3
commit d15850d526
1 file changed
+100
+100
View File
@@ -67,6 +67,16 @@ import {
type RollupFullBackfillPayload,
type RollupRecomputePayload,
} from "@/lib/measurements/rollups";
import {
MOOD_ROLLUP_FULL_BACKFILL_QUEUE,
MOOD_ROLLUP_FULL_BACKFILL_CONCURRENCY,
MOOD_ROLLUP_RECOMPUTE_QUEUE,
MOOD_ROLLUP_RECOMPUTE_CONCURRENCY,
enqueueBootTimeMoodRollupBackfill,
recomputeUserMoodRollups,
type MoodRollupFullBackfillPayload,
type MoodRollupRecomputePayload,
} from "@/lib/mood/rollups";
import {
drainPerSampleCumulative,
DRAIN_CUMULATIVE_CUTOFF_HOURS,
@@ -1655,6 +1665,17 @@ export async function startReminderWorker() {
APPLE_HEALTH_IMPORT_QUEUE,
ROLLUP_RECOMPUTE_QUEUE,
ROLLUP_FULL_BACKFILL_QUEUE,
// v1.4.39 W-MOOD — per-bucket WEEK/MONTH/YEAR fold queue for the
// mood rollup tier. The DAY pass runs inline in the write hook;
// these enqueue paths cover the cross-granularity buckets the
// worker materialises off the request path.
MOOD_ROLLUP_RECOMPUTE_QUEUE,
// v1.4.39 W-MOOD — boot-time fold queue for the mood rollup tier.
// Mirrors the measurement-rollup full-backfill semantics: the
// discovery query enqueues one full-fold per user with mood
// entries but no rollup rows; the user drops off the list once
// the fold completes.
MOOD_ROLLUP_FULL_BACKFILL_QUEUE,
// v1.4.37 W7c — explicit createQueue is required before the
// nightly schedule below registers (pg-boss v12 contract). Without
// this entry the drain schedule silently no-ops and the
@@ -1916,6 +1937,58 @@ export async function startReminderWorker() {
},
);
// v1.4.39 W-MOOD — mood-rollup per-bucket worker. Folds the
// WEEK / MONTH / YEAR buckets that the mood-entry write hooks
// enqueue; the DAY bucket runs synchronously in the hook itself.
// No current read path consumes these buckets — they exist so a
// future cross-granularity reader can ship without a backfill
// step. Concurrency-2 mirrors the measurement-rollup worker.
await boss.work<MoodRollupRecomputePayload>(
MOOD_ROLLUP_RECOMPUTE_QUEUE,
{ localConcurrency: MOOD_ROLLUP_RECOMPUTE_CONCURRENCY },
async (jobs) => {
for (const job of jobs) {
const payload = job.data;
await recomputeUserMoodRollups(payload.userId, {
granularities: [payload.granularity],
from: new Date(payload.from),
to: new Date(payload.to),
});
}
},
);
// v1.4.39 W-MOOD — mood-rollup boot-time fold worker. The boot
// discovery helper below sends one job per user with mood entries
// but zero rollup rows; this handler folds the full 5-year window
// across every granularity. Concurrency-1 so the populator never
// crowds the request pool.
await boss.work<MoodRollupFullBackfillPayload>(
MOOD_ROLLUP_FULL_BACKFILL_QUEUE,
{ localConcurrency: MOOD_ROLLUP_FULL_BACKFILL_CONCURRENCY },
async (jobs) => {
for (const job of jobs) {
const { userId } = job.data;
try {
const { rowsUpserted, durationMs } =
await recomputeUserMoodRollups(userId);
workerLog(
"info",
`[mood-rollup-full-backfill] user=${userId} rows=${rowsUpserted} duration=${durationMs}ms`,
);
} catch (err) {
recordError();
workerLog(
"error",
`[mood-rollup-full-backfill] user=${userId} failed`,
err,
);
throw err;
}
}
},
);
// v1.4.37 W7c — nightly drain worker. Walks every user × cumulative
// type and folds per-sample APPLE_HEALTH rows older than the cutoff
// into one `stats:…` row per calendar day. Idempotent — a second run
@@ -1980,6 +2053,33 @@ export async function startReminderWorker() {
);
}
// v1.4.39 W-MOOD — fire-and-forget boot discovery for the mood
// rollup tier. Mirrors the v1.4.35.1 measurement-rollup pattern:
// one job per user with mood entries but no rollup coverage.
// Idempotent across reboots and singleton-keyed inside pg-boss so
// a fast restart while a backfill is queued doesn't double up.
try {
const { enqueued, skipped, error } =
await enqueueBootTimeMoodRollupBackfill();
if (error) {
workerLog(
"error",
`[mood-rollup-full-backfill] boot discovery failed: ${error}`,
);
} else {
workerLog(
"info",
`[mood-rollup-full-backfill] boot discovery: enqueued=${enqueued} skipped=${skipped}`,
);
}
} catch (err) {
workerLog(
"error",
"[mood-rollup-full-backfill] boot discovery threw an unexpected error",
err,
);
}
return boss;
}