fix: ograniczenie kolejki roboczej DOI — koniec z OOMKill librariana #15

Merged
gitea merged 1 commits from fix-librarian-oom-workq into main 2026-08-02 17:37:54 +00:00
Owner

Objaw

Pod librariana restartował się spontanicznie w trakcie wyszukania i całe wyszukanie ginęło. To nie żaden bug logiczny — to OOMKilled: librarian nie ma liveness/readiness probe (więc nie sonda go ubija), a limit pamięci to 1Gi.

Przyczyna (smoking gun)

search_bot.search_for_doi tworzył kolejkę roboczą Queue(maxsize=35_500_000). Producenci (po jednym na plik-chunk) wlewają całą bazę DOI (dziesiątki mln linii) do tej kolejki, a garstka konsumentów ją opróżnia. Przy 35,5 mln linii × ~100 B to ~3,5 GB buforu — kolejka wysadzała kontener 1Gi na długo przed osiągnięciem limitu, zabierając ze sobą trwające wyszukanie.

Fix

  • Ograniczona kolejka: domyślnie 100k linii (env CONJURER_LIBRARIAN_WORKQ_SIZE) → producenci robią backpressure do konsumentów, RAM spada do kilku-kilkunastu MB. Zero wpływu na poprawność (wyszukanie i tak się kończy, producent tylko czeka na miejsce).
  • Producent odporny na deadlock: skoro przy ograniczonej kolejce producent może zablokować się na pełnej kolejce, jego put ma teraz timeout i dopytuje o sentinel TERM — więc pełna kolejka, której konsumenci już skończyli (wszystkie DOI znalezione), nie zakleszcza go.

Test

test_bounded_queue_does_not_deadlock_on_early_termination: malutka kolejka (3) + cel w pierwszej linii + 5000 śmieci po nim → i tak kończy się i znajduje cel. Suite: 56 unit + 45 integration zielone.

Sprawdź na żywo, że to był OOM

kubectl -n conjurer get pod -l app=librarian -o jsonpath='{.items[0].status.containerStatuses[0].lastState.terminated.reason}'; echo

(→ OOMKilled), oraz kubectl -n conjurer top pod w trakcie wyszukania.

Uwaga: kolejne potencjalne źródła pamięci (osobno)

  1. Deep search pobiera do 15000 rekordów Crossref na raz do RAM.
  2. Pliki cr_results/rr_results/s_results/not_in_db.json akumulują każdy wynik na zawsze i są w całości json.load-owane przy każdym wyszukaniu → rosnący RAM i dysk. Warto to potem obciąć.
    Proponuję headroom na pamięć (deploy) jako bufor + ewentualnie osobny PR na (2).

🤖 Generated with Claude Code

## Objaw Pod librariana **restartował się spontanicznie w trakcie wyszukania** i całe wyszukanie ginęło. To nie żaden bug logiczny — to **OOMKilled**: librarian nie ma liveness/readiness probe (więc nie sonda go ubija), a limit pamięci to 1Gi. ## Przyczyna (smoking gun) `search_bot.search_for_doi` tworzył kolejkę roboczą `Queue(maxsize=35_500_000)`. Producenci (po jednym na plik-chunk) wlewają **całą bazę DOI** (dziesiątki mln linii) do tej kolejki, a garstka konsumentów ją opróżnia. Przy 35,5 mln linii × ~100 B to **~3,5 GB** buforu — kolejka wysadzała kontener 1Gi na długo przed osiągnięciem limitu, zabierając ze sobą trwające wyszukanie. ## Fix - **Ograniczona kolejka**: domyślnie 100k linii (env `CONJURER_LIBRARIAN_WORKQ_SIZE`) → producenci robią backpressure do konsumentów, RAM spada do kilku-kilkunastu MB. Zero wpływu na poprawność (wyszukanie i tak się kończy, producent tylko czeka na miejsce). - **Producent odporny na deadlock**: skoro przy ograniczonej kolejce producent może zablokować się na pełnej kolejce, jego `put` ma teraz timeout i **dopytuje o sentinel TERM** — więc pełna kolejka, której konsumenci już skończyli (wszystkie DOI znalezione), nie zakleszcza go. ## Test `test_bounded_queue_does_not_deadlock_on_early_termination`: malutka kolejka (3) + cel w pierwszej linii + 5000 śmieci po nim → i tak kończy się i znajduje cel. Suite: 56 unit + 45 integration zielone. ## Sprawdź na żywo, że to był OOM ```bash kubectl -n conjurer get pod -l app=librarian -o jsonpath='{.items[0].status.containerStatuses[0].lastState.terminated.reason}'; echo ``` (→ `OOMKilled`), oraz `kubectl -n conjurer top pod` w trakcie wyszukania. ## Uwaga: kolejne potencjalne źródła pamięci (osobno) 1. Deep search pobiera do 15000 rekordów Crossref na raz do RAM. 2. Pliki `cr_results/rr_results/s_results/not_in_db.json` **akumulują każdy wynik na zawsze** i są w całości `json.load`-owane przy każdym wyszukaniu → rosnący RAM i dysk. Warto to potem obciąć. Proponuję headroom na pamięć (deploy) jako bufor + ewentualnie osobny PR na (2). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gitea self-assigned this 2026-08-02 17:35:28 +00:00
gitea added 1 commit 2026-08-02 17:35:28 +00:00
Librarian: bound the DOI search work queue to stop OOM-killing the pod
CI / compile (pull_request) Successful in 9s
CI / unit (pull_request) Successful in 17s
CI / integration (pull_request) Successful in 26s
build / build (push) Successful in 23s
CI / compile (push) Successful in 7s
CI / unit (push) Successful in 22s
CI / integration (push) Successful in 26s
40605b959f
The pod restarted spontaneously mid-search (no liveness probe is set, so
it was the kernel OOM-killer against the 1Gi limit). Cause: search_bot
built its work queue with maxsize 35_500_000. The producers stream the
WHOLE DOI database (tens of millions of lines across chunks) into it while
a few consumers drain, so the queue could buffer gigabytes of lines -
blowing the 1Gi container and taking the whole in-flight search with it.

Bound the queue (default 100k lines, env CONJURER_LIBRARIAN_WORKQ_SIZE),
so producers backpressure to consumers and RAM stays in the low MB.
Because a bounded queue means a producer can now block on a FULL queue,
make the producer's put timeout-poll the TERM sentinel, so a full queue
whose consumers have already finished (all DOIs found) can never deadlock
it. New test pins that: tiny queue + target on line 1 + thousands of
trailing decoys still terminates and finds the target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gitea merged commit 40605b959f into main 2026-08-02 17:37:54 +00:00
gitea deleted branch fix-librarian-oom-workq 2026-08-02 17:37:54 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gitea/conjurer#15