From d0fc19f0cc168a8bba3be2a87620b3b4aa5a808c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 13:12:11 +0000 Subject: [PATCH] perf: replace JOINs with scalar subqueries for library fetching Co-authored-by: jspann21 <179991454+jspann21@users.noreply.github.com> --- .jules/bolt.md | 3 +++ src-tauri/src/library/db.rs | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..1f9e42c --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2026-06-06 - [N+1 DB Query Optimization in Library Search] +**Learning:** Found an N+1 query pattern where fetching library books and hidden books was performing multiple JOINs combined with 'GROUP BY b.id' which created performance bottlenecks. We could push the grouped distinct values down into scalar subqueries on the SELECT clause which was significantly faster. +**Action:** Replace JOIN + GROUP BY combinations with scalar subqueries using '(SELECT COALESCE(group_concat(DISTINCT ...)))' in SQLite when fetching wide lists. diff --git a/src-tauri/src/library/db.rs b/src-tauri/src/library/db.rs index ebc369f..05e0a4e 100644 --- a/src-tauri/src/library/db.rs +++ b/src-tauri/src/library/db.rs @@ -1768,16 +1768,16 @@ impl Repository { }; let mut list_values = values; + // Optimization: Replaced LEFT JOINs and GROUP BY with scalar subqueries to avoid an N+1 Cartesian product issue + // and significantly improve performance during sorting and pagination for large libraries. let mut list_sql = format!( - "SELECT b.id, b.title, b.authors_json, b.publisher, b.publish_date, b.cover_url, b.cover_local_path, b.confidence, COALESCE(group_concat(DISTINCT bf.format),''), COUNT(DISTINCT bf.file_id), COUNT(DISTINCT CASE WHEN f.status='missing' THEN f.id END), - COALESCE(group_concat(DISTINCT t.label), '') + "SELECT b.id, b.title, b.authors_json, b.publisher, b.publish_date, b.cover_url, b.cover_local_path, b.confidence, + (SELECT COALESCE(group_concat(DISTINCT bf.format), '') FROM book_files bf WHERE bf.book_id = b.id), + (SELECT COUNT(DISTINCT bf.file_id) FROM book_files bf WHERE bf.book_id = b.id), + (SELECT COUNT(DISTINCT f.id) FROM book_files bf JOIN files f ON f.id = bf.file_id WHERE bf.book_id = b.id AND f.status='missing'), + (SELECT COALESCE(group_concat(DISTINCT t.label), '') FROM book_tags bt JOIN tags t ON t.id = bt.tag_id WHERE bt.book_id = b.id) FROM books b - LEFT JOIN book_files bf ON bf.book_id = b.id - LEFT JOIN files f ON f.id = bf.file_id - LEFT JOIN book_tags bt ON bt.book_id = b.id - LEFT JOIN tags t ON t.id = bt.tag_id WHERE {where_sql} - GROUP BY b.id ORDER BY {sort_column} {sort_direction}, b.title ASC" ); if let Some((_, normalized_page_size, offset)) = pagination { @@ -1889,16 +1889,16 @@ impl Repository { list_values.push(Value::from(page_size as i64)); list_values.push(Value::from(offset as i64)); + // Optimization: Replaced LEFT JOINs and GROUP BY with scalar subqueries to avoid an N+1 Cartesian product issue + // and significantly improve performance during sorting and pagination for large libraries. let list_sql = format!( - "SELECT b.id, b.title, b.authors_json, b.publisher, b.publish_date, b.cover_url, b.cover_local_path, b.confidence, COALESCE(group_concat(DISTINCT bf.format),''), COUNT(DISTINCT bf.file_id), COUNT(DISTINCT CASE WHEN f.status='missing' THEN f.id END), - COALESCE(group_concat(DISTINCT t.label), '') + "SELECT b.id, b.title, b.authors_json, b.publisher, b.publish_date, b.cover_url, b.cover_local_path, b.confidence, + (SELECT COALESCE(group_concat(DISTINCT bf.format), '') FROM book_files bf WHERE bf.book_id = b.id), + (SELECT COUNT(DISTINCT bf.file_id) FROM book_files bf WHERE bf.book_id = b.id), + (SELECT COUNT(DISTINCT f.id) FROM book_files bf JOIN files f ON f.id = bf.file_id WHERE bf.book_id = b.id AND f.status='missing'), + (SELECT COALESCE(group_concat(DISTINCT t.label), '') FROM book_tags bt JOIN tags t ON t.id = bt.tag_id WHERE bt.book_id = b.id) FROM books b - LEFT JOIN book_files bf ON bf.book_id = b.id - LEFT JOIN files f ON f.id = bf.file_id - LEFT JOIN book_tags bt ON bt.book_id = b.id - LEFT JOIN tags t ON t.id = bt.tag_id WHERE {where_sql} - GROUP BY b.id ORDER BY b.updated_at DESC, b.title ASC LIMIT ? OFFSET ?" );