#!/usr/bin/env node /** * Script zum Erstellen von Performance-Indizes * * Erstellt Indizes basierend auf der Analyse häufiger Queries: * - inventory: stock_id * - stock: branch_id * - production: branch_id * - director: employer_user_id * - knowledge: (character_id, product_id) composite */ import './config/loadEnv.js'; import { sequelize } from './utils/sequelize.js'; async function main() { try { console.log('🔧 Erstelle Performance-Indizes\n'); console.log('='.repeat(60) + '\n'); const indexes = [ { name: 'idx_knowledge_character_product', table: 'falukant_data.knowledge', columns: '(character_id, product_id)', description: 'Composite Index für JOINs mit character_id UND product_id', critical: true }, { name: 'idx_inventory_stock_id', table: 'falukant_data.inventory', columns: '(stock_id)', description: 'Index für WHERE inventory.stock_id = ...', critical: true }, { name: 'idx_stock_branch_id', table: 'falukant_data.stock', columns: '(branch_id)', description: 'Index für WHERE stock.branch_id = ...', critical: true }, { name: 'idx_production_branch_id', table: 'falukant_data.production', columns: '(branch_id)', description: 'Index für WHERE production.branch_id = ...', critical: true }, { name: 'idx_director_employer_user_id', table: 'falukant_data.director', columns: '(employer_user_id)', description: 'Index für WHERE director.employer_user_id = ...', critical: true }, { name: 'idx_production_start_timestamp', table: 'falukant_data.production', columns: '(start_timestamp)', description: 'Index für WHERE production.start_timestamp <= ...', critical: false }, { name: 'idx_director_last_salary_payout', table: 'falukant_data.director', columns: '(last_salary_payout)', description: 'Index für WHERE director.last_salary_payout < ...', critical: false } ]; console.log(`📋 ${indexes.length} Indizes werden erstellt:\n`); let created = 0; let skipped = 0; let errors = 0; for (let i = 0; i < indexes.length; i++) { const idx = indexes[i]; const criticalMark = idx.critical ? ' ⚠️ KRITISCH' : ''; console.log(`[${i + 1}/${indexes.length}] ${idx.name}${criticalMark}`); console.log(` Tabelle: ${idx.table}`); console.log(` Spalten: ${idx.columns}`); console.log(` Beschreibung: ${idx.description}`); try { // Prüfe ob Index bereits existiert const [existing] = await sequelize.query(` SELECT EXISTS( SELECT 1 FROM pg_indexes WHERE schemaname || '.' || tablename = '${idx.table}' AND indexname = '${idx.name}' ) as exists; `); if (existing[0].exists) { console.log(` ⏭️ Index existiert bereits, überspringe\n`); skipped++; continue; } // Erstelle Index const startTime = Date.now(); await sequelize.query(` CREATE INDEX IF NOT EXISTS ${idx.name} ON ${idx.table} USING btree ${idx.columns}; `); const duration = ((Date.now() - startTime) / 1000).toFixed(2); console.log(` ✅ Erstellt in ${duration}s\n`); created++; } catch (error) { console.error(` ❌ Fehler: ${error.message}\n`); errors++; } } console.log('='.repeat(60)); console.log(`✅ Zusammenfassung:`); console.log(` Erstellt: ${created}`); console.log(` Übersprungen: ${skipped}`); console.log(` Fehler: ${errors}\n`); // ANALYZE ausführen, damit PostgreSQL die neuen Indizes berücksichtigt const tablesToAnalyze = [ 'falukant_data.knowledge', 'falukant_data.inventory', 'falukant_data.stock', 'falukant_data.production', 'falukant_data.director' ]; if (created > 0) { console.log('📊 Führe ANALYZE auf betroffenen Tabellen aus...\n'); for (const table of tablesToAnalyze) { try { await sequelize.query(`ANALYZE ${table};`); console.log(` ✅ ANALYZE ${table};`); } catch (err) { console.log(` ⚠️ ${table}: ${err.message}`); } } console.log(''); } await sequelize.close(); process.exit(0); } catch (error) { console.error('❌ Fehler:', error.message); console.error(error.stack); process.exit(1); } } main();