diff --git a/backlog/tasks/ml-169.11 - Fix-Correlated-subqueries-in-ListeningStats.recent_activity.md b/backlog/tasks/ml-169.11 - Fix-Correlated-subqueries-in-ListeningStats.recent_activity.md new file mode 100644 index 00000000..97d414a9 --- /dev/null +++ b/backlog/tasks/ml-169.11 - Fix-Correlated-subqueries-in-ListeningStats.recent_activity.md @@ -0,0 +1,66 @@ +--- +id: ML-169.11 +title: "Fix: Correlated subqueries in ListeningStats.recent_activity" +status: To Do +assignee: [] +created_date: "2026-05-19 11:48" +labels: + - perf + - fix + - query +dependencies: [] +references: + - >- + backlog/documents/doc-27 - + Phase-4-Performance-Audit-Low-Hanging-Fruit-Findings.md +modified_files: + - lib/music_library/listening_stats.ex +parent_task_id: ML-169 +priority: medium +ordinal: 32000 +--- + +## Description + + + +Optimize the `tracks_with_record_info_query` in `ListeningStats.recent_activity` to eliminate 3 correlated subqueries per row by using batch post-processing in Elixir. + +## Problem + +The `tracks_with_record_info_query` includes 3 correlated subqueries per row (matching_records, artist_id, cover_hash), each with a nested subquery through `record_releases`. This runs for up to 100 tracks on every StatsLive mount and every scrobble update. + +```sql +(SELECT json_group_array(...) FROM records r WHERE r.musicbrainz_id = ...), +(SELECT min(ar.musicbrainz_id) FROM artist_records ar ...), +(SELECT r.cover_hash FROM records r ...) +``` + +## Impact + +300 correlated subqueries total for 100 tracks. Already noted in code comments: "cost scales with number of result rows (≤ limit)". This fires on mount + every 5-minute scrobble update. + +## Files + +- `lib/music_library/listening_stats.ex:339-397` — `tracks_with_record_info_query` + +## Fix + +Consider a batch post-processing approach: + +1. Fetch tracks without correlated subqueries +2. Collect all unique album release IDs +3. Batch-look up records in a single query +4. Merge record info in Elixir + + +## Acceptance Criteria + + + +- [ ] #1 recent_activity no longer uses correlated subqueries +- [ ] #2 Batch query produces same data as before +- [ ] #3 No regression in StatsLive activity feed rendering +- [ ] #4 Performance improvement measurable via QueryReporter +- [ ] #5 Existing tests for ListeningStats.recent_activity pass +