mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
Cap and page the unbounded reads that let a multi-year tenant pull its whole measurement history into memory on hot paths. - extractFeatures: read newest-first with a 6000-row cap (mirrors the Coach snapshot) and reverse to ascending, so a 365-400 day window no longer materialises hundreds of thousands of PULSE / glucose rows per Coach turn and nightly briefing. Full-history extremes still come from readAllTimeExtremes, so the cap never relabels an extreme. - Add a (userId, measuredAt) index on measurements (migration 0210) for the type-less range scans that extractFeatures and the consolidation passes drive; the existing (userId, type, measuredAt) cannot serve them as a prefix. - runConsolidation: keyset-paginate the per-(user, type) source scan on (measuredAt, id) instead of one unbounded findMany, removing the giant single result-set allocation that drove the boot storm. Output is identical; covered by a paging unit test. - Stagger the boot-time backfill enqueues with increasing startAfter offsets so the heavy full-history loads no longer all drain in parallel onto one tenant at startup. Non-boot callers keep their immediate send. - Backups: replace load-all-then-stringify with paged narrow-select reads for the weekly and off-host writers, bounding transient heap. Off-host still includes soft-deleted rows. Covered by a serialize/restore round-trip test. - Drop the per-request auth.bearer.success audit insert; the wide event already records auth_method and user_id, so the polling client no longer drives a write + pool checkout on every request. Failure-path audit stays. - Coach conversation detail: cap the message include to the newest 200 so a long thread no longer decrypts every message on each open; response shape unchanged. - Admin audit-log: floor the scan at the retention horizon so the listing and its count stop sweeping open-ended over the substring filters.
31 lines
1.7 KiB
SQL
31 lines
1.7 KiB
SQL
-- v1.25 — type-less `(user_id, measured_at)` supporting index on `measurements`.
|
|
--
|
|
-- The bulk feature read (`extractFeatures`, src/lib/insights/features.ts) and
|
|
-- the consolidation scans walk a user's measurements ordered by `measured_at`
|
|
-- with no `type` predicate. The existing composite indexes cover
|
|
-- `(user_id, type, measured_at)`, `(user_id, source, measured_at)`, and
|
|
-- `(user_id, updated_at, id)` — none serves a type-less `(user_id, measured_at)`
|
|
-- range scan as a prefix. Without this index Postgres filters by `user_id` then
|
|
-- sorts the user's entire measurement set in memory on each of those reads,
|
|
-- which on a multi-year HealthKit tenant is hundreds of thousands of rows.
|
|
--
|
|
-- The composite `(user_id, measured_at)` index lets those reads stream straight
|
|
-- off the index in time order, which the newest-first row caps then bound.
|
|
--
|
|
-- Additive + order-safe: a pure `CREATE INDEX IF NOT EXISTS`, no column add, no
|
|
-- backfill, no rewrite of `measurements`.
|
|
--
|
|
-- Lock note: this is a plain, non-`CONCURRENTLY` index build, so it runs inside
|
|
-- Prisma's migration transaction and holds an ACCESS EXCLUSIVE lock on
|
|
-- `measurements` for the build's duration. The index covers every row (no
|
|
-- partial predicate), so on the largest multi-year HealthKit tenants the build
|
|
-- is slow; run the deploy at a low-traffic window on such deployments. A
|
|
-- `CONCURRENTLY` build cannot run inside the migration transaction; a plain
|
|
-- build is acceptable at this table size in the maintenance window.
|
|
--
|
|
-- Reversibility: down migration is
|
|
-- DROP INDEX IF EXISTS "measurements_user_id_measured_at_idx";
|
|
|
|
CREATE INDEX IF NOT EXISTS "measurements_user_id_measured_at_idx"
|
|
ON "measurements" ("user_id", "measured_at");
|