Files
HealthLog/tests/integration/rollup-coverage-probe.test.ts
T
Marc-André Bombeck 4ab2a8be38 perf(rollups): probe coverage with a loose index scan
probeRollupCoverage runs ahead of the dashboard snapshot, the analytics
slices and the comprehensive insights. It read every live row of the
account for SELECT DISTINCT type and counted every DAY bucket per type,
which the planner served with a sequential scan of measurement_rollups.
Walk the distinct types with a recursive CTE along the live index and
test coverage with EXISTS. On a copy of a long-lived account the probe
drops from 24-50 ms to under 2 ms and returns the same map; an
integration test keeps the old query as the oracle.
2026-10-04 15:29:02 +02:00

129 lines
4.3 KiB
TypeScript

/**
* The rollup coverage probe reads the distinct live types as a loose index
* scan and coverage as a per-type `EXISTS`. The previous shape (a DISTINCT
* over every live row joined to a COUNT over every DAY bucket) is kept here
* as the oracle: both must return the same map on the same fixture, including
* the edges that decide the read-swap — tombstoned types, types with only
* coarser buckets, another account's buckets, and an account with nothing.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import { getPrismaClient, truncateAllTables } from "./setup";
import { probeRollupCoverage } from "@/lib/rollups/measurement-coverage";
import type { MeasurementType } from "@/generated/prisma/client";
vi.mock("@/lib/db-compat", () => ({
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
}));
vi.mock("@/lib/jobs/boss-instance", () => ({
getGlobalBoss: vi.fn(() => null),
}));
const USER = "user-coverage-probe";
const OTHER = "user-coverage-probe-other";
const EMPTY = "user-coverage-probe-empty";
async function oracle(userId: string): Promise<Map<string, boolean>> {
const rows = await getPrismaClient().$queryRaw<
Array<{ type: string; has_buckets: boolean }>
>`
SELECT m."type"::text AS type, COUNT(r.*) > 0 AS has_buckets
FROM (
SELECT DISTINCT "type" FROM measurements
WHERE user_id = ${userId} AND "deleted_at" IS NULL
) m
LEFT JOIN measurement_rollups r
ON r.user_id = ${userId} AND r."type" = m."type" AND r.granularity = 'DAY'
GROUP BY m."type"
`;
return new Map(rows.map((r) => [r.type, Boolean(r.has_buckets)]));
}
beforeEach(async () => {
const prisma = getPrismaClient();
await truncateAllTables(prisma);
for (const id of [USER, OTHER, EMPTY]) {
await prisma.user.create({
data: { id, username: id, email: `${id}@example.test`, timezone: "UTC" },
});
}
});
describe("probeRollupCoverage", () => {
it("matches the full-scan oracle across tombstones, granularities and accounts", async () => {
const prisma = getPrismaClient();
const day = 86_400_000;
const base = Date.UTC(2026, 0, 10);
const reading = (
userId: string,
type: MeasurementType,
i: number,
deleted = false,
) => ({
userId,
type,
value: 60 + i,
unit: "x",
measuredAt: new Date(base + i * day),
deletedAt: deleted ? new Date(base) : null,
});
await prisma.measurement.createMany({
data: [
// Live rows with DAY buckets → covered.
...[0, 1, 2].map((i) => reading(USER, "PULSE", i)),
// Live rows, no buckets at all → uncovered.
...[0, 1].map((i) => reading(USER, "WEIGHT", i)),
// Live rows with only a WEEK bucket → uncovered.
reading(USER, "BLOOD_PRESSURE_SYS", 0),
// Only tombstones → absent from the map, even with a DAY bucket.
...[0, 1].map((i) => reading(USER, "SLEEP_DURATION", i, true)),
// A mix of tombstoned and live rows → present.
reading(USER, "ACTIVITY_STEPS", 0, true),
reading(USER, "ACTIVITY_STEPS", 1),
// Another account's rows and buckets never leak in.
reading(OTHER, "WEIGHT", 0),
reading(OTHER, "OXYGEN_SATURATION", 0),
],
});
const bucket = (
userId: string,
type: MeasurementType,
granularity: "DAY" | "WEEK",
) => ({
userId,
type,
granularity,
bucketStart: new Date(base),
source: "MANUAL" as const,
count: 1,
mean: 1,
minValue: 1,
maxValue: 1,
});
await prisma.measurementRollup.createMany({
data: [
bucket(USER, "PULSE", "DAY"),
bucket(USER, "BLOOD_PRESSURE_SYS", "WEEK"),
bucket(USER, "SLEEP_DURATION", "DAY"),
bucket(USER, "ACTIVITY_STEPS", "DAY"),
bucket(OTHER, "WEIGHT", "DAY"),
bucket(USER, "OXYGEN_SATURATION", "DAY"),
],
});
const coverage = await probeRollupCoverage(USER);
expect(Object.fromEntries(coverage)).toEqual({
ACTIVITY_STEPS: true,
BLOOD_PRESSURE_SYS: false,
PULSE: true,
WEIGHT: false,
});
expect(coverage).toEqual(await oracle(USER));
expect(await probeRollupCoverage(OTHER)).toEqual(await oracle(OTHER));
const empty = await probeRollupCoverage(EMPTY);
expect(empty.size).toBe(0);
expect(empty).toEqual(await oracle(EMPTY));
});
});