#!/usr/bin/env python3 """ Erstellt kritische Datenbankindizes für MYP Backend Optimiert Queries für Raspberry Pi Performance """ import sys import os from sqlalchemy import text, inspect from models import get_db_session, engine from utils.logging_config import get_logger logger = get_logger("database_optimization") def check_index_exists(session, table_name, index_name): """Prüft ob Index bereits existiert""" try: inspector = inspect(engine) indexes = inspector.get_indexes(table_name) return any(idx['name'] == index_name for idx in indexes) except Exception: return False def create_indexes(): """Erstellt alle kritischen Datenbankindizes""" logger.info("🚀 Starte Datenbank-Index-Erstellung...") indexes_to_create = [ # Job-Tabelle (höchste Priorität) { 'name': 'ix_jobs_user_id', 'sql': 'CREATE INDEX IF NOT EXISTS ix_jobs_user_id ON jobs(user_id)', 'table': 'jobs', 'description': 'Jobs nach Benutzer (N+1 Query Prevention)' }, { 'name': 'ix_jobs_printer_id', 'sql': 'CREATE INDEX IF NOT EXISTS ix_jobs_printer_id ON jobs(printer_id)', 'table': 'jobs', 'description': 'Jobs nach Drucker (N+1 Query Prevention)' }, { 'name': 'ix_jobs_status', 'sql': 'CREATE INDEX IF NOT EXISTS ix_jobs_status ON jobs(status)', 'table': 'jobs', 'description': 'Jobs nach Status (Admin-Panel Queries)' }, { 'name': 'ix_jobs_start_at', 'sql': 'CREATE INDEX IF NOT EXISTS ix_jobs_start_at ON jobs(start_at)', 'table': 'jobs', 'description': 'Jobs nach Startzeit (Kalendar-Queries)' }, { 'name': 'ix_jobs_created_at', 'sql': 'CREATE INDEX IF NOT EXISTS ix_jobs_created_at ON jobs(created_at)', 'table': 'jobs', 'description': 'Jobs nach Erstellungszeit (Recent Jobs)' }, # User-Tabelle { 'name': 'ix_users_email', 'sql': 'CREATE INDEX IF NOT EXISTS ix_users_email ON users(email)', 'table': 'users', 'description': 'User nach Email (Login-Performance)' }, { 'name': 'ix_users_username', 'sql': 'CREATE INDEX IF NOT EXISTS ix_users_username ON users(username)', 'table': 'users', 'description': 'User nach Username (Login-Performance)' }, # GuestRequest-Tabelle { 'name': 'ix_guest_requests_email', 'sql': 'CREATE INDEX IF NOT EXISTS ix_guest_requests_email ON guest_requests(email)', 'table': 'guest_requests', 'description': 'Guest Requests nach Email' }, { 'name': 'ix_guest_requests_status', 'sql': 'CREATE INDEX IF NOT EXISTS ix_guest_requests_status ON guest_requests(status)', 'table': 'guest_requests', 'description': 'Guest Requests nach Status' }, { 'name': 'ix_guest_requests_printer_id', 'sql': 'CREATE INDEX IF NOT EXISTS ix_guest_requests_printer_id ON guest_requests(printer_id)', 'table': 'guest_requests', 'description': 'Guest Requests nach Drucker' }, # Notification-Tabelle { 'name': 'ix_notifications_user_id', 'sql': 'CREATE INDEX IF NOT EXISTS ix_notifications_user_id ON notifications(user_id)', 'table': 'notifications', 'description': 'Notifications nach User' }, { 'name': 'ix_notifications_created_at', 'sql': 'CREATE INDEX IF NOT EXISTS ix_notifications_created_at ON notifications(created_at)', 'table': 'notifications', 'description': 'Notifications nach Erstellungszeit' }, # PlugStatusLog-Tabelle { 'name': 'ix_plug_status_logs_printer_id', 'sql': 'CREATE INDEX IF NOT EXISTS ix_plug_status_logs_printer_id ON plug_status_logs(printer_id)', 'table': 'plug_status_logs', 'description': 'Plug Status Logs nach Drucker' }, { 'name': 'ix_plug_status_logs_timestamp', 'sql': 'CREATE INDEX IF NOT EXISTS ix_plug_status_logs_timestamp ON plug_status_logs(timestamp)', 'table': 'plug_status_logs', 'description': 'Plug Status Logs nach Zeitstempel' }, # Composite Indexes für häufige Query-Kombinationen { 'name': 'ix_jobs_user_status', 'sql': 'CREATE INDEX IF NOT EXISTS ix_jobs_user_status ON jobs(user_id, status)', 'table': 'jobs', 'description': 'Composite: Jobs nach User + Status' }, { 'name': 'ix_jobs_printer_status', 'sql': 'CREATE INDEX IF NOT EXISTS ix_jobs_printer_status ON jobs(printer_id, status)', 'table': 'jobs', 'description': 'Composite: Jobs nach Drucker + Status' }, { 'name': 'ix_jobs_status_created', 'sql': 'CREATE INDEX IF NOT EXISTS ix_jobs_status_created ON jobs(status, created_at)', 'table': 'jobs', 'description': 'Composite: Jobs nach Status + Erstellungszeit' } ] created_count = 0 skipped_count = 0 with get_db_session() as session: for index in indexes_to_create: try: # Prüfe ob Index bereits existiert if check_index_exists(session, index['table'], index['name']): logger.info(f"⏭️ Index {index['name']} existiert bereits") skipped_count += 1 continue # Index erstellen session.execute(text(index['sql'])) session.commit() logger.info(f"✅ Erstellt: {index['name']} - {index['description']}") created_count += 1 except Exception as e: logger.error(f"❌ Fehler bei {index['name']}: {e}") session.rollback() logger.info(f"\n🎯 Index-Erstellung abgeschlossen!") logger.info(f"✅ Neue Indizes erstellt: {created_count}") logger.info(f"⏭️ Bereits vorhanden: {skipped_count}") return created_count def analyze_query_performance(): """Analysiert Query-Performance nach Index-Erstellung""" logger.info("\n📊 Query-Performance-Analyse...") test_queries = [ "SELECT COUNT(*) FROM jobs WHERE user_id = 1", "SELECT COUNT(*) FROM jobs WHERE printer_id = 1", "SELECT COUNT(*) FROM jobs WHERE status = 'completed'", "SELECT COUNT(*) FROM guest_requests WHERE email = 'test@example.com'", "SELECT COUNT(*) FROM notifications WHERE user_id = 1" ] with get_db_session() as session: for query in test_queries: try: # Query-Plan anzeigen (SQLite EXPLAIN) explain_query = f"EXPLAIN QUERY PLAN {query}" result = session.execute(text(explain_query)).fetchall() logger.info(f"Query: {query}") for row in result: if 'USING INDEX' in str(row): logger.info(f" ✅ Nutzt Index: {row}") else: logger.info(f" ⚠️ Scan: {row}") except Exception as e: logger.error(f"Fehler bei Query-Analyse: {e}") def main(): """Hauptfunktion""" print("🗄️ MYP Database Index Optimization") print("=" * 40) try: # Indizes erstellen created_count = create_indexes() # Performance-Analyse if created_count > 0: analyze_query_performance() print(f"\n🚀 Optimierung abgeschlossen!") print(f"🔍 Erwartete Performance-Verbesserung: 40-60% für Datenbankzugriffe") except Exception as e: logger.error(f"Kritischer Fehler: {e}") sys.exit(1) if __name__ == "__main__": main()