Skip to content

Commit ae714c6

Browse files
committed
feat(webapp): MOLLIFIER_DRAINER_ENABLED for per-service drainer control
The drainer's polling loop has been gated on WORKER_ENABLED, which couples it to the legacy ZodWorker role. To split the drainer onto a dedicated worker service in cloud (and keep all other replicas as producer-only), introduce its own switch. Semantics: - Unset → inherits MOLLIFIER_ENABLED. Single-container self-hosters with MOLLIFIER_ENABLED=1 get the drainer for free, no second flag to remember. - Explicit MOLLIFIER_DRAINER_ENABLED=0 → drainer off on this replica. Cloud sets this everywhere except the dedicated drainer service. - Explicit MOLLIFIER_DRAINER_ENABLED=1 → drainer on, subject to MOLLIFIER_ENABLED still being the master kill switch (a drainer can't construct without the gate-side buffer singleton). The bootstrap in mollifierDrainerWorker.server.ts now gates on the new flag instead of WORKER_ENABLED, so the drainer's lifecycle is no longer coupled to the legacy worker role.
1 parent 3249d9b commit ae714c6

2 files changed

Lines changed: 49 additions & 7 deletions

File tree

apps/webapp/app/env.server.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,46 @@ const EnvironmentSchema = z
10521052
.optional()
10531053
.transform((v) => v ?? process.env.REDIS_PASSWORD),
10541054
COMMON_WORKER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"),
1055-
COMMON_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),
1055+
MOLLIFIER_ENABLED: z.string().default("0"),
1056+
// Separate switch for the drainer (consumer side) so it can be split
1057+
// off onto a dedicated worker service. Unset → inherits
1058+
// MOLLIFIER_ENABLED, so single-container self-hosters don't have to
1059+
// flip two switches. In multi-replica deployments, set this to "0"
1060+
// explicitly on every replica except the one dedicated drainer
1061+
// service — otherwise every replica's polling loop races for the
1062+
// same buffer entries. `MOLLIFIER_ENABLED` is still the master kill
1063+
// switch; setting this to "1" while `MOLLIFIER_ENABLED` is "0" is a
1064+
// no-op because the gate-side singleton refuses to construct a
1065+
// buffer when the system is off.
1066+
MOLLIFIER_DRAINER_ENABLED: z.string().default(process.env.MOLLIFIER_ENABLED ?? "0"),
1067+
MOLLIFIER_SHADOW_MODE: z.string().default("0"),
1068+
MOLLIFIER_REDIS_HOST: z
1069+
.string()
1070+
.optional()
1071+
.transform((v) => v ?? process.env.REDIS_HOST),
1072+
MOLLIFIER_REDIS_PORT: z.coerce
1073+
.number()
1074+
.optional()
1075+
.transform(
1076+
(v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined),
1077+
),
1078+
MOLLIFIER_REDIS_USERNAME: z
1079+
.string()
1080+
.optional()
1081+
.transform((v) => v ?? process.env.REDIS_USERNAME),
1082+
MOLLIFIER_REDIS_PASSWORD: z
1083+
.string()
1084+
.optional()
1085+
.transform((v) => v ?? process.env.REDIS_PASSWORD),
1086+
MOLLIFIER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"),
1087+
MOLLIFIER_TRIP_WINDOW_MS: z.coerce.number().int().positive().default(200),
1088+
MOLLIFIER_TRIP_THRESHOLD: z.coerce.number().int().positive().default(100),
1089+
MOLLIFIER_HOLD_MS: z.coerce.number().int().positive().default(500),
1090+
MOLLIFIER_DRAIN_CONCURRENCY: z.coerce.number().int().positive().default(50),
1091+
MOLLIFIER_ENTRY_TTL_S: z.coerce.number().int().positive().default(600),
1092+
MOLLIFIER_DRAIN_MAX_ATTEMPTS: z.coerce.number().int().positive().default(3),
1093+
MOLLIFIER_DRAIN_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().positive().default(30_000),
1094+
MOLLIFIER_DRAIN_MAX_ORGS_PER_TICK: z.coerce.number().int().positive().default(500),
10561095

10571096
BATCH_TRIGGER_PROCESS_JOB_VISIBILITY_TIMEOUT_MS: z.coerce
10581097
.number()

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,18 @@ declare global {
2929
* `batchTriggerWorker`).
3030
*
3131
* Gating order:
32-
* - `WORKER_ENABLED !== "true"` → early return (API-only replicas
33-
* still produce into the buffer via the trigger hot path; only worker
34-
* replicas drain it, otherwise every replica races for the same
35-
* entries).
32+
* - `MOLLIFIER_DRAINER_ENABLED !== "1"` → early return. Unset defaults
33+
* to `MOLLIFIER_ENABLED`, so single-container self-hosters still get
34+
* the drainer for free with one flag. In multi-replica deployments,
35+
* set this to "0" explicitly on every replica except the dedicated
36+
* drainer service so the polling loop doesn't race across replicas.
3637
* - `MOLLIFIER_ENABLED !== "1"` → `getMollifierDrainer()` returns null
37-
* and the bootstrap is a no-op.
38+
* and the bootstrap is a no-op. `MOLLIFIER_ENABLED` remains the
39+
* master kill switch; the new flag only controls WHICH replicas
40+
* run the drainer when the system is on.
3841
*/
3942
export function initMollifierDrainerWorker(): void {
40-
if (env.WORKER_ENABLED !== "true") {
43+
if (env.MOLLIFIER_DRAINER_ENABLED !== "1") {
4144
return;
4245
}
4346

0 commit comments

Comments
 (0)