-- ===================================================== -- MIGRATION: Add composite indexes to ml.predictions for performance -- ===================================================== -- Date: 2026-02-03 -- Task: ST-3.4, GAP-008 -- Description: Optimize query performance with composite indexes -- ===================================================== -- =========================================== -- COMPOSITE INDEX 1: Symbol + Timeframe + Created -- =========================================== -- Query pattern: SELECT * FROM ml.predictions -- WHERE symbol = ? AND timeframe = ? -- ORDER BY created_at DESC LIMIT ? -- Use case: Most common query for fetching latest predictions for a symbol/timeframe CREATE INDEX IF NOT EXISTS idx_predictions_symbol_timeframe_created ON ml.predictions(symbol, timeframe, created_at DESC); -- =========================================== -- COMPOSITE INDEX 2: Symbol + Prediction Type + Created -- =========================================== -- Query pattern: SELECT * FROM ml.predictions -- WHERE symbol = ? AND prediction_type = ? -- ORDER BY created_at DESC -- Use case: Filtered queries by prediction type (e.g., only DIRECTION predictions) CREATE INDEX IF NOT EXISTS idx_predictions_symbol_type_created ON ml.predictions(symbol, prediction_type, created_at DESC); -- =========================================== -- COMPOSITE INDEX 3: Model + Symbol + Timeframe + Created -- =========================================== -- Query pattern: SELECT * FROM ml.predictions -- WHERE model_id = ? AND symbol = ? AND timeframe = ? -- ORDER BY created_at DESC -- Use case: Model-specific prediction queries for analysis and comparison CREATE INDEX IF NOT EXISTS idx_predictions_model_symbol_timeframe ON ml.predictions(model_id, symbol, timeframe, created_at DESC); -- =========================================== -- COMPOSITE INDEX 4: High Confidence Predictions (Partial) -- =========================================== -- Query pattern: SELECT * FROM ml.predictions -- WHERE symbol = ? AND timeframe = ? AND confidence_score >= 0.7 -- ORDER BY created_at DESC -- Use case: Fetch only high-confidence predictions for display/alerts CREATE INDEX IF NOT EXISTS idx_predictions_high_confidence ON ml.predictions(symbol, timeframe, created_at DESC) WHERE confidence_score >= 0.7; -- =========================================== -- COMPOSITE INDEX 5: Recent Valid Predictions (Partial) -- =========================================== -- Query pattern: SELECT * FROM ml.predictions -- WHERE symbol = ? AND timeframe = ? AND valid_until > NOW() -- ORDER BY prediction_type, created_at DESC -- Use case: Active predictions that haven't expired yet -- Note: Uses immutable predicate-safe pattern (valid_until compared at query time) CREATE INDEX IF NOT EXISTS idx_predictions_recent_valid ON ml.predictions(symbol, timeframe, prediction_type, created_at DESC) WHERE valid_until IS NOT NULL; -- =========================================== -- COMPOSITE INDEX 6: Overlay Display for Charts -- =========================================== -- Query pattern: SELECT * FROM ml.predictions -- WHERE symbol = ? AND timeframe = ? -- AND (chart_config->>'show_on_chart')::boolean = true -- ORDER BY display_priority DESC, created_at DESC -- Use case: Chart rendering - fetch predictions configured for display CREATE INDEX IF NOT EXISTS idx_predictions_overlay_display ON ml.predictions(symbol, timeframe, display_priority DESC, created_at DESC) WHERE (chart_config->>'show_on_chart')::boolean = true; -- =========================================== -- COMPOSITE INDEX 7: Symbol + Confidence Score (Descending) -- =========================================== -- Query pattern: SELECT * FROM ml.predictions -- WHERE symbol = ? -- ORDER BY confidence_score DESC -- Use case: Ranking predictions by confidence for a symbol CREATE INDEX IF NOT EXISTS idx_predictions_symbol_confidence ON ml.predictions(symbol, confidence_score DESC, created_at DESC); -- =========================================== -- COMPOSITE INDEX 8: Timeframe + Prediction Result + Created -- =========================================== -- Query pattern: SELECT * FROM ml.predictions -- WHERE timeframe = ? AND prediction_result = ? -- ORDER BY created_at DESC -- Use case: Cross-symbol analysis of specific prediction results (e.g., all BUY signals on 1H) CREATE INDEX IF NOT EXISTS idx_predictions_timeframe_result_created ON ml.predictions(timeframe, prediction_result, created_at DESC) WHERE prediction_result IS NOT NULL; -- =========================================== -- DOCUMENTATION NOTES -- =========================================== -- Performance considerations: -- 1. These indexes are designed for read-heavy workloads -- 2. Partial indexes reduce storage and improve write performance -- 3. DESC ordering on timestamps optimizes LIMIT queries for recent data -- 4. Run ANALYZE after creation in production: -- ANALYZE ml.predictions; -- -- Index maintenance: -- - Monitor pg_stat_user_indexes for usage statistics -- - Consider REINDEX CONCURRENTLY if fragmentation occurs -- - Review query plans with EXPLAIN ANALYZE periodically