---
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

<!-- SECTION:DESCRIPTION:BEGIN -->

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
<!-- SECTION:DESCRIPTION:END -->

## Acceptance Criteria

<!-- AC:BEGIN -->

- [ ] #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
<!-- AC:END -->
