Skip to content

Commit 1699582

Browse files
committed
feat(webapp): debounce LLM pricing registry reloads
Coalesce reload calls from the pub/sub subscriber so a burst of publishes only triggers one reload. The first publish in a window schedules a reload at T+LLM_PRICING_RELOAD_DEBOUNCE_MS (default 1s); subsequent publishes during that window are no-ops because the trailing reload will pick up everything when it queries the DB. Bounds reload rate to at most 1 per debounce window regardless of publisher chattiness, so a runaway upstream publisher can't fan out into a flood of full-table-scan reloads across every webapp pod.
1 parent e768e53 commit 1699582

2 files changed

Lines changed: 25 additions & 5 deletions

File tree

apps/webapp/app/env.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,7 @@ const EnvironmentSchema = z
14251425
LLM_COST_TRACKING_ENABLED: BoolEnv.default(true),
14261426
LLM_PRICING_RELOAD_INTERVAL_MS: z.coerce.number().int().default(5 * 60 * 1000), // 5 minutes
14271427
LLM_PRICING_RELOAD_CHANNEL: z.string().default("llm-registry:reload"),
1428+
LLM_PRICING_RELOAD_DEBOUNCE_MS: z.coerce.number().int().default(1000),
14281429
LLM_PRICING_SEED_ON_STARTUP: BoolEnv.default(false),
14291430
LLM_PRICING_READY_TIMEOUT_MS: z.coerce.number().int().default(500),
14301431
LLM_METRICS_BATCH_SIZE: z.coerce.number().int().default(5000),

apps/webapp/app/v3/llmPricingRegistry.server.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,40 @@ export const llmPricingRegistry = singleton("llmPricingRegistry", () => {
5757
});
5858
});
5959

60+
// Coalesce reload calls so a burst of publishes only triggers one reload.
61+
// A reload always fires within LLM_PRICING_RELOAD_DEBOUNCE_MS of the first
62+
// publish in a burst; subsequent publishes during that window are no-ops
63+
// because the trailing-edge reload will pick up everything when it queries
64+
// the DB. Bounds reload rate to at most 1 / debounce-window regardless of
65+
// how chatty the publisher is.
66+
const debounceMs = env.LLM_PRICING_RELOAD_DEBOUNCE_MS;
67+
let pendingReloadTimer: NodeJS.Timeout | null = null;
68+
69+
function scheduleReload() {
70+
if (pendingReloadTimer) return;
71+
pendingReloadTimer = setTimeout(() => {
72+
pendingReloadTimer = null;
73+
registry.reload().catch((err) => {
74+
logger.warn("Failed to reload LLM pricing registry from pub/sub", {
75+
error: err instanceof Error ? err.message : String(err),
76+
});
77+
});
78+
}, debounceMs);
79+
}
80+
6081
subscriber.on("message", (channel) => {
6182
if (channel !== env.LLM_PRICING_RELOAD_CHANNEL) return;
62-
registry.reload().catch((err) => {
63-
logger.warn("Failed to reload LLM pricing registry from pub/sub", {
64-
error: err instanceof Error ? err.message : String(err),
65-
});
66-
});
83+
scheduleReload();
6784
});
6885

6986
signalsEmitter.on("SIGTERM", () => {
7087
clearInterval(interval);
88+
if (pendingReloadTimer) clearTimeout(pendingReloadTimer);
7189
void subscriber.quit().catch(() => {});
7290
});
7391
signalsEmitter.on("SIGINT", () => {
7492
clearInterval(interval);
93+
if (pendingReloadTimer) clearTimeout(pendingReloadTimer);
7594
void subscriber.quit().catch(() => {});
7695
});
7796

0 commit comments

Comments
 (0)