Skip to content

feat: serve GET and HEAD from an autocommit, read-only engine - #84

Merged
lesnik512 merged 1 commit into
mainfrom
feat/read-engine
Oct 16, 2026
Merged

lesnik512 merged 1 commit into
mainfrom
feat/read-engine

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Closes #83.

GET and HEAD now always go to a read engine. It is built from DB_REPLICA_DSN when that is set and from DB_DSN otherwise, and it is created with:

  • isolation_level="AUTOCOMMIT", so SQLAlchemy sends no BEGIN/ROLLBACK and the pre-ping is a bare ;. A one-query GET drops from six round trips to two.
  • server_settings={"default_transaction_read_only": "on"}, so Postgres rejects writes on these connections. The setting goes in the startup packet and costs nothing per request.

Pre-ping stays on for both engines. The issue has the measurements: locally 4,022 to 5,281 req/s with one client, and about 3x under added network latency.

Code changes:

  • create_read_sa_engine replaces create_replica_sa_engine and never returns None, so choose_sa_engine and close_sa_engine lose their None handling.
  • database_read_engine replaces database_replica_engine in app/ioc.py.
  • create_sa_engine takes read_only: bool, rather than accepting arbitrary engine kwargs.

Behaviour changes, also written up in AGENTS.md:

  • Each statement on GET gets its own snapshot, so fetch_with_cards (deck, then cards through selectinload) can see a card committed between the two queries.
  • Without a replica, the read engine is a second pool on the primary, so one process can hold up to 2 × db_pool_size connections.

Tests:

  • The routing tests now target the read engine.
  • New tests check that the read engine falls back to DB_DSN, that two statements on it get different transaction_timestamp() values (autocommit), and that an INSERT fails with a read-only error. Both behaviour tests fail if the engine options are removed.
  • test_tracing.py already sends a real GET /api/decks/ through the DI graph without overrides, so it covers the read engine end to end.

@lesnik512
lesnik512 merged commit ef142f7 into main Oct 16, 2026
2 checks passed
@lesnik512
lesnik512 deleted the feat/read-engine branch October 16, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Serve GET and HEAD from an autocommit, read-only engine

1 participant