From f2b906686c0faeaf9e818065756c866693a07381 Mon Sep 17 00:00:00 2001 From: Daniel Sutton Date: Sun, 5 Jul 2026 13:31:29 +0100 Subject: [PATCH] feat(webapp): add RUN_REPLICATION_RUN_OPS_DATABASE_URL for the runs-replication source The run-ops runs-replication source now takes its connection URL from RUN_REPLICATION_RUN_OPS_DATABASE_URL, required whenever the run-ops split is enabled. The runs replicator speaks the Postgres streaming replication protocol, which cannot run through a transaction pooler, so it needs its own direct endpoint separate from the app's RUN_OPS_DATABASE_URL (which may point at a pooler). When the split is on and this is unset, boot fails via SplitReplicationMisconfiguredError rather than silently falling back to a wrong endpoint. --- apps/webapp/app/env.server.ts | 10 +++++++--- .../app/services/runsReplicationInstance.server.ts | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index f5e0f0a671c..9ecfb54b217 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -1713,9 +1713,13 @@ const EnvironmentSchema = z // --- Run-ops DB split — second replication source (the NEW dedicated run-ops DB). --- // Cloud-only; only consulted when isSplitEnabled() is true. Self-host never sets these. - // The NEW source's connection URL is RUN_OPS_DATABASE_URL; these add the NEW source's replication - // slot/publication and an explicit per-source enable so it can be brought up independently of the - // legacy source during the transition. + // Connection URL for the run-ops DB used by the runs-replication source. Required when the split is + // enabled (unset → boot fails via SplitReplicationMisconfiguredError, no silent fallback). Kept + // separate from the app's RUN_OPS_DATABASE_URL: replication can't run over a pooler, so this is direct. + RUN_REPLICATION_RUN_OPS_DATABASE_URL: z + .string() + .refine(isValidDatabaseUrl, "RUN_REPLICATION_RUN_OPS_DATABASE_URL is invalid") + .optional(), RUN_REPLICATION_NEW_SLOT_NAME: z.string().default("task_runs_to_clickhouse_v2"), RUN_REPLICATION_NEW_PUBLICATION_NAME: z .string() diff --git a/apps/webapp/app/services/runsReplicationInstance.server.ts b/apps/webapp/app/services/runsReplicationInstance.server.ts index ad048422ac7..8d7eefc2c72 100644 --- a/apps/webapp/app/services/runsReplicationInstance.server.ts +++ b/apps/webapp/app/services/runsReplicationInstance.server.ts @@ -71,7 +71,7 @@ export class SplitReplicationMisconfiguredError extends Error { 'RUN_OPS_SPLIT_ENABLED is on but the runs-replication sources[] has no "new" source: ' + "run-ops runs on the new DB would not replicate to ClickHouse, under-counting every " + "ClickHouse-fronted aggregate. Enable the new replication source " + - "(RUN_REPLICATION_NEW_ENABLED / RUN_OPS_DATABASE_URL) or turn the split off." + "(RUN_REPLICATION_NEW_ENABLED / RUN_REPLICATION_RUN_OPS_DATABASE_URL) or turn the split off." ); this.name = "SplitReplicationMisconfiguredError"; } @@ -172,7 +172,7 @@ function initializeRunsReplicationInstance() { const sources = buildReplicationSources({ splitEnabled, legacyUrl: DATABASE_URL, - newUrl: env.RUN_OPS_DATABASE_URL, + newUrl: env.RUN_REPLICATION_RUN_OPS_DATABASE_URL, newSourceOverride: env.RUN_REPLICATION_NEW_ENABLED === "disabled" ? false : undefined, legacySlotName: env.RUN_REPLICATION_SLOT_NAME, legacyPublicationName: env.RUN_REPLICATION_PUBLICATION_NAME,