backend-v0.9.3
fix(search): tokenize on a ProcessPool, not to_thread (event-loop starvation → 503) (#244)
Kiwi (BM25 sparse tokenizer) is a CPU-bound C++ extension that does not release the GIL during native tokenize(). Running it via asyncio.to_thread therefore serialized concurrent tokenizations on the GIL and starved the single event-loop thread → /livez couldn't be scheduled → readiness/liveness probes timed out → pod pulled from Service → intermittent 503 flapping under search + indexing load.
sparse_encoder.tokenize() now offloads to a dedicated spawn ProcessPoolExecutor (each worker its own GIL; initializer warms Kiwi per worker), created/torn-down in the lifespan; falls back to to_thread when the pool isn't started (tests). search/embed_worker/stats-refresher all benefit. Search results unchanged; RWO/single-replica unaffected.
Measured locally: event-loop max stall ~472 ms (to_thread, fully serial) → ~13–34 ms (ProcessPool), ~4× tokenization throughput.