/** * πŸ”₯ λ²„νŠΌ μ œμ–΄κ΄€λ¦¬ μ„±λŠ₯ μ΅œμ ν™” 인덱슀 μ„€μΉ˜ 슀크립트 * * μ‚¬μš©λ²•: * node scripts/install-dataflow-indexes.js */ const { PrismaClient } = require("@prisma/client"); const fs = require("fs"); const path = require("path"); const prisma = new PrismaClient(); async function installDataflowIndexes() { try { console.log("πŸ”₯ Starting Button Dataflow Performance Optimization...\n"); // SQL 파일 읽기 const sqlFilePath = path.join( __dirname, "../database/migrations/add_button_dataflow_indexes.sql" ); const sqlContent = fs.readFileSync(sqlFilePath, "utf8"); console.log("πŸ“– Reading SQL migration file..."); console.log(`πŸ“ File: ${sqlFilePath}\n`); // λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° 확인 console.log("πŸ” Checking database connection..."); await prisma.$queryRaw`SELECT 1`; console.log("βœ… Database connection OK\n"); // κΈ°μ‘΄ 인덱슀 μƒνƒœ 확인 console.log("πŸ” Checking existing indexes..."); const existingIndexes = await prisma.$queryRaw` SELECT indexname, tablename FROM pg_indexes WHERE tablename = 'dataflow_diagrams' AND indexname LIKE 'idx_dataflow%' ORDER BY indexname; `; if (existingIndexes.length > 0) { console.log("πŸ“‹ Existing dataflow indexes:"); existingIndexes.forEach((idx) => { console.log(` - ${idx.indexname}`); }); } else { console.log("πŸ“‹ No existing dataflow indexes found"); } console.log(""); // ν…Œμ΄λΈ” μƒνƒœ 확인 console.log("πŸ” Checking dataflow_diagrams table stats..."); const tableStats = await prisma.$queryRaw` SELECT COUNT(*) as total_rows, COUNT(*) FILTER (WHERE control IS NOT NULL) as with_control, COUNT(*) FILTER (WHERE plan IS NOT NULL) as with_plan, COUNT(*) FILTER (WHERE category IS NOT NULL) as with_category, COUNT(DISTINCT company_code) as companies FROM dataflow_diagrams; `; if (tableStats.length > 0) { const stats = tableStats[0]; console.log(`πŸ“Š Table Statistics:`); console.log(` - Total rows: ${stats.total_rows}`); console.log(` - With control: ${stats.with_control}`); console.log(` - With plan: ${stats.with_plan}`); console.log(` - With category: ${stats.with_category}`); console.log(` - Companies: ${stats.companies}`); } console.log(""); // SQL μ‹€ν–‰ console.log("πŸš€ Installing performance indexes..."); console.log("⏳ This may take a few minutes for large datasets...\n"); const startTime = Date.now(); // SQL을 λ¬Έμž₯λ³„λ‘œ λ‚˜λˆ„μ–΄ μ‹€ν–‰ (PostgreSQL ν•¨μˆ˜ λ•Œλ¬Έμ—) const sqlStatements = sqlContent .split(/;\s*(?=\n|$)/) .filter( (stmt) => stmt.trim().length > 0 && !stmt.trim().startsWith("--") && !stmt.trim().startsWith("/*") ); for (let i = 0; i < sqlStatements.length; i++) { const statement = sqlStatements[i].trim(); if (statement.length === 0) continue; try { // DO λΈ”λ‘μ΄λ‚˜ 볡합 λ¬Έμž₯ 처리 if ( statement.includes("DO $$") || statement.includes("CREATE OR REPLACE VIEW") ) { console.log( `⚑ Executing statement ${i + 1}/${sqlStatements.length}...` ); await prisma.$executeRawUnsafe(statement + ";"); } else if (statement.startsWith("CREATE INDEX")) { const indexName = statement.match(/CREATE INDEX[^"]*"?([^"\s]+)"?/)?.[1] || "unknown"; console.log(`πŸ”§ Creating index: ${indexName}...`); await prisma.$executeRawUnsafe(statement + ";"); } else if (statement.startsWith("ANALYZE")) { console.log(`πŸ“Š Analyzing table statistics...`); await prisma.$executeRawUnsafe(statement + ";"); } else { await prisma.$executeRawUnsafe(statement + ";"); } } catch (error) { // 이미 μ‘΄μž¬ν•˜λŠ” 인덱슀 μ—λŸ¬λŠ” λ¬΄μ‹œ if (error.message.includes("already exists")) { console.log(`⚠️ Index already exists, skipping...`); } else { console.error(`❌ Error executing statement: ${error.message}`); console.error(`πŸ“ Statement: ${statement.substring(0, 100)}...`); } } } const endTime = Date.now(); const executionTime = (endTime - startTime) / 1000; console.log( `\nβœ… Index installation completed in ${executionTime.toFixed(2)} seconds!` ); // μ„€μΉ˜λœ 인덱슀 확인 console.log("\nπŸ” Verifying installed indexes..."); const newIndexes = await prisma.$queryRaw` SELECT indexname, pg_size_pretty(pg_relation_size(indexrelid)) as size FROM pg_stat_user_indexes WHERE tablename = 'dataflow_diagrams' AND indexname LIKE 'idx_dataflow%' ORDER BY indexname; `; if (newIndexes.length > 0) { console.log("πŸ“‹ Installed indexes:"); newIndexes.forEach((idx) => { console.log(` βœ… ${idx.indexname} (${idx.size})`); }); } // μ„±λŠ₯ 톡계 쑰회 console.log("\nπŸ“Š Performance statistics:"); try { const perfStats = await prisma.$queryRaw`SELECT * FROM dataflow_performance_stats;`; if (perfStats.length > 0) { const stats = perfStats[0]; console.log(` - Table size: ${stats.table_size}`); console.log(` - Total diagrams: ${stats.total_rows}`); console.log(` - With control: ${stats.with_control}`); console.log(` - Companies: ${stats.companies}`); } } catch (error) { console.log(" ⚠️ Performance view not available yet"); } console.log("\n🎯 Performance Optimization Complete!"); console.log("Expected improvements:"); console.log(" - Button dataflow lookup: 500ms+ β†’ 10-50ms ⚑"); console.log(" - Category filtering: 200ms+ β†’ 5-20ms ⚑"); console.log(" - Company queries: 100ms+ β†’ 5-15ms ⚑"); console.log("\nπŸ’‘ Monitor performance with:"); console.log(" SELECT * FROM dataflow_performance_stats;"); console.log(" SELECT * FROM dataflow_index_efficiency;"); } catch (error) { console.error("\n❌ Error installing dataflow indexes:", error); process.exit(1); } finally { await prisma.$disconnect(); } } // μ‹€ν–‰ if (require.main === module) { installDataflowIndexes() .then(() => { console.log("\nπŸŽ‰ Installation completed successfully!"); process.exit(0); }) .catch((error) => { console.error("\nπŸ’₯ Installation failed:", error); process.exit(1); }); } module.exports = { installDataflowIndexes };