From b5d1abc586bce0a5226db2099fccaf033af09d13 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Tue, 2 Jun 2026 23:33:13 -0700 Subject: [PATCH] feat(doctor): pool_budget check for GBRAIN_MAX_CONNECTIONS (#1794) computePoolBudgetCheck + checkPoolBudget warn when the parent pool leaves no room for a parallel sync worker under GBRAIN_MAX_CONNECTIONS, pointing at GBRAIN_POOL_SIZE=2. Registered in the ops category set. --- src/commands/doctor.ts | 62 +++++++++++++++++++++++++++++++++++ src/core/doctor-categories.ts | 1 + 2 files changed, 63 insertions(+) diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index 6441ab3d3..b463cd1c8 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -677,6 +677,9 @@ export async function doctorReportRemote(engine: BrainEngine): Promise maxConnections) { + return { + name: 'pool_budget', + status: 'warn', + message: + `GBRAIN_MAX_CONNECTIONS=${maxConnections} leaves no room for a parallel sync worker ` + + `(parent pool ${parentPool} + ${perWorkerPool} per-worker > ${maxConnections}). ` + + `Sync will run serial. If you hit EMAXCONNSESSION, lower the parent pool: ` + + '`gbrain config` / set GBRAIN_POOL_SIZE=2 (recommended for low-cap poolers like Supabase Supavisor).', + }; + } + const maxWorkers = Math.floor((maxConnections - parentPool) / perWorkerPool); + return { + name: 'pool_budget', + status: 'ok', + message: + `GBRAIN_MAX_CONNECTIONS=${maxConnections}: room for up to ${maxWorkers} parallel sync ` + + `worker(s) (parent pool ${parentPool} + ${perWorkerPool} per-worker).`, + }; +} + +/** Thin env/engine wrapper over `computePoolBudgetCheck`. */ +export async function checkPoolBudget(_engine: BrainEngine): Promise { + try { + const { resolveMaxConnections } = await import('../core/sync-concurrency.ts'); + const { resolvePoolSize } = await import('../core/db.ts'); + const maxConnections = resolveMaxConnections(); + const parentPool = resolvePoolSize(); + const perWorkerPool = Math.min(2, resolvePoolSize(2)); + return computePoolBudgetCheck(maxConnections, parentPool, perWorkerPool); + } catch (err) { + return { + name: 'pool_budget', + status: 'ok', + message: `Skipped (${err instanceof Error ? err.message : String(err)})`, + }; + } +} + /** * v0.38 — per-source `last_full_cycle_at` freshness check. * diff --git a/src/core/doctor-categories.ts b/src/core/doctor-categories.ts index 81dfedd5b..0941ce495 100644 --- a/src/core/doctor-categories.ts +++ b/src/core/doctor-categories.ts @@ -136,6 +136,7 @@ export const OPS_CHECK_NAMES: ReadonlySet = new Set([ 'orphan_clones', 'pgbouncer_prepare', 'pgvector', + 'pool_budget', 'progressive_batch_audit_health', 'queue_health', 'reranker_health',