diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index b0ef3e574..f0869d449 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -1055,6 +1055,17 @@ const EnvironmentSchema = z COMMON_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), MOLLIFIER_ENABLED: z.string().default("0"), + // Separate switch for the drainer (consumer side) so it can be split + // off onto a dedicated worker service. Unset → inherits + // MOLLIFIER_ENABLED, so single-container self-hosters don't have to + // flip two switches. In multi-replica deployments, set this to "0" + // explicitly on every replica except the one dedicated drainer + // service — otherwise every replica's polling loop races for the + // same buffer entries. `MOLLIFIER_ENABLED` is still the master kill + // switch; setting this to "1" while `MOLLIFIER_ENABLED` is "0" is a + // no-op because the gate-side singleton refuses to construct a + // buffer when the system is off. + MOLLIFIER_DRAINER_ENABLED: z.string().default(process.env.MOLLIFIER_ENABLED ?? "0"), MOLLIFIER_SHADOW_MODE: z.string().default("0"), MOLLIFIER_REDIS_HOST: z .string() diff --git a/apps/webapp/app/v3/mollifierDrainerWorker.server.ts b/apps/webapp/app/v3/mollifierDrainerWorker.server.ts index 639c9bb5d..e2e58e820 100644 --- a/apps/webapp/app/v3/mollifierDrainerWorker.server.ts +++ b/apps/webapp/app/v3/mollifierDrainerWorker.server.ts @@ -29,15 +29,18 @@ declare global { * `batchTriggerWorker`). * * Gating order: - * - `WORKER_ENABLED !== "true"` → early return (API-only replicas - * still produce into the buffer via the trigger hot path; only worker - * replicas drain it, otherwise every replica races for the same - * entries). + * - `MOLLIFIER_DRAINER_ENABLED !== "1"` → early return. Unset defaults + * to `MOLLIFIER_ENABLED`, so single-container self-hosters still get + * the drainer for free with one flag. In multi-replica deployments, + * set this to "0" explicitly on every replica except the dedicated + * drainer service so the polling loop doesn't race across replicas. * - `MOLLIFIER_ENABLED !== "1"` → `getMollifierDrainer()` returns null - * and the bootstrap is a no-op. + * and the bootstrap is a no-op. `MOLLIFIER_ENABLED` remains the + * master kill switch; the new flag only controls WHICH replicas + * run the drainer when the system is on. */ export function initMollifierDrainerWorker(): void { - if (env.WORKER_ENABLED !== "true") { + if (env.MOLLIFIER_DRAINER_ENABLED !== "1") { return; }