Skip to content

Commit 5f06709

Browse files
committed
fix(mollifier): degrade to disabled when redis host is unset, no main-redis fallback
Two operational guards for misconfigured rollouts: 1. Drop the MOLLIFIER_REDIS_* fallback to the main REDIS_* cluster. The mollifier writes to a dedicated Redis to keep burst traffic off the engine's primary queue — silently colocating with the main Redis when MOLLIFIER_REDIS_HOST is unset defeats the design. 2. Degrade gracefully instead of crashing the pod. If MOLLIFIER_ENABLED was flipped on without setting MOLLIFIER_REDIS_HOST, the buffer returns null (with a one-shot warn log per process) and the drainer no-ops. No crash loops, no failed deploys, no traffic impact — operators see the warn line and fix the misconfig in a follow-up deploy. The drainer's previously-unreachable "env vars inconsistent" throw becomes reachable in this degraded mode; replace it with a null return so worker.server.ts's existing null check short-circuits cleanly.
1 parent 7d74b8a commit 5f06709

3 files changed

Lines changed: 35 additions & 22 deletions

File tree

apps/webapp/app/env.server.ts

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,25 +1032,16 @@ const EnvironmentSchema = z
10321032

10331033
MOLLIFIER_ENABLED: z.string().default("0"),
10341034
MOLLIFIER_SHADOW_MODE: z.string().default("0"),
1035-
MOLLIFIER_REDIS_HOST: z
1036-
.string()
1037-
.optional()
1038-
.transform((v) => v ?? process.env.REDIS_HOST),
1039-
MOLLIFIER_REDIS_PORT: z.coerce
1040-
.number()
1041-
.optional()
1042-
.transform(
1043-
(v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined),
1044-
),
1045-
MOLLIFIER_REDIS_USERNAME: z
1046-
.string()
1047-
.optional()
1048-
.transform((v) => v ?? process.env.REDIS_USERNAME),
1049-
MOLLIFIER_REDIS_PASSWORD: z
1050-
.string()
1051-
.optional()
1052-
.transform((v) => v ?? process.env.REDIS_PASSWORD),
1053-
MOLLIFIER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"),
1035+
// No fallback to the main `REDIS_*` cluster. The mollifier writes to a
1036+
// dedicated Redis to keep burst traffic off the engine's primary queue.
1037+
// If `MOLLIFIER_ENABLED=1` but `MOLLIFIER_REDIS_HOST` is unset, the
1038+
// buffer degrades to disabled (with a warn log) rather than silently
1039+
// colocating with the main Redis. See `mollifierBuffer.server.ts`.
1040+
MOLLIFIER_REDIS_HOST: z.string().optional(),
1041+
MOLLIFIER_REDIS_PORT: z.coerce.number().optional(),
1042+
MOLLIFIER_REDIS_USERNAME: z.string().optional(),
1043+
MOLLIFIER_REDIS_PASSWORD: z.string().optional(),
1044+
MOLLIFIER_REDIS_TLS_DISABLED: z.string().default("false"),
10541045
MOLLIFIER_TRIP_WINDOW_MS: z.coerce.number().int().positive().default(200),
10551046
MOLLIFIER_TRIP_THRESHOLD: z.coerce.number().int().positive().default(100),
10561047
MOLLIFIER_HOLD_MS: z.coerce.number().int().positive().default(500),

apps/webapp/app/v3/mollifier/mollifierBuffer.server.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,25 @@ function initializeMollifierBuffer(): MollifierBuffer {
2626
});
2727
}
2828

29+
// Latch so we log the degraded-config warning exactly once per process
30+
// instead of on every `getMollifierBuffer()` call (which is per-trigger).
31+
let degradedConfigLogged = false;
32+
2933
export function getMollifierBuffer(): MollifierBuffer | null {
3034
if (env.MOLLIFIER_ENABLED !== "1") return null;
35+
// Fail safe, not loud: if MOLLIFIER_ENABLED was flipped on without
36+
// setting `MOLLIFIER_REDIS_HOST`, degrade the mollifier to disabled
37+
// rather than crash-looping the pod (or — worse — sharing the main
38+
// engine Redis). One warn log per process is enough for operators to
39+
// spot the misconfig without drowning logs in repeats.
40+
if (!env.MOLLIFIER_REDIS_HOST) {
41+
if (!degradedConfigLogged) {
42+
logger.warn(
43+
"mollifier.degraded_config: MOLLIFIER_ENABLED=1 but MOLLIFIER_REDIS_HOST is unset — treating as disabled until configured",
44+
);
45+
degradedConfigLogged = true;
46+
}
47+
return null;
48+
}
3149
return singleton("mollifierBuffer", initializeMollifierBuffer);
3250
}

apps/webapp/app/v3/mollifier/mollifierDrainer.server.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import { singleton } from "~/utils/singleton";
66
import { getMollifierBuffer } from "./mollifierBuffer.server";
77
import type { BufferedTriggerPayload } from "./bufferedTriggerPayload.server";
88

9-
function initializeMollifierDrainer(): MollifierDrainer<BufferedTriggerPayload> {
9+
function initializeMollifierDrainer(): MollifierDrainer<BufferedTriggerPayload> | null {
1010
const buffer = getMollifierBuffer();
1111
if (!buffer) {
12-
// Should be unreachable: getMollifierDrainer() guards on the same env flag as getMollifierBuffer().
13-
throw new Error("MollifierDrainer initialised without a buffer — env vars inconsistent");
12+
// Buffer degraded to disabled (e.g. MOLLIFIER_ENABLED=1 but
13+
// MOLLIFIER_REDIS_HOST unset). Don't crash the pod — return null and
14+
// let the worker shutdown registration short-circuit. The degraded
15+
// config is logged once by `getMollifierBuffer()`; we don't double
16+
// log here.
17+
return null;
1418
}
1519

1620
logger.debug("Initializing mollifier drainer", {

0 commit comments

Comments
 (0)