mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
perf(analytics): bound 15-way Promise.all to 4 concurrent type-fetches
Wrap the per-type fan-out in `buildAnalyticsResponse` with a p-limit(4) gate. The empirical v1.4.39 cold-mount trace (`.planning/round-v1439-empirical-trace.md` §B1) showed thick /api/analytics holding ≥8 of the default-10 pg.Pool slots for 6.5 s on a power-user account, starving every other dashboard query. The cap keeps the same total work but holds ≤4 pool slots for this branch at any moment, leaving the remaining slots free for the parallel chart-tile reads. Thick analytics is already 60 s cache-wrapped so the small absolute wall-clock cost on a single cold mount is offset by the chart-tile burst paying ~5 s less of pool-wait time on every mount thereafter.
This commit is contained in:
1 parent
c9d5479b0d
commit
96b2ee4c1c
3 files changed
+53
No files matched your search
@@ -66,6 +66,7 @@
|
||||
"mmdb-lib": "^3.0.2",
|
||||
"next": "16.2.6",
|
||||
"next-themes": "^0.4.6",
|
||||
"p-limit": "^7.3.0",
|
||||
"pg-boss": "^12.18.2",
|
||||
"prisma": "^7.8.0",
|
||||
"radix-ui": "^1.4.3",
|
||||
|
||||
Generated
+17
@@ -65,6 +65,9 @@ importers:
|
||||
next-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||
p-limit:
|
||||
specifier: ^7.3.0
|
||||
version: 7.3.0
|
||||
pg-boss:
|
||||
specifier: ^12.18.2
|
||||
version: 12.18.2
|
||||
@@ -4808,6 +4811,10 @@ packages:
|
||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p-limit@7.3.0:
|
||||
resolution: {integrity: sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
p-locate@5.0.0:
|
||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -6060,6 +6067,10 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
yocto-queue@1.2.2:
|
||||
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
|
||||
engines: {node: '>=12.20'}
|
||||
|
||||
yocto-spinner@1.2.0:
|
||||
resolution: {integrity: sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw==}
|
||||
engines: {node: '>=18.19'}
|
||||
@@ -10991,6 +11002,10 @@ snapshots:
|
||||
dependencies:
|
||||
yocto-queue: 0.1.0
|
||||
|
||||
p-limit@7.3.0:
|
||||
dependencies:
|
||||
yocto-queue: 1.2.2
|
||||
|
||||
p-locate@5.0.0:
|
||||
dependencies:
|
||||
p-limit: 3.1.0
|
||||
@@ -12445,6 +12460,8 @@ snapshots:
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
yocto-queue@1.2.2: {}
|
||||
|
||||
yocto-spinner@1.2.0:
|
||||
dependencies:
|
||||
yoctocolors: 2.1.2
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import pLimit from "p-limit";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
@@ -27,6 +28,32 @@ import {
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* v1.4.40 W-POOL — bound the per-type analytics fan-out below to ≤ 4
|
||||
* concurrent Prisma round-trips.
|
||||
*
|
||||
* The v1.4.39 empirical cold-mount trace
|
||||
* (`.planning/round-v1439-empirical-trace.md` § B1) recorded thick
|
||||
* `/api/analytics` running 15× `fetchMeasurementSeriesChunked` in
|
||||
* parallel via a bare `Promise.all` (this file's `route.ts:261-269`
|
||||
* pre-fix), each pulling chunked findMany pages of 5 000 rows. The
|
||||
* combined fan-out held ≥ 8 of the default-10 `pg.Pool` slots for
|
||||
* 6.5 s on Marc's 347k-row tenant, starving every other Wave-B / C
|
||||
* dashboard query.
|
||||
*
|
||||
* Capping the concurrency to 4 keeps the same total work but holds
|
||||
* at most 4 pool slots for the analytics branch at any moment. With
|
||||
* the W-POOL ceiling raised to 20 (`src/lib/db.ts:getPoolMax`),
|
||||
* every other dashboard query has ≥ 16 free slots regardless of how
|
||||
* many types analytics is mid-walk over. The thick endpoint pays a
|
||||
* small (~10–15 %) absolute wall-clock cost on a single cold mount —
|
||||
* 15 chunks across 4 lanes = 4 batches instead of 1 — but is already
|
||||
* wrapped in the 60s `caches.analytics` LRU so the user-visible
|
||||
* impact is only the very first mount, in exchange for the chart-tile
|
||||
* burst paying ~5 s less of pool wait time on every mount thereafter.
|
||||
*/
|
||||
export const ANALYTICS_TYPE_FETCH_CONCURRENCY = 4;
|
||||
|
||||
/**
|
||||
* v1.4.33 C1 — pull `?slice=…` from either a NextRequest (the
|
||||
* route's production wrapper) or a plain `Request` (the integration
|
||||
@@ -258,8 +285,15 @@ async function buildAnalyticsResponse(user: AuthedUser) {
|
||||
const lastSeenByType: Record<string, { lastSeenAt: string } | null> = {};
|
||||
|
||||
let totalRowsReadForAggregate = 0;
|
||||
// v1.4.40 W-POOL — bounded-concurrency wrapper for the 15-way
|
||||
// per-type fan-out below. See `ANALYTICS_TYPE_FETCH_CONCURRENCY`
|
||||
// above + `.planning/round-v1439-empirical-trace.md` § B1 for the
|
||||
// pool-saturation root cause. Same total work, ≤ 4 pool slots held
|
||||
// by this branch at any moment.
|
||||
const typeFetchLimit = pLimit(ANALYTICS_TYPE_FETCH_CONCURRENCY);
|
||||
const measurementsByType = await Promise.all(
|
||||
types.map((type) =>
|
||||
typeFetchLimit(() =>
|
||||
fetchMeasurementSeriesChunked(user.id, type, {
|
||||
includeSleepStage: true,
|
||||
// v1.4.39 W-SINCE — trailing 425-day floor for the live read.
|
||||
@@ -348,6 +382,7 @@ async function buildAnalyticsResponse(user: AuthedUser) {
|
||||
summary: summarize(datapoints),
|
||||
};
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user