mirror of
https://github.com/polaroi8d/cactoide.git
synced 2026-03-22 06:05:28 +00:00
feat: fail fast when database connection cannot be established
This commit is contained in:
@@ -1,8 +1,30 @@
|
|||||||
// src/hooks.server.ts
|
// src/hooks.server.ts
|
||||||
import type { Handle } from '@sveltejs/kit';
|
import type { Handle } from '@sveltejs/kit';
|
||||||
import { generateUserId } from '$lib/generateUserId.js';
|
import { generateUserId } from '$lib/generateUserId.js';
|
||||||
|
import { ensureDatabaseConnection } from '$lib/database/healthCheck';
|
||||||
|
|
||||||
|
// Global flag to track if database health check has been performed
|
||||||
|
let dbHealthCheckPerformed = false;
|
||||||
|
|
||||||
export const handle: Handle = async ({ event, resolve }) => {
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
|
// Perform database health check only once during application startup
|
||||||
|
if (!dbHealthCheckPerformed) {
|
||||||
|
try {
|
||||||
|
await ensureDatabaseConnection({
|
||||||
|
maxRetries: 3,
|
||||||
|
baseDelay: 1000,
|
||||||
|
maxDelay: 10000,
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
dbHealthCheckPerformed = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Database health check failed:', error);
|
||||||
|
// The ensureDatabaseConnection function will exit the process
|
||||||
|
// if the database is unavailable, so this catch is just for safety
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const cactoideUserId = event.cookies.get('cactoideUserId');
|
const cactoideUserId = event.cookies.get('cactoideUserId');
|
||||||
const userId = generateUserId();
|
const userId = generateUserId();
|
||||||
|
|
||||||
|
|||||||
104
src/lib/database/healthCheck.ts
Normal file
104
src/lib/database/healthCheck.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import postgres from 'postgres';
|
||||||
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
|
interface HealthCheckOptions {
|
||||||
|
maxRetries?: number;
|
||||||
|
baseDelay?: number;
|
||||||
|
maxDelay?: number;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HealthCheckResult {
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
attempts: number;
|
||||||
|
duration?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a database health check with retry logic and exponential backoff
|
||||||
|
*/
|
||||||
|
export async function checkDatabaseHealth(
|
||||||
|
options: HealthCheckOptions = {}
|
||||||
|
): Promise<HealthCheckResult> {
|
||||||
|
const {
|
||||||
|
maxRetries = 3,
|
||||||
|
baseDelay = 1000, // 1 second
|
||||||
|
maxDelay = 10000, // 10 seconds
|
||||||
|
timeout = 5000 // 5 seconds
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
if (!env.DATABASE_URL) {
|
||||||
|
console.error('DATABASE_URL environment variable is not set');
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'DATABASE_URL environment variable is not set',
|
||||||
|
attempts: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastError: Error | null = null;
|
||||||
|
|
||||||
|
console.log(`Starting database health check (max retries: ${maxRetries})`);
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
console.log(`Attempt ${attempt}/${maxRetries} - Testing database connection`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a new connection for the health check
|
||||||
|
const client = postgres(env.DATABASE_URL, {
|
||||||
|
max: 1,
|
||||||
|
idle_timeout: 20,
|
||||||
|
connect_timeout: timeout / 1000, // Convert to seconds
|
||||||
|
onnotice: () => {} // Suppress notices
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test the connection with a simple query
|
||||||
|
await client`SELECT 1 as health_check`;
|
||||||
|
await client.end();
|
||||||
|
|
||||||
|
console.log(`Database connection successful on attempt ${attempt}.`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
attempts: attempt
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error as Error;
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
console.error(`Connection failed on attempt ${attempt}: ${errorMessage}`);
|
||||||
|
|
||||||
|
// Don't wait after the last attempt
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
|
||||||
|
console.log(`Waiting ${delay}ms before retry...`);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalError = lastError?.message || 'Unknown database connection error';
|
||||||
|
|
||||||
|
console.error(`All ${maxRetries} attempts failed. Final error: ${finalError}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: finalError,
|
||||||
|
attempts: maxRetries
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs database health check and exits the process if it fails
|
||||||
|
*/
|
||||||
|
export async function ensureDatabaseConnection(options?: HealthCheckOptions): Promise<void> {
|
||||||
|
const result = await checkDatabaseHealth(options);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
console.error('Database connection failed after all retry attempts');
|
||||||
|
console.error(`Error: ${result.error}`);
|
||||||
|
console.error(`Attempts made: ${result.attempts}`);
|
||||||
|
console.error('Exiting application...');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user