From c1cda5fa62711c325fd47aa81b02f0240e40d7b1 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 23 Jan 2026 12:48:26 +0100 Subject: [PATCH] Enhance Sequelize configuration and query handling in sequelize.js - Added connection pool settings to optimize database connection management. - Introduced a queryWithTimeout helper function to handle long-running queries, improving error handling and preventing indefinite hangs. - Updated syncModelsAlways function to utilize queryWithTimeout for foreign key checks and cleanup operations, enhancing robustness and logging for better visibility during synchronization. --- backend/utils/sequelize.js | 88 ++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/backend/utils/sequelize.js b/backend/utils/sequelize.js index 3ab1747..c7243f6 100644 --- a/backend/utils/sequelize.js +++ b/backend/utils/sequelize.js @@ -47,8 +47,38 @@ const sequelize = new Sequelize(dbName, dbUser, dbPass, { }, benchmark: SQL_BENCHMARK, logging: sqlLogger, + pool: { + max: 20, // Maximale Anzahl von Verbindungen im Pool + min: 5, // Minimale Anzahl von Verbindungen im Pool + acquire: 60000, // Maximale Zeit (ms) zum Erwerb einer Verbindung (60 Sekunden) + idle: 10000, // Maximale Zeit (ms), die eine Verbindung idle sein kann, bevor sie entfernt wird + evict: 1000 // Intervall (ms) zum Prüfen auf idle Verbindungen + }, + dialectOptions: { + connectTimeout: 60000 // Timeout für Verbindungsaufbau (60 Sekunden) + } }); +// Helper: Query mit Timeout (muss nach sequelize Initialisierung definiert werden) +const queryWithTimeout = async (query, timeoutMs = 10000, description = 'Query') => { + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs); + }); + + try { + const result = await Promise.race([ + sequelize.query(query), + timeoutPromise + ]); + return result; + } catch (error) { + if (error.message.includes('Timeout')) { + throw error; // Re-throw für bessere Fehlerbehandlung + } + throw error; + } +}; + const createSchemas = async () => { await sequelize.query('CREATE SCHEMA IF NOT EXISTS community'); await sequelize.query('CREATE SCHEMA IF NOT EXISTS logs'); @@ -547,46 +577,57 @@ const syncModelsAlways = async (models) => { const schema = model.options?.schema || 'public'; console.log(` 🔍 Checking for foreign keys in ${schema}.${tableName}...`); - const foreignKeys = await sequelize.query(` + // Verwende queryWithTimeout für Foreign Key Queries + const foreignKeys = await queryWithTimeout(` SELECT tc.constraint_name FROM information_schema.table_constraints AS tc WHERE tc.constraint_type = 'FOREIGN KEY' - AND tc.table_name = :tableName - AND tc.table_schema = :schema - `, { - replacements: { tableName, schema }, - type: sequelize.QueryTypes.SELECT - }); + AND tc.table_name = '${tableName.replace(/'/g, "''")}' + AND tc.table_schema = '${schema.replace(/'/g, "''")}' + `, 10000, `FK check for ${model.name}`); if (foreignKeys && foreignKeys.length > 0) { console.log(` ⚠️ Found ${foreignKeys.length} existing foreign keys:`, foreignKeys.map(fk => fk.constraint_name).join(', ')); console.log(` ⚠️ Removing ${foreignKeys.length} existing foreign keys from ${model.name} (schema: ${schema}) before sync`); for (const fk of foreignKeys) { - console.log(` 🗑️ Dropping constraint: ${fk.constraint_name}`); - await sequelize.query(` - ALTER TABLE "${schema}"."${tableName}" - DROP CONSTRAINT IF EXISTS "${fk.constraint_name}" CASCADE - `); + try { + console.log(` 🗑️ Dropping constraint: ${fk.constraint_name}`); + await queryWithTimeout(` + ALTER TABLE "${schema}"."${tableName}" + DROP CONSTRAINT IF EXISTS "${fk.constraint_name}" CASCADE + `, 10000, `Drop FK ${fk.constraint_name}`); + } catch (dropError) { + if (dropError.message.includes('Timeout')) { + console.warn(` ⚠️ Timeout beim Entfernen von ${fk.constraint_name} - überspringe...`); + } else { + console.warn(` ⚠️ Konnte ${fk.constraint_name} nicht entfernen:`, dropError.message?.substring(0, 100)); + } + } } - console.log(` ✅ All foreign keys removed for ${model.name}`); + console.log(` ✅ Foreign key removal completed for ${model.name}`); } else { console.log(` ✅ No foreign keys found for ${model.name}`); } } catch (fkError) { - console.warn(` ⚠️ Could not remove foreign keys for ${model.name}:`, fkError.message); - console.warn(` ⚠️ Error details:`, fkError); + // Ignoriere Timeout-Fehler - nicht kritisch + if (fkError.message && fkError.message.includes('Timeout')) { + console.warn(` ⚠️ Timeout beim Prüfen der Foreign Keys für ${model.name} - überspringe...`); + } else { + console.warn(` ⚠️ Could not remove foreign keys for ${model.name}:`, fkError.message?.substring(0, 100)); + } } console.log(` 🔄 Syncing model ${model.name} with constraints: false`); try { // Versuche doppelte pg_description Einträge vor dem Sync zu bereinigen // Hinweis: Benötigt Superuser-Rechte oder spezielle Berechtigungen + // Überspringe diese Query, da sie oft fehlschlägt und Verbindungen blockiert + // Die doppelten pg_description Einträge sind nicht kritisch für die Funktionalität try { const tableName = model.tableName; const schema = model.options?.schema || 'public'; - // Verwende direkte Parameter-Einsetzung, da DO $$ keine Parameterbindung unterstützt - // Die Parameter sind sicher, da sie von Sequelize-Modell-Eigenschaften kommen - await sequelize.query(` + // Verwende queryWithTimeout mit kurzem Timeout, da diese Query oft hängt + await queryWithTimeout(` DELETE FROM pg_catalog.pg_description d1 WHERE d1.objoid IN ( SELECT c.oid @@ -602,13 +643,14 @@ const syncModelsAlways = async (models) => { AND d2.objsubid = d1.objsubid AND d2.ctid < d1.ctid ) - `); + `, 5000, `pg_description cleanup for ${model.name}`); } catch (descError) { - // Ignoriere Berechtigungsfehler - das ist normal, wenn der Benutzer keine Superuser-Rechte hat - if (descError.message && descError.message.includes('Berechtigung')) { - console.log(` ℹ️ Cannot clean up duplicate pg_description entries (requires superuser privileges): ${model.name}`); + // Ignoriere alle Fehler - diese Query ist optional und blockiert oft + if (descError.message && (descError.message.includes('Berechtigung') || descError.message.includes('timeout') || descError.message.includes('Timeout'))) { + // Stille Warnung - nicht kritisch } else { - console.warn(` ⚠️ Could not clean up duplicate pg_description entries for ${model.name}:`, descError.message); + // Nur bei unerwarteten Fehlern warnen + console.warn(` ⚠️ Could not clean up duplicate pg_description entries for ${model.name}:`, descError.message?.substring(0, 100)); } }