Batching /api/embed juz istnial (Krok 1 fazy mailowej, batch 64). Recon przed Etapem B wykazal w torze backfillu blad blokujacy i dwie luki. BUG (blokujacy dla Etapu B): flush_embed_buffer lapal wylacznie aiohttp.ClientError, a wyczerpanie ClientTimeout(total=...) rzuca goly builtins.TimeoutError, ktory NIE jest jego podklasa (zweryfikowane empirycznie na aiohttp 3.14.3). Zawieszona Ollama — czyli jej udokumentowany failure mode, "przyjmuje polaczenie i milczy" — wywalala caly run nieobsluzonym wyjatkiem, bez breakera i bez flushu threadingu. Na plastrze 50k = utrata zarobionej pracy. Klasy przejsciowe nazwane teraz jawnie w TRANSIENT_EMBED_ERRORS. kb-retrieval: - embed_batch(timeout_s=...) — bound per zadanie, skalowalny z batch size - embed_batch_resilient() — retry z backoffem wykladniczym, a po ich wyczerpaniu probe /api/tags rozstrzyga: backend zywy -> bisekcja izolujaca trujacy chunk (jeden zly tekst kosztowal caly batch 64, bo /api/embed jest all-or-nothing); backend martwy -> natychmiastowe gave_up bez bisekcji, ktora spalilaby 2n-1 zadan i opoznila breaker. EmbeddingDimensionError nigdy nie jest retry'owane. - failed_indices wyprowadzane z wyniku, nie akumulowane per span — przy gave_up w srodku bisekcji porzucone poddrzewo nigdy nie dochodzi do liscia. mail-body-ingest: - breaker liczy give-upy (backend padl), nie dowolne nieudane batche; porazka czesciowa przy zywym backendzie nie przesuwa licznika, bo te chunki i tak zlapie kolejny run przez idempotencje - wiersze zembedowane w umierajacym batchu sa commitowane przed abortem - parametryzacja: --batch-size/--embed-retries/--embed-backoff/--embed-timeout, kazdy z odpowiednikiem env MAIL_INGEST_*; bledna wartosc env = glosny SystemExit - metryka embed_ms_per_chunk (porownywalna miedzy runami, w odroznieniu od sredniej per batch) + embed_requests_total/embed_calls jako sygnal zdrowia mail-body-ingest-bench: nowy entry point, sweep batch size na realnych chunkach. Read-only (SELECT + inferencja, zero sciezki zapisu), warmup przed pomiarem, ten sam zbior chunkow dla kazdego rozmiaru. Czyni liczby z planu §1.4 odtwarzalnymi. Fallback SOLARIA->PIHA dla backfillu SWIADOMIE nie powstaje (potwierdzone przez operatora): 271k chunkow x 790 ms CPU ~ 60 h na 8 GB PIHA dzielonym z HA i Paperlessem. Wlasciwa odpowiedzia na martwy backend jest exit 2 i wznowienie plastra. Tor online (kb-query -> embed_router) zachowuje fallback — rozdzial torow udokumentowany w docstringu embed.py i w kb/services/. Testy: 117 zielonych (62 job + 22 klient embed + reszta pakietow), w tym regresja na TimeoutError, bisekcja, ograniczony koszt przy martwym backendzie i porazka czesciowa nieprzesuwajaca breakera. Bez uruchamiania backfillu. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
94 lines
4.1 KiB
Markdown
94 lines
4.1 KiB
Markdown
---
|
|
okf: "0.1"
|
|
type: runbook
|
|
visibility: private
|
|
status: active
|
|
updated: 2026-08-05
|
|
links:
|
|
- ../services/job-mail-body-ingest.md
|
|
---
|
|
|
|
# mail-body-ingest — uruchomienie i testy
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
# Dry run (default) — parse, quote-strip, classify, chunk, count. Zero Ollama calls, zero
|
|
# DB writes (including the threading UPDATE):
|
|
mail-body-ingest --dsn postgresql://kb:<pw>@piha:5433/kb --archive-root /home/oskar/kb/mail/archive
|
|
|
|
# Etap A pilot — last 12 months only (plan Decyzja 9):
|
|
mail-body-ingest --dsn ... --since 2025-07-01 --apply > mail-ingest-etapA.log 2>&1
|
|
|
|
# Etap B — full archive in 50k slices (plan §9; ORDER BY id is stable, so slices are
|
|
# reproducible, and idempotency covers their boundaries):
|
|
nice -n 10 ionice -c2 -n7 mail-body-ingest --dsn ... --apply \
|
|
--limit 50000 --offset 0 > mail-ingest-etapB-0.log 2>&1
|
|
|
|
# Smoke-test slice:
|
|
mail-body-ingest --dsn ... --apply --limit 10
|
|
```
|
|
|
|
DSN can also come from `KB_DSN`, Ollama URL from `OLLAMA_URL` (default
|
|
`http://localhost:11434` — this job is meant to run where Ollama lives).
|
|
|
|
## Embed tuning
|
|
|
|
| Flag | Env | Default | Notes |
|
|
|---|---|---|---|
|
|
| `--batch-size` | `MAIL_INGEST_BATCH_SIZE` | 64 | `/api/embed` batch. 64 measured best across the corpus's length mix; short-text-heavy slices favour 128 (plan §1.4) |
|
|
| `--embed-retries` | `MAIL_INGEST_EMBED_RETRIES` | 2 | Retries per batch on transport errors; `0` disables |
|
|
| `--embed-backoff` | `MAIL_INGEST_EMBED_BACKOFF` | 1.0 s | Base backoff, doubled each attempt |
|
|
| `--embed-timeout` | `MAIL_INGEST_EMBED_TIMEOUT` | 120 s | Per-request hard timeout — scale it with `--batch-size` |
|
|
| `--max-embed-failures` | — | 5 | Consecutive give-ups before aborting with exit 2; `0` disables |
|
|
|
|
A malformed env value is a startup failure, not a silent fallback — a typo'd
|
|
`MAIL_INGEST_BATCH_SIZE` must not quietly produce a multi-hour run at the wrong batch size.
|
|
|
|
## Batch-size benchmark
|
|
|
|
Read-only (`SELECT`s + inference, no write path at all), so it is safe against the live DB:
|
|
|
|
```bash
|
|
mail-body-ingest-bench --dsn postgresql://kb:<pw>@piha:5433/kb \
|
|
--archive-root /home/oskar/kb/mail/archive --sample-envelopes 200
|
|
|
|
# Wider sweep, capped so batch=1 doesn't dominate the wall clock:
|
|
mail-body-ingest-bench --dsn ... --sizes 1,8,32,64,128 --max-chunks 300
|
|
```
|
|
|
|
Prints ms/chunk, chunks/s and a projected full-corpus wall clock per batch size. Re-run it
|
|
after an Ollama upgrade or on a different GPU before trusting the default `--batch-size`.
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
pip install -e "jobs/mail-body-ingest[dev]"
|
|
cd jobs/mail-body-ingest && pytest
|
|
```
|
|
|
|
Pure unit tests (62), no DB/Ollama — `run()` is tested by monkeypatching `asyncpg.connect`
|
|
and `aiohttp.ClientSession` with in-memory fakes, `.eml` bytes written to `tmp_path`. Covers:
|
|
quote-strip (EN/PL/Outlook markers, bare `>` lines), HTML->text (style/script/blockquote/
|
|
gmail_quote skipping), newsletter classification, threading extraction, prefix building,
|
|
body extraction (plain-preferred, HTML fallback, attachment-only), the typed/compat32 parse
|
|
fallback, stats balance, idempotency (second run inserts nothing new), newsletter chunks
|
|
never reaching Ollama, dimension-mismatch abort, and the circuit breaker (trips on N
|
|
consecutive give-ups, resets on a success, disabled by `0`, flushes pending threading on
|
|
abort). Batch-specific: a poison chunk isolated out of a full batch, a partial failure not
|
|
advancing the breaker, and an Ollama hang degrading to the breaker instead of crashing the run.
|
|
|
|
The batch client itself is tested in `packages/kb-retrieval/tests/test_embed.py` (22) — retry
|
|
with backoff, bisection, dead-backend give-up bounds, and the `TimeoutError`-is-not-a-
|
|
`ClientError` regression. The benchmark's pure logic (batch splitting, derived metrics, table
|
|
formatting, failure counting) is in `jobs/mail-body-ingest/tests/test_benchmark.py`.
|
|
|
|
In a worktree without a preinstalled venv:
|
|
|
|
```bash
|
|
python3 -m venv /tmp/kbvenv && /tmp/kbvenv/bin/pip install -q pytest pytest-asyncio \
|
|
-e packages/kb-mail/ -e packages/kb-retrieval/ -e jobs/mail-body-ingest/
|
|
/tmp/kbvenv/bin/python -m pytest jobs/mail-body-ingest/tests/ packages/kb-retrieval/tests/ -q
|
|
```
|
|
|