Skip to content

Commit c6c86d7

Browse files
rickyromboclaude
andauthored
Use LATERAL joins for followee reposts/favorites queries (#759)
## Summary - Rewrites the followee_reposts and followee_favorites subqueries in both `get_playlists.sql` and `get_tracks.sql` to use `CROSS JOIN LATERAL` - Forces Postgres to drive from the `my_follows` CTE (~5k rows max) and do index point lookups on saves/reposts PKs, instead of scanning the full saves/reposts table for a given item ID (92k+ rows for popular playlists) - The planner was choosing hash joins that scan all saves for a playlist_id first — LATERAL prevents this by making the subquery depend on each followee row ## Test plan - [x] Verified with `EXPLAIN ANALYZE` on production data: query time dropped significantly for popular playlists - [x] `sqlc generate` succeeds - [x] `go build ./...` passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bdeae40 commit c6c86d7

File tree

4 files changed

+100
-60
lines changed

4 files changed

+100
-60
lines changed

api/dbv1/get_playlists.sql.go

Lines changed: 25 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/dbv1/get_tracks.sql.go

Lines changed: 25 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/dbv1/queries/get_playlists.sql

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,24 @@ SELECT
7474
SELECT json_agg(
7575
json_build_object(
7676
'user_id', r.user_id::text,
77-
'repost_item_id', repost_item_id::text, -- this is redundant
77+
'repost_item_id', r.repost_item_id::text, -- this is redundant
7878
'repost_type', 'RepostType.track', -- some sqlalchemy bs
7979
'created_at', r.created_at -- this is not actually present in python response?
8080
)
8181
)
8282
FROM (
83-
SELECT user_id, repost_item_id, reposts.created_at
84-
FROM reposts
85-
JOIN my_follows USING (user_id)
86-
WHERE repost_item_id = p.playlist_id
87-
AND repost_type != 'track'
88-
AND reposts.is_delete = false
89-
ORDER BY follower_count DESC
83+
SELECT mf.user_id, lr.repost_item_id, lr.created_at, mf.follower_count
84+
FROM my_follows mf
85+
CROSS JOIN LATERAL (
86+
SELECT reposts.repost_item_id, reposts.created_at
87+
FROM reposts
88+
WHERE reposts.user_id = mf.user_id
89+
AND reposts.repost_item_id = p.playlist_id
90+
AND reposts.repost_type != 'track'
91+
AND reposts.is_delete = false
92+
LIMIT 1
93+
) lr
94+
ORDER BY mf.follower_count DESC
9095
LIMIT 6
9196
) r
9297
)::jsonb as followee_reposts,
@@ -101,13 +106,18 @@ SELECT
101106
)
102107
)
103108
FROM (
104-
SELECT user_id, save_item_id, saves.created_at
105-
FROM saves
106-
JOIN my_follows USING (user_id)
107-
WHERE save_item_id = p.playlist_id
108-
AND save_type != 'track'
109-
AND saves.is_delete = false
110-
ORDER BY follower_count DESC
109+
SELECT mf.user_id, ls.save_item_id, ls.created_at, mf.follower_count
110+
FROM my_follows mf
111+
CROSS JOIN LATERAL (
112+
SELECT saves.save_item_id, saves.created_at
113+
FROM saves
114+
WHERE saves.user_id = mf.user_id
115+
AND saves.save_item_id = p.playlist_id
116+
AND saves.save_type != 'track'
117+
AND saves.is_delete = false
118+
LIMIT 1
119+
) ls
120+
ORDER BY mf.follower_count DESC
111121
LIMIT 6
112122
) r
113123
)::jsonb as followee_favorites

api/dbv1/queries/get_tracks.sql

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -107,19 +107,24 @@ SELECT
107107
SELECT json_agg(
108108
json_build_object(
109109
'user_id', r.user_id::text,
110-
'repost_item_id', repost_item_id::text, -- this is redundant
110+
'repost_item_id', r.repost_item_id::text, -- this is redundant
111111
'repost_type', 'RepostType.track', -- some sqlalchemy bs
112112
'created_at', r.created_at -- this is not actually present in python response?
113113
)
114114
)
115115
FROM (
116-
SELECT user_id, repost_item_id, reposts.created_at
117-
FROM reposts
118-
JOIN my_follows USING (user_id)
119-
WHERE repost_item_id = t.track_id
120-
AND repost_type = 'track'
121-
AND reposts.is_delete = false
122-
ORDER BY follower_count DESC
116+
SELECT mf.user_id, lr.repost_item_id, lr.created_at, mf.follower_count
117+
FROM my_follows mf
118+
CROSS JOIN LATERAL (
119+
SELECT reposts.repost_item_id, reposts.created_at
120+
FROM reposts
121+
WHERE reposts.user_id = mf.user_id
122+
AND reposts.repost_item_id = t.track_id
123+
AND reposts.repost_type = 'track'
124+
AND reposts.is_delete = false
125+
LIMIT 1
126+
) lr
127+
ORDER BY mf.follower_count DESC
123128
LIMIT 3
124129
) r
125130
)::jsonb as followee_reposts,
@@ -134,13 +139,18 @@ SELECT
134139
)
135140
)
136141
FROM (
137-
SELECT user_id, save_item_id, saves.created_at
138-
FROM saves
139-
JOIN my_follows USING (user_id)
140-
WHERE save_item_id = t.track_id
141-
AND save_type = 'track'
142-
AND saves.is_delete = false
143-
ORDER BY follower_count DESC
142+
SELECT mf.user_id, ls.save_item_id, ls.created_at, mf.follower_count
143+
FROM my_follows mf
144+
CROSS JOIN LATERAL (
145+
SELECT saves.save_item_id, saves.created_at
146+
FROM saves
147+
WHERE saves.user_id = mf.user_id
148+
AND saves.save_item_id = t.track_id
149+
AND saves.save_type = 'track'
150+
AND saves.is_delete = false
151+
LIMIT 1
152+
) ls
153+
ORDER BY mf.follower_count DESC
144154
LIMIT 3
145155
) r
146156
)::jsonb as followee_favorites,

0 commit comments

Comments
 (0)