Here’s how most teams ship “real-time translation” in 2026: a FastAPI service, one
asynciotask per open tenant stream, each task holding a websocket to an LLM translation endpoint, buffering chunks in a Python list, and pushing them throughasyncio.Queueto an output socket. It demos beautifully at 20 concurrent tenants. Someone puts it in a Helm chart, setsreplicas: 3, and calls it horizontally scalable.It isn’t. It’s vertically scalable up to the point where the GIL, the per-task heap churn, and the kernel’s scheduler all conspire against you at once — and that point arrives a lot sooner than the load test suggested, because the load test ran on one machine with 40 idle cores and nobody watching
/proc/schedstat.The abstraction hides three things from you: how many OS threads are actually runnable at once, how much of your P99 latency is TLB and cache-line cost rather than “the model was slow,” and how much heap fragmentation your per-chunk
strallocations are generating over a six-hour tenant session. None of that shows up in atime.time()wrapper around your handler. It shows up inperf statand in 3 a.m. pages.
A process-per-tenant (or asyncio-task-per-tenant) translation pipeline fails at hyperscale through three concrete mechanisms, not vague “overhead”:
Scheduler thrashing. Each tenant stream is I/O-bound — it blocks waiting on the next chunk from the LLM, then briefly runs to re-buffer, then blocks again. At 5,000 concurrent tenants this produces tens of thousands of
wake→run→blocktransitions per second. CFS has to re-insert each task into the red-black run-queue on every wake, and cross-core load balancing migrates tasks to whichever CPU has room — which means the task’s working set (buffers, interpreter state) is now cold on that core’s L2/L3, and the TLB entries for its heap pages are gone. A TLB miss that walks a 4-level page table costs on the order of 150–300 cycles; multiply by tens of thousands of migrations per second and you’ve built yourself a latency tax nobody put in the design doc.Heap fragmentation under long-lived sessions. Python
strobjects for each translated chunk are allocated and freed continuously. Over a multi-hour session, the allocator’s free lists fragment,RSScreeps upward independent of actual live data, and eventually the kernel starts reclaiming pages under memory pressure — introducing GC-adjacent pauses that show up as random 40–80 ms latency spikes with no corresponding CPU spike in your dashboards, because the cost is insystime (page faults), notusertime.GIL serialization disguised as concurrency.
asynciogives you the appearance of 5,000 parallel streams. It gives you one OS thread. Any CPU-bound step in your “translation” path — tokenization, SimHash computation for routing, JSON encoding of the chunk envelope — serializes behind the GIL. You’re not parallel, you’re interleaved, and the interleaving overhead is pure loss.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.