novela/docs/TODO-PERF-library-load.md
Ivo Oskamp 00e75a6106 Add duplicate detection, Convert warning, and performance TODO
- Convert: warn when title+author already exists in library (preload check)
- Library: Duplicates sidebar section with grouped view and live counter
- Fix: Duplicates view cover loading now uses same canvas/two-pass pattern as renderBooksGrid
- Docs: add TODO-PERF-library-load.md with four identified bottlenecks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:22:02 +01:00

53 lines
2.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TODO: Performance improvements — Library "All Books" load speed
Observed: loading all books (especially on hard refresh CTRL+F5) is slow.
Root cause analysis identified four bottlenecks, ordered by impact.
---
### 1. Lazy cover loading (HIGH impact)
**Problem:** On every full render, a `<img>` is created immediately for every book card that has a cover. With 500+ books this fires 500+ simultaneous HTTP requests to `/library/cover-cached/`. The browser throttles these and the server gets hammered.
**Fix:** Use an `IntersectionObserver` to defer image loading until a card scrolls into the viewport. Cards outside the viewport stay as canvas placeholders until they appear.
**Affected:** `static/library.js``renderBooksGrid()` second pass (lines ~9811005)
---
### 2. HTTP caching on `/api/library` (HIGH impact)
**Problem:** Every refresh (including soft refresh) fetches the full book list JSON from the server with no caching. No `ETag`, `Last-Modified` or `Cache-Control` headers are set.
**Fix:** Add an `ETag` header based on a hash of the serialized response (or a DB row count + last `updated_at`). Browser sends `If-None-Match`; server returns `304 Not Modified` when nothing changed — zero data transfer.
**Affected:** `routers/library.py``GET /api/library` endpoint; `static/library.js``loadLibrary()`
---
### 3. Double DOM pass in renderBooksGrid (MEDIUM impact)
**Problem:** `renderBooksGrid` makes two full iterations over the book list:
1. Build all card HTML and append to the grid.
2. Query the DOM again for each `canvas-*` and `wrap-*` element to set up image loading.
This causes two reflows and 500+ `getElementById` calls after the DOM is already populated.
**Fix:** Combine both passes into one: build the card element, immediately set up the `<img>` element and canvas, then append. No second iteration needed.
**Affected:** `static/library.js``renderBooksGrid()` (lines ~9041005)
---
### 4. Tags via separate query + Python merge (LOW impact)
**Problem:** `list_library_json()` fetches book tags via a separate `SELECT * FROM book_tags` query and then merges them in Python using a dict. For large libraries this means two round-trips and an O(n) in-process merge.
**Fix:** Use a PostgreSQL JSON aggregation in the main query:
```sql
COALESCE(
json_agg(json_build_object('tag', bt.tag, 'tag_type', bt.tag_type))
FILTER (WHERE bt.tag IS NOT NULL),
'[]'
) AS tags
```
This returns tags inline per book row, eliminating the second query and the Python merge loop.
**Affected:** `routers/common.py``list_library_json()` (lines ~397458)