63 lines
2.3 KiB
TypeScript
63 lines
2.3 KiB
TypeScript
// In-memory cache of the enabled monitor list, keyed by region. The /internal/due
|
|
// endpoint is polled by every runner roughly once a second per region; without
|
|
// this cache each poll re-runs a 500-row scan against `monitors` with an array
|
|
// predicate, which dominates the api's Postgres traffic at any real fleet size.
|
|
//
|
|
// The list almost never changes between polls — monitor create/edit/delete is at
|
|
// most a few times per hour. So we memoize per-region with a short TTL and bust
|
|
// the cache from the monitor mutation handlers so edits are visible instantly.
|
|
|
|
import sql from "../db";
|
|
|
|
const TTL_MS = 5000;
|
|
|
|
type MonitorRow = Record<string, any>;
|
|
type Entry = { rows: MonitorRow[]; expiresAt: number };
|
|
|
|
const cache = new Map<string, Entry>();
|
|
const inflight = new Map<string, Promise<MonitorRow[]>>();
|
|
|
|
async function fetchForRegion(region: string): Promise<MonitorRow[]> {
|
|
return sql<MonitorRow[]>`
|
|
SELECT id, url, method, request_headers, request_body, timeout_ms, interval_s, query, regions,
|
|
max_retries, retry_interval_s, created_at
|
|
FROM monitors
|
|
WHERE enabled = true
|
|
AND (
|
|
array_length(regions, 1) IS NULL
|
|
OR regions = '{}'
|
|
OR ${region} = ANY(regions)
|
|
)
|
|
LIMIT 500
|
|
`;
|
|
}
|
|
|
|
export async function getMonitorsForRegion(region: string): Promise<MonitorRow[]> {
|
|
const now = Date.now();
|
|
const hit = cache.get(region);
|
|
if (hit && hit.expiresAt > now) return hit.rows;
|
|
|
|
// Coalesce concurrent refreshes for the same region so a thundering herd of
|
|
// runner polls doesn't fan out into N parallel SELECTs against an expired
|
|
// entry.
|
|
let pending = inflight.get(region);
|
|
if (!pending) {
|
|
pending = fetchForRegion(region)
|
|
.then((rows) => {
|
|
cache.set(region, { rows, expiresAt: Date.now() + TTL_MS });
|
|
return rows;
|
|
})
|
|
.finally(() => { inflight.delete(region); });
|
|
inflight.set(region, pending);
|
|
}
|
|
return pending;
|
|
}
|
|
|
|
// Called by monitor create/patch/delete/toggle handlers. Wipes the entire
|
|
// region map — fine because (a) entries are tiny, (b) refresh is cheap, and
|
|
// (c) we don't know which regions a freshly-edited monitor belongs to without
|
|
// reading it back. Simpler than per-region invalidation, identical net effect.
|
|
export function invalidateMonitorList(): void {
|
|
cache.clear();
|
|
}
|