Route read-only GET endpoints onto the streaming replica pool to
deflect ~50-60% of primary-pool pressure. Builds on the RO pool
infrastructure (commit 03f91a907) and the pgBouncer scidex_ro
alias.
api_shared/db.py_thread_local_db_ro — parallel threading.local for RO connections.get_db_ro() — now thread-local cached (mirroring get_db()).close_thread_local_dbs() — helper the middleware uses to closeapi.pyclose_thread_db_connection switched from inlined_thread_local_db cleanup to close_thread_local_dbs().
get_db() → get_db_ro().api_routes/admin.py, api_routes/dedup.py, api_routes/epistemic.pyA GET handler migrates ONLY when:
- Its body contains a get_db( call
- Its body contains ZERO of: INSERT INTO, UPDATE ... SET,
DELETE FROM, CREATE [TEMP] TABLE, DROP TABLE, ALTER TABLE,
CREATE INDEX, TRUNCATE, UPSERT, INSERT OR REPLACE,
INSERT OR IGNORE, ON CONFLICT
- Its body contains ZERO calls matching
record_edit, log_pageview, _write_pageview, write_audit,
save_, store_, record_, insert_, upsert_*,
update_, delete_, *.commit()
- (whitelist for false positives: commit_attribution, __save__,
save_state_dir, record_seen, save_ghost_snapshot)
Mixed handlers (pageview tracking, cache writes, index creation)
stay on get_db().
Counts:
- 297 pure-read GET handlers identified; 285 contain get_db( and
were migrated (the remaining 12 use a direct pool API or
already-async helper — not in scope).
- 9 mixed GET handlers left on primary.
- 130 GET handlers don't touch the DB through our helpers — no-op.
scidex-api — no startup errors./hypothesis/, /entity/,/wiki/* direct-read variants, /senate, /exchange,/showcase, /dashboard, etc.) — responses identical tocurl -I status codes).
/health?pool=1 shows both pool_ (primary) and ro_pg_stat_activity on the primary shows reduced steady-statepg_stat_activity shows matching rise in connections.cannot execute … in a read-only transaction errors inRevert this commit. Old code reverts to get_db() for all handlers;
the RO pool becomes unused but harmless.