diff --git a/backend/check-knowledge-pkey.js b/backend/check-knowledge-pkey.js new file mode 100755 index 0000000..84a03f1 --- /dev/null +++ b/backend/check-knowledge-pkey.js @@ -0,0 +1,130 @@ +#!/usr/bin/env node + +/** + * Script zur Analyse des knowledge_pkey Problems + * + * Prüft warum knowledge_pkey nicht verwendet wird + */ + +import './config/loadEnv.js'; +import { sequelize } from './utils/sequelize.js'; + +async function main() { + try { + console.log('🔍 Analyse knowledge_pkey Problem\n'); + console.log('='.repeat(60) + '\n'); + + // Prüfe ob knowledge einen Primary Key hat + const [pkInfo] = await sequelize.query(` + SELECT + a.attname as column_name, + t.conname as constraint_name, + t.contype as constraint_type + FROM pg_constraint t + JOIN pg_class c ON c.oid = t.conrelid + JOIN pg_namespace n ON n.oid = c.relnamespace + JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(t.conkey) + WHERE n.nspname = 'falukant_data' + AND c.relname = 'knowledge' + AND t.contype = 'p'; + `); + + console.log('📋 Primary Key Information:'); + if (pkInfo.length > 0) { + pkInfo.forEach(pk => { + console.log(` Constraint: ${pk.constraint_name}`); + console.log(` Spalte: ${pk.column_name}`); + console.log(` Typ: ${pk.constraint_type === 'p' ? 'PRIMARY KEY' : pk.constraint_type}`); + }); + } else { + console.log(' ⚠️ Kein Primary Key gefunden!'); + } + console.log(''); + + // Prüfe alle Indizes auf knowledge + const [allIndexes] = await sequelize.query(` + SELECT + indexname, + indexdef, + idx_scan, + idx_tup_read, + idx_tup_fetch + FROM pg_indexes + LEFT JOIN pg_stat_user_indexes + ON pg_stat_user_indexes.indexrelname = pg_indexes.indexname + AND pg_stat_user_indexes.schemaname = pg_indexes.schemaname + WHERE pg_indexes.schemaname = 'falukant_data' + AND pg_indexes.tablename = 'knowledge' + ORDER BY indexname; + `); + + console.log('📊 Alle Indizes auf knowledge:'); + allIndexes.forEach(idx => { + console.log(`\n ${idx.indexname}:`); + console.log(` Definition: ${idx.indexdef}`); + console.log(` Scans: ${idx.idx_scan ? parseInt(idx.idx_scan).toLocaleString() : 'N/A'}`); + console.log(` Zeilen gelesen: ${idx.idx_tup_read ? parseInt(idx.idx_tup_read).toLocaleString() : 'N/A'}`); + }); + console.log(''); + + // Prüfe Tabellenstruktur + const [tableStructure] = await sequelize.query(` + SELECT + column_name, + data_type, + is_nullable, + column_default + FROM information_schema.columns + WHERE table_schema = 'falukant_data' + AND table_name = 'knowledge' + ORDER BY ordinal_position; + `); + + console.log('📋 Tabellenstruktur:'); + tableStructure.forEach(col => { + console.log(` ${col.column_name}: ${col.data_type} ${col.is_nullable === 'NO' ? 'NOT NULL' : 'NULL'}`); + }); + console.log(''); + + // Prüfe ob Queries mit id (Primary Key) gemacht werden + const [idUsage] = await sequelize.query(` + SELECT + query, + calls, + total_exec_time, + mean_exec_time + FROM pg_stat_statements + WHERE query LIKE '%knowledge%' + AND (query LIKE '%knowledge.id%' OR query LIKE '%knowledge%id%') + ORDER BY calls DESC + LIMIT 5; + `); + + if (idUsage.length > 0) { + console.log('🔍 Queries die knowledge.id verwenden:'); + idUsage.forEach(q => { + console.log(` Aufrufe: ${parseInt(q.calls).toLocaleString()}`); + console.log(` Query: ${q.query.substring(0, 150)}...`); + console.log(''); + }); + } else { + console.log(' ℹ️ Keine Queries gefunden, die knowledge.id verwenden'); + console.log(' 💡 Das erklärt, warum knowledge_pkey nicht verwendet wird\n'); + } + + await sequelize.close(); + process.exit(0); + + } catch (error) { + if (error.message.includes('pg_stat_statements')) { + console.log(' ⚠️ pg_stat_statements ist nicht aktiviert oder nicht verfügbar\n'); + } else { + console.error('❌ Fehler:', error.message); + console.error(error.stack); + } + await sequelize.close(); + process.exit(1); + } +} + +main(); diff --git a/backend/create-performance-indexes.js b/backend/create-performance-indexes.js new file mode 100755 index 0000000..858c52f --- /dev/null +++ b/backend/create-performance-indexes.js @@ -0,0 +1,147 @@ +#!/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`); + + if (created > 0) { + console.log('💡 Tipp: Führe ANALYZE aus, damit PostgreSQL die neuen Indizes berücksichtigt:'); + console.log(' ANALYZE falukant_data.knowledge;'); + console.log(' ANALYZE falukant_data.inventory;'); + console.log(' ANALYZE falukant_data.stock;'); + console.log(' ANALYZE falukant_data.production;'); + console.log(' ANALYZE falukant_data.director;\n'); + } + + await sequelize.close(); + process.exit(0); + + } catch (error) { + console.error('❌ Fehler:', error.message); + console.error(error.stack); + process.exit(1); + } +} + +main();