mirror of
https://github.com/element-hq/synapse.git
synced 2026-08-16 02:30:26 +00:00
Every `execute` did a fresh `prepare` + `query_raw` — two server round-trips per query — and threw the `Statement` away; even BEGIN/COMMIT/ROLLBACK went through a prepare. psycopg2 pays neither cost, which showed up as timing margins under load (e.g. the worker-lock contention stress test timing out under a parallel test run). The pooled item is now a `PooledClient`: the `Client` plus a per-connection statement cache keyed by SQL (`prepare_cached`). The cache lives with the pooled connection — named prepared statements are per-session server state, so they survive checkouts and a repeated query on any later checkout skips the prepare round-trip. `PooledClient` derefs to `Client`, so existing call sites are unchanged. The map is cleared at a size cap so one-off SQL (e.g. execute_values' literal-spliced statements) can't grow it without bound; the hot set re-warms in one round. Concurrent DDL can invalidate a cached plan (SQLSTATE 0A000, "cached plan must not change result type"). `execute` handles it: outside a transaction the statement is re-prepared fresh and retried once; inside one (already aborted) the stale cache entry is shed without I/O and the error propagates, so the caller's next transaction gets a valid plan. Transaction control (BEGIN/COMMIT/ROLLBACK) now uses `batch_execute` — the simple-query protocol, one round-trip, nothing to prepare. Verified: rust tests 158/158; storage/lock/worker-lock suites pass on the Rust backend, including the worker-lock contention test under -j4 that previously exceeded its wall-clock budget. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W3G4M92AmwSSZCbmtMJU3d