Fix DB connection leak in health check handlers

Both server.ts files called createDb() on every /health request,
creating a new postgres.js connection pool (~10 connections) that was
never closed. Docker healthchecks hit this every 15s, exhausting
max_connections within minutes and causing "too many clients" errors.

Fix: use a fresh postgres.js client (max: 1) per health check that is
properly closed in a finally block. This truly tests whether the DB is
accepting new connections without leaking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-06 22:37:34 +02:00
parent 2d7844c02b
commit be13145072
2 changed files with 10 additions and 8 deletions

View file

@ -4,6 +4,7 @@ import { createReadStream, statSync } from "node:fs";
import { join, extname, resolve } from "node:path";
import { logger } from "./app/lib/logger.server.ts";
import { httpRequestDuration, registry } from "./app/lib/metrics.server.ts";
import postgres from "postgres";
const port = Number(process.env.PORT ?? 3000);
const CLIENT_DIR = resolve(import.meta.dirname, "build", "client");
@ -56,16 +57,16 @@ async function handleMetrics(_req: IncomingMessage, res: ServerResponse): Promis
const version = process.env.SENTRY_RELEASE ?? "dev";
async function handleHealth(_req: IncomingMessage, res: ServerResponse): Promise<void> {
const client = postgres(process.env.DATABASE_URL ?? "postgres://trails:trails@localhost:5432/trails", { max: 1 });
try {
const { createDb } = await import("@trails-cool/db");
const { sql } = await import("drizzle-orm");
const db = createDb();
await db.execute(sql`SELECT 1`);
await client`SELECT 1`;
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok", version, db: "connected" }));
} catch {
res.writeHead(503, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "degraded", version, db: "unreachable" }));
} finally {
await client.end();
}
}

View file

@ -6,6 +6,7 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:ht
import { createReadStream, statSync } from "node:fs";
import { join, extname, resolve } from "node:path";
import { setupYjsWebSocket } from "./app/lib/yjs-server.ts";
import postgres from "postgres";
const sentryEnvironment = process.env.CI ? "ci" : (process.env.NODE_ENV ?? "development");
@ -74,16 +75,16 @@ async function handleMetrics(_req: IncomingMessage, res: ServerResponse): Promis
const version = process.env.SENTRY_RELEASE ?? "dev";
async function handleHealth(_req: IncomingMessage, res: ServerResponse): Promise<void> {
const client = postgres(process.env.DATABASE_URL ?? "postgres://trails:trails@localhost:5432/trails", { max: 1 });
try {
const { createDb } = await import("@trails-cool/db");
const { sql } = await import("drizzle-orm");
const db = createDb();
await db.execute(sql`SELECT 1`);
await client`SELECT 1`;
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok", version, db: "connected" }));
} catch {
res.writeHead(503, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "degraded", version, db: "unreachable" }));
} finally {
await client.end();
}
}