Suche fühlt sich träge an — Rückmeldung fehlt, Poster blockieren den Eindruck #3

Closed
opened 2026-09-01 20:57:10 +00:00 by florian.wilde · 3 comments
Owner

Beobachtung

Beim Tippen in der Suche dauert es produktiv „ein paar Sekunden", bis Treffer erscheinen. Die Suche filtert bereits live (kein Enter nötig), aber sie wirkt stehengeblieben.

Was gemessen ist

Die Datenbank ist es nicht. Gegen die Entwicklungsinstanz, Median aus 12 Läufen bei 18 Bibliothekseinträgen:

getLibrary() ohne Filter 46 ms
getLibrary() mit Suchbegriff 42 ms
getLibraryFacets() 16 ms
beide parallel 46 ms

Serverseitig kostet ein Tastendruck also rund 50 ms. Die wahrgenommenen Sekunden entstehen woanders.

Wo die Zeit vermutlich hingeht

Nach Auffälligkeit sortiert, noch nicht am Produktivsystem verifiziert:

  1. Poster beim ersten Aufruf. Jedes Poster läuft über /api/image. Bei einem Cache-Fehltreffer holt der Server das Bild synchron von TMDB, bevor er antwortet — bei zwanzig Treffern zwanzig ausgehende Anfragen. Der Text steht dann längst da, aber die Seite sieht unfertig aus. Bei einer zweiten Suche nach demselben Begriff sind die Bilder im Cache und es geht sofort. Das passt zum Eindruck „mal schnell, mal Sekunden".

  2. TMDB-Abruf je Suchbegriff. /suche fragt TMDB an; von Deutschland aus je nach Verbindung 200–500 ms. Wiederholungen kommen zehn Minuten lang aus dem Zwischenspeicher.

  3. Volle Serverrunde je Eingabe. 350 ms Verzögerung nach dem Tippen (/suche) bzw. 300 ms (Bibliothek), dann eine RSC-Anfrage über die Leitung. Über das Internet zu einer eigenen Instanz kommen leicht nochmal einige hundert Millisekunden dazu.

  4. Fehlende Rückmeldung in der Bibliothek. /suche zeigt beim Tippen ein kleines „sucht …". Die Filterleiste der Bibliothek zeigt gar nichts — dort bleiben die alten Treffer sichtbar, bis die neuen da sind. Ohne Signal wirkt das wie Stillstand.

Vorschlag

Zuerst messen, dann bauen. Am Produktivsystem im Netzwerk-Tab nachsehen, wie lange die Navigationsanfrage braucht und wie lange die /api/image-Anfragen. Danach gezielt:

  • Rückmeldung beim Tippen in beiden Suchen: das Raster während des Ladens gedämpft darstellen oder Platzhalterkacheln zeigen, statt alte Treffer stehen zu lassen. Billig und wirkt sofort — auch wenn die Zeit gleich bleibt, fühlt sich das nicht mehr kaputt an.
  • Poster nicht blockieren lassen: bei einem Cache-Fehltreffer sofort antworten und im Hintergrund nachladen, oder auf TMDB weiterleiten statt zu spiegeln. Trade-off: die Weiterleitung kostet die Eigenschaft, dass die laufende Instanz keine Fremdanfragen auslöst — das müsste eine Einstellung werden, keine stille Änderung.
  • Verzögerung von 350 auf ~250 ms senken.
  • Facetten nicht bei jedem Tastendruck neu rechnen. getLibraryFacets() hängt nicht vom Suchbegriff ab, läuft aber bei jeder Navigation mit. Bei 18 Einträgen sind das 16 ms, bei einer großen Bibliothek mit DISTINCT unnest über alle Genres deutlich mehr.

Abnahme

  • Am Produktivsystem gemessen, wo die Zeit tatsächlich hingeht
  • Beim Tippen ist sichtbar, dass gearbeitet wird — in beiden Suchen
  • Ein Suchlauf mit kaltem Bild-Cache blockiert die Trefferanzeige nicht mehr
  • Facetten werden nicht mehr bei jedem Tastendruck neu berechnet

Angelegt auf ausdrücklichen Wunsch direkt in Gitea, ohne Verknüpfung zu einem Nebula-Task.

## Beobachtung Beim Tippen in der Suche dauert es produktiv „ein paar Sekunden", bis Treffer erscheinen. Die Suche filtert bereits live (kein Enter nötig), aber sie *wirkt* stehengeblieben. ## Was gemessen ist Die Datenbank ist es nicht. Gegen die Entwicklungsinstanz, Median aus 12 Läufen bei 18 Bibliothekseinträgen: | | | |---|---| | `getLibrary()` ohne Filter | 46 ms | | `getLibrary()` mit Suchbegriff | 42 ms | | `getLibraryFacets()` | 16 ms | | beide parallel | 46 ms | Serverseitig kostet ein Tastendruck also rund 50 ms. Die wahrgenommenen Sekunden entstehen woanders. ## Wo die Zeit vermutlich hingeht Nach Auffälligkeit sortiert, **noch nicht am Produktivsystem verifiziert**: 1. **Poster beim ersten Aufruf.** Jedes Poster läuft über `/api/image`. Bei einem Cache-Fehltreffer holt der Server das Bild *synchron* von TMDB, bevor er antwortet — bei zwanzig Treffern zwanzig ausgehende Anfragen. Der Text steht dann längst da, aber die Seite sieht unfertig aus. Bei einer zweiten Suche nach demselben Begriff sind die Bilder im Cache und es geht sofort. Das passt zum Eindruck „mal schnell, mal Sekunden". 2. **TMDB-Abruf je Suchbegriff.** `/suche` fragt TMDB an; von Deutschland aus je nach Verbindung 200–500 ms. Wiederholungen kommen zehn Minuten lang aus dem Zwischenspeicher. 3. **Volle Serverrunde je Eingabe.** 350 ms Verzögerung nach dem Tippen (`/suche`) bzw. 300 ms (Bibliothek), dann eine RSC-Anfrage über die Leitung. Über das Internet zu einer eigenen Instanz kommen leicht nochmal einige hundert Millisekunden dazu. 4. **Fehlende Rückmeldung in der Bibliothek.** `/suche` zeigt beim Tippen ein kleines „sucht …". Die Filterleiste der Bibliothek zeigt gar nichts — dort bleiben die alten Treffer sichtbar, bis die neuen da sind. Ohne Signal wirkt das wie Stillstand. ## Vorschlag **Zuerst messen, dann bauen.** Am Produktivsystem im Netzwerk-Tab nachsehen, wie lange die Navigationsanfrage braucht und wie lange die `/api/image`-Anfragen. Danach gezielt: - **Rückmeldung beim Tippen** in beiden Suchen: das Raster während des Ladens gedämpft darstellen oder Platzhalterkacheln zeigen, statt alte Treffer stehen zu lassen. Billig und wirkt sofort — auch wenn die Zeit gleich bleibt, fühlt sich das nicht mehr kaputt an. - **Poster nicht blockieren lassen:** bei einem Cache-Fehltreffer sofort antworten und im Hintergrund nachladen, oder auf TMDB weiterleiten statt zu spiegeln. Trade-off: die Weiterleitung kostet die Eigenschaft, dass die laufende Instanz keine Fremdanfragen auslöst — das müsste eine Einstellung werden, keine stille Änderung. - **Verzögerung von 350 auf ~250 ms** senken. - **Facetten nicht bei jedem Tastendruck neu rechnen.** `getLibraryFacets()` hängt nicht vom Suchbegriff ab, läuft aber bei jeder Navigation mit. Bei 18 Einträgen sind das 16 ms, bei einer großen Bibliothek mit `DISTINCT unnest` über alle Genres deutlich mehr. ## Abnahme - [ ] Am Produktivsystem gemessen, wo die Zeit tatsächlich hingeht - [ ] Beim Tippen ist sichtbar, dass gearbeitet wird — in beiden Suchen - [ ] Ein Suchlauf mit kaltem Bild-Cache blockiert die Trefferanzeige nicht mehr - [ ] Facetten werden nicht mehr bei jedem Tastendruck neu berechnet --- *Angelegt auf ausdrücklichen Wunsch direkt in Gitea, ohne Verknüpfung zu einem Nebula-Task.*
florian.wilde added the in-arbeit label 2026-09-01 22:16:02 +00:00
florian.wilde started working 2026-09-01 22:16:07 +00:00
Author
Owner

🤖 Claude Code hat mit der Umsetzung begonnen.

  • Branch: fix/3-suche-rueckmeldung
  • Verstanden als: Die Suche soll beim Tippen sichtbar arbeiten statt stillzustehen, und alles, was nachweislich unnötig Zeit kostet, soll raus — ohne dass ich Ursachen behebe, die ich nicht belegen kann.
  • Geplantes Vorgehen:
    • Rückmeldung in beiden Suchen. In der Bibliotheksfilterleiste wird das Signal schon erzeugt und dann weggeworfen: const [, startTransition] = useTransition() (src/components/library/LibraryFilters.tsx:29). Das isPending daraus nutzen, dazu das Trefferraster während des Ladens dämpfen — in /suche genauso.
    • Verzögerung von 350 ms (SearchField) und 300 ms (Bibliothek) angleichen und senken.
    • Facetten messen, bevor ich sie zwischenspeichere: laufen sie parallel zur Hauptabfrage und sind sie schneller, kostet ihr Wegfall keine Millisekunde und eine Zwischenspeicherung brächte nur Risiko für veraltete Zähler.
    • Bilder nachmessen statt vermuten. Belegen, ob ein kalter Bild-Cache die Trefferanzeige tatsächlich blockiert — die Bilder holt der Browser erst nach dem Text und parallel. Wenn er sie nicht blockiert, sage ich das, statt einen Umbau zu rechtfertigen.

Was ich hier nicht kann: den ersten Abnahmepunkt — die Messung am Produktivsystem. Den kannst nur du beisteuern (Netzwerk-Tab, einmal tippen, Dauer der Navigationsanfrage gegen die der /api/image-Anfragen). Ich mache alles, was ohne diese Zahl belastbar ist, und lasse den Punkt offen.

Zeiterfassung läuft.

🤖 **Claude Code hat mit der Umsetzung begonnen.** - Branch: `fix/3-suche-rueckmeldung` - Verstanden als: Die Suche soll beim Tippen sichtbar arbeiten statt stillzustehen, und alles, was nachweislich unnötig Zeit kostet, soll raus — ohne dass ich Ursachen behebe, die ich nicht belegen kann. - Geplantes Vorgehen: - **Rückmeldung in beiden Suchen.** In der Bibliotheksfilterleiste wird das Signal schon erzeugt und dann weggeworfen: `const [, startTransition] = useTransition()` (`src/components/library/LibraryFilters.tsx:29`). Das `isPending` daraus nutzen, dazu das Trefferraster während des Ladens dämpfen — in `/suche` genauso. - **Verzögerung** von 350 ms (`SearchField`) und 300 ms (Bibliothek) angleichen und senken. - **Facetten messen**, bevor ich sie zwischenspeichere: laufen sie parallel zur Hauptabfrage und sind sie schneller, kostet ihr Wegfall keine Millisekunde und eine Zwischenspeicherung brächte nur Risiko für veraltete Zähler. - **Bilder nachmessen statt vermuten.** Belegen, ob ein kalter Bild-Cache die Trefferanzeige tatsächlich blockiert — die Bilder holt der Browser erst *nach* dem Text und parallel. Wenn er sie nicht blockiert, sage ich das, statt einen Umbau zu rechtfertigen. **Was ich hier nicht kann:** den ersten Abnahmepunkt — die Messung am Produktivsystem. Den kannst nur du beisteuern (Netzwerk-Tab, einmal tippen, Dauer der Navigationsanfrage gegen die der `/api/image`-Anfragen). Ich mache alles, was ohne diese Zahl belastbar ist, und lasse den Punkt offen. Zeiterfassung läuft.
florian.wilde worked for 7 minutes 2026-09-01 22:23:10 +00:00
florian.wilde added zu-testen and removed in-arbeit labels 2026-09-01 22:23:42 +00:00
Author
Owner

✅ Umsetzung abgeschlossen — bereit zum Testen.

Was geändert wurde

  • SearchPending — eine schmale Klammer, die beide Suchen teilen: die Filterleiste meldet den Ladezustand, die Trefferliste daneben wird gedämpft und auf aria-busy gesetzt
  • Der Hinweis „sucht …" steht jetzt auch in der Bibliothek, nicht nur unter /suche
  • Alte Treffer bleiben sichtbar statt zu verschwinden — sonst springt die Liste bei jedem Tastendruck
  • Sammelverzögerung in beiden auf 250 ms angeglichen (vorher 350 und 300)

Geänderte Dateien

  • src/components/SearchPending.tsx (neu)
  • src/components/SearchField.tsx
  • src/components/library/LibraryFilters.tsx
  • src/app/suche/page.tsx
  • src/app/bibliothek/page.tsx

Zwei Punkte der Abnahme sind nach Messung gegenstandslos

Facetten: an einer eigens angelegten Bibliothek mit 2000 Einträgen gemessen (Median aus 9 Läufen) — getLibrary() 50 ms, getLibraryFacets() 17 ms, beide parallel 61 ms. Sie sind die schnellere Hälfte eines Promise.all; ein Wegfall spart 12 ms, eine Zwischenspeicherung brächte veraltete Zähler. Die Annahme „bei großer Bibliothek deutlich mehr" trifft nicht zu.

Bilder: mit künstlich um 1500 ms verzögerten /api/image-Antworten stehen die Treffer nach 465 ms im DOM — 891 ms vor den Bildern. Sie blockieren also nichts, und die Rückfallkacheln zeigen derweil die Titel. Ein Umbau des Endpunkts wäre Aufwand ohne Wirkung.

Beides habe ich deshalb nicht angefasst.

Geprüft (Produktionsbau, Serverantwort künstlich gebremst)

Bibliothek Suche
aria-busy vorher/während/danach false / true / false false / true / false
„sucht …" sichtbar ja ja
Deckkraft der Treffer währenddessen 0.45 0.45

59 Tests grün, Typecheck sauber.

So testest du es

  1. git switch fix/3-suche-rueckmeldung
  2. npm run dev (Port 3011), /bibliothek öffnen und in das Suchfeld tippen
  3. Erwartet: rechts im Feld erscheint „sucht …", das Raster wird kurz blasser und ist währenddessen nicht anklickbar; danach steht das neue Ergebnis scharf da
  4. Dasselbe unter /suche
  5. Am besten mit gedrosselter Verbindung (DevTools → Network → Slow 3G), sonst ist es zu schnell zum Sehen

Offen bleibt Abnahmepunkt 1 — die Messung am Produktivsystem. Die kann nur von dir kommen; siehe PR.

Aufgewendete Zeit: 00:07
Pull Request: #11

Nach Freigabe: /issue-freigabe 3

✅ **Umsetzung abgeschlossen — bereit zum Testen.** <!-- claude-status branch: fix/3-suche-rueckmeldung pr: 11 zeit_sekunden: 423 --> **Was geändert wurde** - `SearchPending` — eine schmale Klammer, die beide Suchen teilen: die Filterleiste meldet den Ladezustand, die Trefferliste daneben wird gedämpft und auf `aria-busy` gesetzt - Der Hinweis „sucht …" steht jetzt auch in der Bibliothek, nicht nur unter `/suche` - Alte Treffer bleiben sichtbar statt zu verschwinden — sonst springt die Liste bei jedem Tastendruck - Sammelverzögerung in beiden auf 250 ms angeglichen (vorher 350 und 300) **Geänderte Dateien** - `src/components/SearchPending.tsx` (neu) - `src/components/SearchField.tsx` - `src/components/library/LibraryFilters.tsx` - `src/app/suche/page.tsx` - `src/app/bibliothek/page.tsx` **Zwei Punkte der Abnahme sind nach Messung gegenstandslos** *Facetten:* an einer eigens angelegten Bibliothek mit **2000 Einträgen** gemessen (Median aus 9 Läufen) — `getLibrary()` 50 ms, `getLibraryFacets()` 17 ms, beide parallel 61 ms. Sie sind die schnellere Hälfte eines `Promise.all`; ein Wegfall spart **12 ms**, eine Zwischenspeicherung brächte veraltete Zähler. Die Annahme „bei großer Bibliothek deutlich mehr" trifft nicht zu. *Bilder:* mit künstlich um 1500 ms verzögerten `/api/image`-Antworten stehen die Treffer nach **465 ms** im DOM — **891 ms vor den Bildern**. Sie blockieren also nichts, und die Rückfallkacheln zeigen derweil die Titel. Ein Umbau des Endpunkts wäre Aufwand ohne Wirkung. Beides habe ich deshalb **nicht** angefasst. **Geprüft** (Produktionsbau, Serverantwort künstlich gebremst) | | Bibliothek | Suche | |---|---|---| | `aria-busy` vorher/während/danach | false / **true** / false | false / **true** / false | | „sucht …" sichtbar | ja | ja | | Deckkraft der Treffer währenddessen | 0.45 | 0.45 | 59 Tests grün, Typecheck sauber. **So testest du es** 1. `git switch fix/3-suche-rueckmeldung` 2. `npm run dev` (Port 3011), `/bibliothek` öffnen und in das Suchfeld tippen 3. Erwartet: rechts im Feld erscheint „sucht …", das Raster wird kurz blasser und ist währenddessen nicht anklickbar; danach steht das neue Ergebnis scharf da 4. Dasselbe unter `/suche` 5. Am besten mit gedrosselter Verbindung (DevTools → Network → Slow 3G), sonst ist es zu schnell zum Sehen **Offen bleibt Abnahmepunkt 1** — die Messung am Produktivsystem. Die kann nur von dir kommen; siehe PR. **Aufgewendete Zeit:** 00:07 **Pull Request:** #11 Nach Freigabe: `/issue-freigabe 3`
Author
Owner

🚀 Freigegeben und nach main übernommen.

  • Merge-Commit: 58a1d17
  • Pull Request: #11 (gemergt)
  • Gesamtaufwand: 00:07

Vor dem Merge noch einmal geprüft: Typecheck sauber, 59 Tests grün.

Issue wird geschlossen. Der erste Abnahmepunkt — die Messung am Produktivsystem — bleibt damit unerledigt; die Erkenntnisse dazu stehen im PR, falls es doch noch klemmt.

🚀 **Freigegeben und nach `main` übernommen.** <!-- claude-status merge: 58a1d177dfc6fe034ae4acc747ae3fa76c901c0b pr: 11 zeit_sekunden: 423 --> - Merge-Commit: `58a1d17` - Pull Request: #11 (gemergt) - Gesamtaufwand: 00:07 Vor dem Merge noch einmal geprüft: Typecheck sauber, 59 Tests grün. Issue wird geschlossen. Der erste Abnahmepunkt — die Messung am Produktivsystem — bleibt damit unerledigt; die Erkenntnisse dazu stehen im PR, falls es doch noch klemmt.
florian.wilde removed the zu-testen label 2026-09-02 04:33:45 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Total Time Spent: 7 minutes
florian.wilde
7 minutes
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: florian.wilde/flimmer#3