커넥션 재시도 작업

This commit is contained in:
김판돌 2026-04-08 12:38:13 +09:00
parent cafc88c37c
commit d850e8154c
1 changed files with 36 additions and 12 deletions

View File

@ -5,11 +5,13 @@ import { logger } from '../utils/logger';
// Prisma 7 requires a driver adapter for direct database connections. // Prisma 7 requires a driver adapter for direct database connections.
const resolveDbSchema = (): string => { const resolveDbSchema = (): string => {
const raw = process.env.DATABASE_URL || ''; const raw = (process.env.DATABASE_URL || '').trim().replace(/^['"]|['"]$/g, '');
try { try {
const u = new URL(raw); const u = new URL(raw);
// Prisma-style: ?schema=kord_live // Prisma-style: ?schema=kord_live
return u.searchParams.get('schema') || 'public'; const schema = u.searchParams.get('schema') || 'public';
// Guard against invalid/unsafe identifiers in connection options.
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(schema) ? schema : 'public';
} catch { } catch {
return 'public'; return 'public';
} }
@ -18,6 +20,8 @@ const resolveDbSchema = (): string => {
const dbSchema = resolveDbSchema(); const dbSchema = resolveDbSchema();
const pool = new Pool({ const pool = new Pool({
connectionString: process.env.DATABASE_URL, connectionString: process.env.DATABASE_URL,
// Fail fast when DB is temporarily unreachable; connectDB will retry.
connectionTimeoutMillis: 5_000,
// pg(Pool) doesn't understand Prisma's `?schema=...` param. // pg(Pool) doesn't understand Prisma's `?schema=...` param.
// Force PostgreSQL search_path so both Prisma adapter and raw queries // Force PostgreSQL search_path so both Prisma adapter and raw queries
// operate on the intended schema (e.g. kord_live). // operate on the intended schema (e.g. kord_live).
@ -30,16 +34,36 @@ export const prisma = new PrismaClient({
log: ['warn', 'error'], log: ['warn', 'error'],
}); });
const sleep = (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms));
export const connectDB = async () => { export const connectDB = async () => {
const maxAttempts = 6;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try { try {
// Adapter-based client connects when first used, // Adapter-based client connects when first used,
// but we can test the pool connection here. // but we can test the pool connection here.
const client = await pool.connect(); const client = await pool.connect();
const { rows } = await client.query('SHOW search_path;'); const { rows } = await client.query('SHOW search_path;');
client.release(); client.release();
logger.info(`Connected to PostgreSQL successfully via Driver Adapter. (search_path=${rows?.[0]?.search_path ?? 'unknown'})`);
let hostPort = 'unknown';
try {
const u = new URL((process.env.DATABASE_URL || '').trim().replace(/^['"]|['"]$/g, ''));
hostPort = `${u.hostname}:${u.port || '5432'}`;
} catch {
// ignore
}
logger.info(
`Connected to PostgreSQL successfully via Driver Adapter. (targetSchema=${dbSchema}, search_path=${rows?.[0]?.search_path ?? 'unknown'}, host=${hostPort})`,
);
return;
} catch (error) { } catch (error) {
logger.error('Failed to connect to PostgreSQL:', error); const isLast = attempt === maxAttempts;
process.exit(1); logger.error(`Failed to connect to PostgreSQL (attempt ${attempt}/${maxAttempts}):`, error);
if (isLast) process.exit(1);
// Backoff: 0.5s, 1s, 2s, 4s, 8s...
await sleep(500 * 2 ** (attempt - 1));
}
} }
}; };