mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
`/api/dashboard/summary` ran two unbounded `prisma.measurement.findMany`
queries on every cold mount: a 7-day window for latest+sparkline and a
365-day window for the streak day-keys. On accounts backfilled from
Apple Health those two reads dominated the wall-clock at ~4.6 s even
though the JS code only ever consumed one latest value + a ≤7-point
sparkline + a set of day-keys per metric.
Replace the unbounded reads with three bounded SQL aggregates:
- `DISTINCT ON (type)` over the 7-day window returns one row per
measurement type carrying the latest value + timestamp. Bounded by
the number of metric types (≤ ~10), not the raw row count.
- `measurement_rollups` DAY buckets for the trailing 7 days drive the
sparkline. Bounded by `SPARK_DAYS × N_metric_types`, typically
< 80 rows even for the heaviest power-user, and the bucket means
are a smoother trend signal for high-volume metrics than raw step
samples were.
- `SELECT DISTINCT to_char((measured_at AT TIME ZONE $userTz),
'YYYY-MM-DD')` over the 365-day window returns the activity-day
keys directly. At most 365 rows vs. potentially 100k raw samples.
The server-side `AT TIME ZONE` matches what `userDayKey` produces
in JS so the streak walk lands on byte-identical day-keys.
Wrap the whole response in the 60 s analytics LRU keyed
`${userId}|dashboard-summary`. Measurement / mood writes already evict
the analytics user-bucket via `invalidateUserMeasurements` and
`invalidateUserMood`; extend `invalidateUserMedications` to do the
same so a medication intake event flushes the dashboard cache the
next time iOS polls.
Per-sub-query wall-clock timings are emitted under
`meta.dashboard.sub_*_ms` so the next perf-verify can attribute any
future regression to a single sub-query without re-instrumenting the
route.
Estimated cold-mount: 4.6 s → ~500 ms (rollup-fresh cache miss) →
< 50 ms (warm cache hit). Subsequent cache hits skip the entire
builder.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>