-- ===================================================================== -- FASE 1: Índices Críticos para Optimización Inmediata -- Propósito: Mejorar rendimiento de scanning_report_daily_full -- Tiempo estimado: 1-2 horas (dependiendo del tamaño de datos) -- Mejora esperada: 40-60% más rápido -- ===================================================================== -- IMPORTANTE: Todos los índices usan CONCURRENTLY para no bloquear producción -- Esto permite crear índices sin downtime, pero toma más tiempo SET statement_timeout = 0; SET lock_timeout = 0; -- ===================================================================== -- 1. Índice para JOIN de SplashUserConnections -- ===================================================================== -- Problema: El LEFT JOIN con DISTINCT es muy lento -- Solución: Índice filtrado solo en registros no eliminados CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_splash_user_connections_mac_notdeleted ON "SplashUserConnections" ("MacAddress") WHERE "IsDeleted" = false; COMMENT ON INDEX idx_splash_user_connections_mac_notdeleted IS 'Índice parcial para JOIN rápido en scanning_report_daily_full. Solo indexa registros activos.'; -- ===================================================================== -- 2. Índice compuesto principal para queries comunes -- ===================================================================== -- Optimiza: WHERE NetworkId IN (...) AND DetectionDate BETWEEN ... AND ... -- Este es el patrón más común en los dashboards CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_scanning_report_network_date_person ON scanning_report_daily_full ("NetworkId", "DetectionDate" DESC, "PersonType") INCLUDE ("MacAddress", "DurationInMinutes", "IsRegisteredUser", "DetectionsCount"); COMMENT ON INDEX idx_scanning_report_network_date_person IS 'Índice compuesto para filtros por red + fecha + tipo. INCLUDE permite index-only scans.'; -- ===================================================================== -- 3. Índice para filtros de rango de fechas -- ===================================================================== -- Optimiza: WHERE DetectionDate BETWEEN ... AND ... -- Útil para queries de métricas sin filtro de red CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_scanning_report_detection_date_network ON scanning_report_daily_full ("DetectionDate" DESC, "NetworkId"); COMMENT ON INDEX idx_scanning_report_detection_date_network IS 'Índice para queries de rango de fechas. Orden DESC optimiza ORDER BY recientes.'; -- ===================================================================== -- 4. Índice para filtros por tipo de persona (PasserBy/Visitor) -- ===================================================================== -- Optimiza: WHERE PersonType = 'Visitor' -- Usado en análisis de conversión CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_scanning_report_person_type ON scanning_report_daily_full ("PersonType") WHERE "PersonType" IS NOT NULL; COMMENT ON INDEX idx_scanning_report_person_type IS 'Índice filtrado para análisis por tipo de persona (Visitor/PasserBy).'; -- ===================================================================== -- 5. Índice para búsquedas por MAC Address -- ===================================================================== -- Optimiza: WHERE MacAddress = '...' -- Usado en reportes individuales y seguimiento de dispositivos CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_scanning_report_mac_address ON scanning_report_daily_full ("MacAddress"); COMMENT ON INDEX idx_scanning_report_mac_address IS 'Índice para búsquedas por dispositivo específico (MAC address).'; -- ===================================================================== -- 6. Índice para análisis por Access Point -- ===================================================================== -- Optimiza: JOIN con SplashAccessPoints -- Mejora queries de distribución por ubicación CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_splash_access_points_mac ON "SplashAccessPoints" ("Mac") WHERE "IsDeleted" = false; COMMENT ON INDEX idx_splash_access_points_mac IS 'Índice para JOIN rápido con AccessPoints en scanning reports.'; -- ===================================================================== -- 7. Índice para JOIN con Networks -- ===================================================================== -- Optimiza: LEFT JOIN "SplashMerakiNetworks" -- Mejora enriquecimiento de datos CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_splash_networks_id_org ON "SplashMerakiNetworks" ("Id", "OrganizationId") WHERE "IsDeleted" = false; COMMENT ON INDEX idx_splash_networks_id_org IS 'Índice para JOINs de Networks con Organizations en scanning reports.'; -- ===================================================================== -- 8. Índice compuesto para widget "Comparativa entre Sucursales" -- ===================================================================== -- Optimiza la query específica del widget más lento CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_scanning_report_branch_comparison ON scanning_report_daily_full ("NetworkId", "DetectionDate" DESC, "PersonType", "IsRegisteredUser") WHERE "DurationInMinutes" > 1; COMMENT ON INDEX idx_scanning_report_branch_comparison IS 'Índice especializado para widget Comparativa entre Sucursales. Filtrado por duración > 1min.'; -- ===================================================================== -- 9. Actualizar estadísticas del optimizador -- ===================================================================== -- Crítico: PostgreSQL necesita estadísticas actualizadas para elegir el índice correcto ANALYZE "SplashUserConnections"; ANALYZE "SplashAccessPoints"; ANALYZE "SplashMerakiNetworks"; ANALYZE "SplashMerakiOrganizations"; -- Si scanning_report_daily_full es materialized view: -- ANALYZE scanning_report_daily_full; -- Si es vista regular, analizar la tabla base: -- ANALYZE "SplashWiFiScanningData"; -- ===================================================================== -- 10. Verificar que los índices se crearon correctamente -- ===================================================================== SELECT schemaname, tablename, indexname, indexdef FROM pg_indexes WHERE indexname LIKE 'idx_scanning%' OR indexname LIKE 'idx_splash%' ORDER BY tablename, indexname; -- ===================================================================== -- 11. Verificar tamaño de índices -- ===================================================================== SELECT schemaname, tablename, indexname, pg_size_pretty(pg_relation_size(schemaname||'.'||indexname)) as index_size FROM pg_indexes WHERE indexname LIKE 'idx_scanning%' OR indexname LIKE 'idx_splash%' ORDER BY pg_relation_size(schemaname||'.'||indexname) DESC; -- ===================================================================== -- 12. Monitoreo: Queries lentas relacionadas (requiere pg_stat_statements) -- ===================================================================== -- Descomentar si pg_stat_statements está habilitado: /* SELECT query, calls, mean_exec_time, max_exec_time, total_exec_time FROM pg_stat_statements WHERE query ILIKE '%scanning_report_daily_full%' ORDER BY mean_exec_time DESC LIMIT 10; */ -- ===================================================================== -- NOTAS POST-IMPLEMENTACIÓN -- ===================================================================== /* 1. Monitorear pg_stat_user_indexes para verificar uso: SELECT * FROM pg_stat_user_indexes WHERE indexrelname LIKE 'idx_scanning%'; 2. Si un índice no se usa después de 1 semana, considerar eliminarlo 3. Ejecutar REINDEX periódicamente si hay muchos UPDATEs/DELETEs: REINDEX INDEX CONCURRENTLY idx_scanning_report_network_date_person; 4. Próximo paso: Implementar Fase 2 (pre-calcular timezone fields) */ -- ===================================================================== -- FIN DE FASE 1 -- =====================================================================