f43d79f
commit f43d79f
1 file changed
+35 -8
Showing 1 changed file+35-8
▼BENCHMARKS.md
+35-8
| @@ -157,14 +157,41 @@ normal Python path if no schema is present). The response is the validated reque | ||
157 | 157 | body echoed verbatim; handlers that transform the body must keep using a Python |
158 | 158 | handler. |
159 | 159 | |
160 | -> **Known issue — non-native deadlock at high concurrency (see ADR-049):** the | |
161 | -> *Python* dispatch path (`spawn_blocking` + `Python::attach`) hard-stalls at | |
162 | -> ~100 concurrent connections for **every** non-native route, including the | |
163 | -> simplest handler (≈16–20 req/s, all connections aborted). The native fast path | |
164 | -> is immune because it never acquires the GIL or enters the blocking pool. This | |
165 | -> is a pre-existing server-level GIL/blocking-pool deadlock, **not** introduced | |
166 | -> by the native optimization; it must be fixed before non-native routes are | |
167 | -> production-viable at scale (recommended fix: a dedicated GIL thread-pool). | |
160 | +> **Non-native dispatch deadlock — RESOLVED (ADR-049).** The dedicated GIL | |
161 | +> thread-pool (`gil_pool.rs`) replaced per-request `spawn_blocking` + | |
162 | +> `Python::attach`; the Python path no longer stalls at high concurrency (verified | |
163 | +> at `-c 200`, 100% success). On CPython the Python path is still GIL-serialized | |
164 | +> (~100–120k req/s ceiling on this hardware) — that is the GIL, not a deadlock. | |
165 | +> The native fast path remains the high-throughput option. | |
166 | + | |
167 | +> **Measuring the native fast path correctly:** the 410k–724k numbers require a | |
168 | +> route registered with **both** `native=True` **and** a `Schema`. `native=True` | |
169 | +> *without* a schema silently falls back to the Python GIL path (see the caveat | |
170 | +> below), which is why a naively-tagged route benchmarks at ~120k, not ~700k. The | |
171 | +> benchmark below reproduces the methodology on current hardware. | |
172 | + | |
173 | +### Current-hardware re-run (2026-07-16, release build) | |
174 | + | |
175 | +Same methodology as the fixture (`oha -z 10s -c 100`, single-process, release | |
176 | +`maturin develop --release`) but on the dev box (different CPU bin). This | |
177 | +substantiates the fast-path claim here and isolates the three variables that | |
178 | +move the number: **build profile**, **concurrency**, and **whether the route | |
179 | +actually takes the Rust fast path**. | |
180 | + | |
181 | +| Route | Registration | Mode | req/sec @ -c 100 | | |
182 | +|---|---|---|---| | |
183 | +| `/validate_native` | `native=True, schema=UserSchema` | **Rust fast path** | **430,367** | | |
184 | +| `/python_route` | plain handler | Python GIL path | 123,170 | | |
185 | +| `/native` (no schema) | `native=True` only | Python GIL path (fallback) | ~120,000 | | |
186 | + | |
187 | +- On a **dev** build (`maturin develop`, no `--release`) the Python path drops to | |
188 | + ~64k req/s — optimization level, not a deadlock. | |
189 | +- The Python path is GIL-serialized; ~100–120k req/s is its realistic ceiling on | |
190 | + this hardware. The 430k native number is ~3.5× the Python path and matches the | |
191 | + lower end of the fixture's 410k–724k range (the fixture CPU is a faster bin). | |
192 | +- **Takeaway:** to actually beat Robyn-class raw throughput, opt routes into | |
193 | + `native=True` + a `Schema`. Plain Python handlers are correct and stable but | |
194 | + GIL-bound. | |
168 | 195 | |
169 | 196 | --- |
170 | 197 | |
171 | 198 | |