Skip to content

Commit e768e53

Browse files
committed
feat(webapp): reload LLM pricing registry on Redis pub/sub
Subscribe to LLM_PRICING_RELOAD_CHANNEL on the worker Redis. Any process that publishes on the channel triggers an immediate reload of the in-memory model registry. The 5-minute periodic reload stays as a backstop. Lets pricing and model changes propagate to the live registry within seconds instead of up to 5 minutes.
1 parent a8966a4 commit e768e53

3 files changed

Lines changed: 41 additions & 1 deletion

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: improvement
4+
---
5+
6+
The LLM pricing registry now reloads from the database whenever a publish lands on `LLM_PRICING_RELOAD_CHANNEL` on the worker Redis, instead of waiting for the next 5-minute interval. LLM model and pricing changes reflect in cost enrichment within seconds.

apps/webapp/app/env.server.ts

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

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { ModelPricingRegistry, seedLlmPricing } from "@internal/llm-model-catalog";
22
import { prisma, $replica } from "~/db.server";
33
import { env } from "~/env.server";
4+
import { logger } from "~/services/logger.server";
45
import { signalsEmitter } from "~/services/signals.server";
6+
import { createRedisClient } from "~/redis.server";
57
import { singleton } from "~/utils/singleton";
68
import { setLlmPricingRegistry } from "./utils/enrichCreatableEvents.server";
79

@@ -27,19 +29,50 @@ export const llmPricingRegistry = singleton("llmPricingRegistry", () => {
2729
console.error("Failed to initialize LLM pricing registry", err);
2830
});
2931

30-
// Periodic reload
32+
// Periodic reload (backstop for the pub/sub path below)
3133
const reloadInterval = env.LLM_PRICING_RELOAD_INTERVAL_MS;
3234
const interval = setInterval(() => {
3335
registry.reload().catch((err) => {
3436
console.error("Failed to reload LLM pricing registry", err);
3537
});
3638
}, reloadInterval);
3739

40+
// Pub/sub reload — billing's LLM registry worker publishes on this channel
41+
// immediately after writing new/changed model rows, so all webapp pods see
42+
// updates within ~1s instead of waiting for the next interval tick.
43+
const subscriber = createRedisClient("llm-pricing:subscriber", {
44+
keyPrefix: "llm-pricing:subscriber:",
45+
host: env.COMMON_WORKER_REDIS_HOST,
46+
port: env.COMMON_WORKER_REDIS_PORT,
47+
username: env.COMMON_WORKER_REDIS_USERNAME,
48+
password: env.COMMON_WORKER_REDIS_PASSWORD,
49+
tlsDisabled: env.COMMON_WORKER_REDIS_TLS_DISABLED === "true",
50+
clusterMode: env.COMMON_WORKER_REDIS_CLUSTER_MODE_ENABLED === "1",
51+
});
52+
53+
subscriber.subscribe(env.LLM_PRICING_RELOAD_CHANNEL).catch((err) => {
54+
logger.warn("Failed to subscribe to LLM pricing reload channel", {
55+
channel: env.LLM_PRICING_RELOAD_CHANNEL,
56+
error: err instanceof Error ? err.message : String(err),
57+
});
58+
});
59+
60+
subscriber.on("message", (channel) => {
61+
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+
});
67+
});
68+
3869
signalsEmitter.on("SIGTERM", () => {
3970
clearInterval(interval);
71+
void subscriber.quit().catch(() => {});
4072
});
4173
signalsEmitter.on("SIGINT", () => {
4274
clearInterval(interval);
75+
void subscriber.quit().catch(() => {});
4376
});
4477

4578
return registry;

0 commit comments

Comments
 (0)