Customer impact. Search was slow or timed out for roughly 8% of active sessions during the 23-minute window; p95 latency on /search rose from 180 ms to 2.4 s. No data was lost and no other endpoint was affected. The fix was a rollback; the root cause is understood and the permanent fix is scheduled below.
14:06:12 WARN search-api p95=1840ms window=60s replica=search-ro-2 14:06:42 WARN search-api p95=2410ms window=60s replica=search-ro-1 14:07:05 ERROR search-api upstream timeout after 2000ms route=/search n=41
jit = on for the search read replicas. Query plans on search-ro-1 confirm JIT compilation on the hot query.
search-ro-1=# EXPLAIN (ANALYZE) SELECT ... FROM documents WHERE tsv @@ $1 ...
JIT:
Functions: 14
Options: Inlining true, Optimization true, Expressions true, Deforming true
Timing: Generation 2.1 ms, Inlining 38.4 ms, Optimization 214.9 ms, Emission 96.2 ms, Total 351.6 msThe v2.31.0 migration enabled PostgreSQL's JIT compiler on the search read replicas, intending to speed up analytical queries on the same hosts. The planner also applied JIT to the search hot path: a full-text query that runs about 400 times per second. JIT compilation costs roughly 350 ms per plan, and because the query's parameters vary, plans were not being reused. Every search paid the compilation cost.
The canary did not catch it because the canary gate checks error rate, not latency, and the requests were succeeding, just slowly, until the upstream timeout started tripping.
| Action | Owner | Due |
|---|---|---|
Pin jit = off in the read-replica config; move the setting out of app migrations. | Maya | Fri 2026-09-18 |
| Add a p95 latency check to the deploy canary gate, per route. | Tomas | Next sprint |
| Write down the rollback procedure used here as a runbook. | Priya | This week |
| Add "read the release notes first" to the incident-lead checklist. | Maya | This week |
The planner enables JIT when the estimated query cost exceeds jit_above_cost (default 100 000). The search query's cost estimate sits just above that line because of the GIN index scan estimate, so it qualifies, but it runs in under 5 ms without JIT. Analytical queries run for seconds and amortise the compile. The right fix is per-role or per-replica, not global.