perf(nats): Implement bulk cache prewarming for runners to optimize startup performance

This commit is contained in:
2026-02-20 19:40:02 +01:00
parent d3e0206a3c
commit 024e647295
3 changed files with 86 additions and 11 deletions

View File

@@ -126,3 +126,65 @@ export async function warmRunner(runnerId: number): Promise<RunnerKVEntry> {
await setRunnerEntry(runnerId, entry);
return entry;
}
/**
* Bulk cache prewarming: loads all runners from the database and populates the KV cache.
* Uses 3 efficient queries and parallel KV writes to minimize startup time.
*
* Call from loader during startup (if NATS_PREWARM=true) to eliminate DB reads on the hot
* path from the very first scan.
*/
export async function warmAll(): Promise<void> {
const connection = getConnection();
// Query 1: All runners
const runners = await connection
.getRepository(Runner)
.createQueryBuilder('runner')
.select(['runner.id', 'runner.firstname', 'runner.lastname'])
.getMany();
// Query 2: Total valid distance per runner
const distanceResults = await connection
.getRepository(TrackScan)
.createQueryBuilder('scan')
.select('scan.runner', 'runnerId')
.addSelect('COALESCE(SUM(track.distance), 0)', 'total')
.innerJoin('scan.track', 'track')
.where('scan.valid = :valid', { valid: true })
.groupBy('scan.runner')
.getRawMany();
// Query 3: Latest valid scan timestamp per runner
const latestResults = await connection
.getRepository(TrackScan)
.createQueryBuilder('scan')
.select('scan.runner', 'runnerId')
.addSelect('MAX(scan.timestamp)', 'latestTimestamp')
.where('scan.valid = :valid', { valid: true })
.groupBy('scan.runner')
.getRawMany();
// Build lookup maps
const distanceMap = new Map<number, number>();
distanceResults.forEach((row: any) => {
distanceMap.set(parseInt(row.runnerId, 10), parseInt(row.total, 10));
});
const latestMap = new Map<number, number>();
latestResults.forEach((row: any) => {
latestMap.set(parseInt(row.runnerId, 10), parseInt(row.latestTimestamp, 10));
});
// Write all entries in parallel
const writePromises = runners.map((runner) => {
const entry: RunnerKVEntry = {
displayName: `${runner.firstname} ${runner.lastname}`,
totalDistance: distanceMap.get(runner.id) ?? 0,
latestTimestamp: latestMap.get(runner.id) ?? 0,
};
return setRunnerEntry(runner.id, entry);
});
await Promise.all(writePromises);
}