perf(insights): cache /api/insights/targets behind the analytics LRU

The targets endpoint runs >1 s cold — pulls 30 days of measurements
for every type plus medications, intakes, mood and glucose to build
the consistency strips, streaks and target-met-this-week rollup. It's
read on every Insights mount and on every sub-page click, so the
cumulative cost dominates a power user's session.

Wrap the body in the existing analytics-style server cache with a 60 s
TTL and a per-user key. The invalidation helpers for measurement,
mood and medication writes evict the user bucket so the next read
paints fresh data without the cache lagging the underlying state.

The bfcache-friendly Cache-Control header lets a back-forward
navigation restore from the BFCache instead of repaying the cost.
This commit is contained in:
Marc-André Bombeck committed 2026-05-17 12:36:27 +02:00
1 parent 23cf9a4a78
commit e203f07cbd
3 files changed
+52 -3

No files matched your search

+11
View File
@@ -31,6 +31,11 @@ export function invalidateUserMeasurements(userId: string): void {
caches.analytics.deleteByPrefix(`${userId}|`);
caches.achievements.deleteByPrefix(userId);
caches.workouts.deleteByPrefix(`${userId}|`);
// v1.4.36 W1 — measurement writes change the per-target consistency
// strips, the in-range rates and the streak counters that
// `/api/insights/targets` computes. Evict the user's bucket so the
// next mount paints fresh data.
caches.insightsTargets.deleteByPrefix(userId);
}
/**
@@ -44,6 +49,9 @@ export function invalidateUserMood(userId: string): void {
caches.moodAnalytics.deleteByPrefix(userId);
caches.achievements.deleteByPrefix(userId);
caches.analytics.deleteByPrefix(`${userId}|`);
// v1.4.36 W1 — mood writes change the mood target rows on the
// insights/targets response (MOOD_SCORE, MOOD_STABILITY).
caches.insightsTargets.deleteByPrefix(userId);
}
/**
@@ -56,6 +64,9 @@ export function invalidateUserMedications(userId: string): void {
caches.medications.deleteByPrefix(userId);
caches.medicationsIntake.deleteByPrefix(`${userId}|`);
caches.achievements.deleteByPrefix(userId);
// v1.4.36 W1 — medication writes change the MEDICATION_COMPLIANCE
// target rollup (compliance7 / compliance30 / consistency strip).
caches.insightsTargets.deleteByPrefix(userId);
}
/**
+13
View File
@@ -247,6 +247,19 @@ export const caches = {
maxEntries: 1000,
ttlMs: 60_000,
}),
/**
* v1.4.36 W1 — `/api/insights/targets` is read on every Insights
* mount and runs >1 s cold (it pulls 30-day measurements for every
* type + medications + intakes + mood + glucose). 60 s TTL matches
* the analytics cache so multiple sub-page mounts inside a minute
* all hit a warm cache. Invalidated alongside the analytics bucket
* on measurement / mood / medication writes.
*/
insightsTargets: new ServerCache<unknown>({
name: "insightsTargets",
maxEntries: 1000,
ttlMs: 60_000,
}),
} as const;
/**