Ein Dashboard-Aufruf feuert acht Requests. Im Log kamen alle in derselben
Millisekunde zurueck, /auth/me mit 1116ms — ein Request, der eine Zeile liest.
Nichts davon war fuer sich langsam: einzeln gemessen kosten die acht zusammen
355ms, die Seite brauchte trotzdem so lange wie ihre Summe statt so lange wie
ihr teuerster Request.
Die Handler waren `async def`, obwohl jede Zeile darin eine blockierende
SQLAlchemy-Session benutzt. FastAPI fuehrt `async def` auf dem Event-Loop
selbst aus, also hielt jeder Handler den einzigen Loop fuer die Dauer seines
kompletten Query-Batches fest — und `get_current_user` haengt vor jedem
authentifizierten Request, deshalb war auch der Ein-Zeilen-Request betroffen.
111 solcher Handler, keiner davon mit einem einzigen `await`, sind jetzt
plain `def` und laufen im Worker-Threadpool, wo Blockieren vorgesehen ist.
Damit sie dort auch eine Verbindung finden, deckt der Connection-Pool jetzt
die 40 Threads ab, die FastAPI vergibt, statt 30: die 31. gleichzeitige
Anfrage haette nicht auf eine langsame Query gewartet, sondern auf eine
Connection, und das ist ein Timeout, keine Langsamkeit.
Zweiter Posten auf derselben Seite: kev-recent baute den zusammengefuehrten
KEV-Katalog bei jedem Request neu — 2 MB JSON aus `settings` parsen und
mergen, zweimal pro Seitenaufruf, fuer einen Katalog der sich einmal am Tag
aendert. Jetzt memoisiert, und zwar auf den `_updated_at`-Werten der
Quell-Caches statt auf einer Uhr, damit "jetzt aktualisieren, Seite neu laden"
weiter den neuen Katalog zeigt. Das Datumsparsen laeuft ueber fromisoformat
statt strptime; mit 9100 Aufrufen pro Request war es der Hotspot im Merge.
Gemessen mit acht gleichzeitigen Requests gegen 36.000 Findings auf 300
Assets: Seitenaufruf 420ms -> 190ms, Event-Loop blockiert 250ms -> 50ms,
/auth/me unter Last 304ms -> 91ms. Assets-/Scans-Seite 146ms -> 108ms.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>