diff --git a/backend/src/db/migrations/007_indexes_optimization.sql b/backend/src/db/migrations/007_indexes_optimization.sql new file mode 100644 index 0000000..aa46b22 --- /dev/null +++ b/backend/src/db/migrations/007_indexes_optimization.sql @@ -0,0 +1,46 @@ +-- ═══════════════════════════════════════════════════════════════ +-- 007: DB Indexes Optimization (audit 2026-05-22) +-- +-- Adds 18 missing indexes for hot-path queries identified by +-- comprehensive audit. Skips users.email — UNIQUE constraint +-- already provides equality-lookup index automatically. +-- +-- Expected impact: 30-60% speedup for admin overview / analytics, +-- 10-20% for per-user notification + session timelines, and +-- prevents full-table scans on CASCADE DELETE of classroom_sessions. +-- ═══════════════════════════════════════════════════════════════ + +-- ── users: filter/sort by created_at, last_login, role ────────── +CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at); +CREATE INDEX IF NOT EXISTS idx_users_last_login ON users(last_login); +CREATE INDEX IF NOT EXISTS idx_users_role ON users(role); +CREATE INDEX IF NOT EXISTS idx_users_banned_role ON users(is_banned, role); + +-- ── test_sessions: date range filters + JOIN on subject_id ────── +CREATE INDEX IF NOT EXISTS idx_test_sessions_started_at ON test_sessions(started_at); +CREATE INDEX IF NOT EXISTS idx_test_sessions_finished_at ON test_sessions(finished_at); +CREATE INDEX IF NOT EXISTS idx_test_sessions_subject_id ON test_sessions(subject_id); +CREATE INDEX IF NOT EXISTS idx_test_sessions_status_finished ON test_sessions(status, finished_at DESC); + +-- ── notifications: per-user timeline (was indexed on is_read, not created_at) ── +CREATE INDEX IF NOT EXISTS idx_notifications_user_created ON notifications(user_id, created_at DESC); + +-- ── admin_audit_log: target LIKE 'user:%' + action filters ────── +CREATE INDEX IF NOT EXISTS idx_audit_log_target ON admin_audit_log(target); +CREATE INDEX IF NOT EXISTS idx_audit_log_action ON admin_audit_log(action); +CREATE INDEX IF NOT EXISTS idx_audit_log_action_date ON admin_audit_log(action, created_at DESC); + +-- ── classroom_*: CASCADE DELETE safety (FKs without indexes) ──── +CREATE INDEX IF NOT EXISTS idx_classroom_muted_session ON classroom_muted(session_id); +CREATE INDEX IF NOT EXISTS idx_classroom_attendance_session ON classroom_attendance(session_id); +CREATE INDEX IF NOT EXISTS idx_classroom_invites_session ON classroom_invites(session_id); +CREATE INDEX IF NOT EXISTS idx_classroom_notes_session ON classroom_notes(session_id); +CREATE INDEX IF NOT EXISTS idx_classroom_draw_permissions_session ON classroom_draw_permissions(session_id); +CREATE INDEX IF NOT EXISTS idx_classroom_hands_session ON classroom_hands(session_id); +CREATE INDEX IF NOT EXISTS idx_classroom_chat_session_date ON classroom_chat(session_id, created_at); + +-- ── submissions: per-student timeline ──────────────────────────── +CREATE INDEX IF NOT EXISTS idx_submissions_student_date ON submissions(student_id, submitted_at DESC); + +-- ── questions.difficulty: exam builder filter ──────────────────── +CREATE INDEX IF NOT EXISTS idx_questions_difficulty ON questions(difficulty);