From 0f946846104a72df7ccdf79772dca2c60250cdb9 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 16 Apr 2026 14:02:43 +0100 Subject: [PATCH] Retry the initial boot using p-retry --- .../organizationDataStoresRegistry.server.ts | 7 +++++- ...zationDataStoresRegistryInstance.server.ts | 22 +++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/apps/webapp/app/services/dataStores/organizationDataStoresRegistry.server.ts b/apps/webapp/app/services/dataStores/organizationDataStoresRegistry.server.ts index 379084a0d..f2ef02cc3 100644 --- a/apps/webapp/app/services/dataStores/organizationDataStoresRegistry.server.ts +++ b/apps/webapp/app/services/dataStores/organizationDataStoresRegistry.server.ts @@ -13,7 +13,12 @@ export class OrganizationDataStoresRegistry { private _loaded = false; private _readyResolve!: () => void; - /** Resolves once the initial `loadFromDatabase()` completes successfully. */ + /** + * Resolves once the initial `loadFromDatabase()` completes successfully. + * At process startup the singleton loads the registry with unbounded retries + * (exponential backoff, capped delay) until Postgres is reachable; until then + * this promise stays pending and callers that await readiness will block. + */ readonly isReady: Promise; constructor(prisma: PrismaClient | PrismaReplicaClient) { diff --git a/apps/webapp/app/services/dataStores/organizationDataStoresRegistryInstance.server.ts b/apps/webapp/app/services/dataStores/organizationDataStoresRegistryInstance.server.ts index f7d82da56..24ec572c5 100644 --- a/apps/webapp/app/services/dataStores/organizationDataStoresRegistryInstance.server.ts +++ b/apps/webapp/app/services/dataStores/organizationDataStoresRegistryInstance.server.ts @@ -1,5 +1,7 @@ +import pRetry from "p-retry"; import { $replica } from "~/db.server"; import { env } from "~/env.server"; +import { logger } from "~/services/logger.server"; import { signalsEmitter } from "~/services/signals.server"; import { singleton } from "~/utils/singleton"; import { OrganizationDataStoresRegistry } from "./organizationDataStoresRegistry.server"; @@ -7,8 +9,24 @@ import { OrganizationDataStoresRegistry } from "./organizationDataStoresRegistry export const organizationDataStoresRegistry = singleton("organizationDataStoresRegistry", () => { const registry = new OrganizationDataStoresRegistry($replica); - registry.loadFromDatabase().catch((err) => { - console.error("[OrganizationDataStoresRegistry] Failed to initialize", err); + // Runs as soon as this singleton is created (first import of this module). The + // registry’s `isReady` promise resolves when this eventually succeeds. + const startupLoadPromise = pRetry(() => registry.loadFromDatabase(), { + forever: true, + retries: 10, + minTimeout: 1_000, + maxTimeout: 60_000, + factor: 2, + onFailedAttempt: (error) => { + logger.warn("[OrganizationDataStoresRegistry] Startup load failed, retrying", { + attemptNumber: error.attemptNumber, + retriesLeft: error.retriesLeft, + error: error.message, + }); + }, + }); + startupLoadPromise.catch((err) => { + console.error("[OrganizationDataStoresRegistry] Unexpected startup load failure", err); }); const interval = setInterval(() => {