-
Notifications
You must be signed in to change notification settings - Fork 0
⚡ Bolt: Optimized playlist song fetching #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,7 @@ | ||
| ## 2026-02-10 - Atomic Database Updates | ||
| **Learning:** Read-modify-write patterns for counters (like `playCount`) cause race conditions and extra DB round trips. | ||
| **Action:** Use atomic SQL updates (e.g., `playCount = playCount + 1`) with `returning()` to ensure data integrity and performance. | ||
|
|
||
| ## 2026-02-25 - N+1 Query Optimization in Playlists | ||
| **Learning:** `getPlaylistWithSongs` was using an N+1 pattern (fetching IDs then fetching songs) which caused multiple DB round trips and required manual sorting. | ||
| **Action:** Replaced with a single `innerJoin` query using `getTableColumns` and `orderBy(playlistSongs.id)` to fetch songs in insertion order efficiently. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -160,24 +160,15 @@ export class DatabaseStorage implements IStorage { | |||||||||||||||||||||
| const playlist = await this.getPlaylist(id); | ||||||||||||||||||||||
| if (!playlist) return undefined; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const playlistSongRows = await db.select({ songId: playlistSongs.songId }) | ||||||||||||||||||||||
| .from(playlistSongs) | ||||||||||||||||||||||
| .where(eq(playlistSongs.playlistId, id)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const songIds = playlistSongRows.map(r => r.songId).filter(id => typeof id === 'number' && !isNaN(id)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (songIds.length === 0) { | ||||||||||||||||||||||
| return { ...playlist, songs: [] }; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const songsResult = await db.select() | ||||||||||||||||||||||
| // Optimized: Single query using innerJoin to fetch songs directly in insertion order (by playlistSongs.id) | ||||||||||||||||||||||
| // This replaces the previous N+1 pattern of fetching IDs then fetching songs | ||||||||||||||||||||||
| const songsList = await db.select(getTableColumns(songs)) | ||||||||||||||||||||||
| .from(songs) | ||||||||||||||||||||||
|
Comment on lines
+163
to
166
|
||||||||||||||||||||||
| .where(inArray(songs.id, songIds)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const songMap = new Map(songsResult.map(s => [s.id, s])); | ||||||||||||||||||||||
| const songsList = songIds.map(id => songMap.get(id)).filter((s): s is Song => !!s); | ||||||||||||||||||||||
| .innerJoin(playlistSongs, eq(songs.id, playlistSongs.songId)) | ||||||||||||||||||||||
| .where(eq(playlistSongs.playlistId, id)) | ||||||||||||||||||||||
| .orderBy(playlistSongs.id); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return { ...playlist, songs: songsList }; | ||||||||||||||||||||||
| return { ...playlist, songs: songsList as Song[] }; | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The To correctly map the result to an array of
Suggested change
Comment on lines
+169
to
+171
|
||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| async createPlaylist(insertPlaylist: InsertPlaylist): Promise<Playlist> { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment says this replaces an “N+1 pattern”, but the removed code path executed a fixed number of queries (fetch join-table rows, then fetch songs via
inArray). Consider updating the comment to describe it as a “two-step fetch” / “multiple round trips” to avoid misleading future readers.