Skip to content

feat: route GET/HEAD requests to an optional read replica - #71

Merged
lesnik512 merged 1 commit into
mainfrom
feat/read-replica-session
Sep 27, 2026
Merged

lesnik512 merged 1 commit into
mainfrom
feat/read-replica-session

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Closes #30.

What

  • New DB_REPLICA_DSN setting (default empty). When set, GET/HEAD requests use a replica engine; every other method, and any resolution without a request, uses the primary. When unset, behaviour is unchanged and no second pool is created.
  • app/ioc.py: database_engine (primary), database_replica_engine (AsyncEngine | None), and a request-scoped dynamic_engine chosen by choose_sa_engine. The session gets dynamic_engine via explicit kwargs.
  • Tests override dynamic_engine with the rolled-back connection, so reads and writes still share one transaction.
  • tests/test_db_routing.py covers method routing, no-request fallback, no-replica fallback, and building the replica engine from the DSN.

Design notes

  • Modelled on the rchat chats service, with two deliberate differences:
    • The session is wired to dynamic_engine explicitly instead of relying on bound_type=None making it the only AsyncEngine for type autowiring. Both base engines are bound_type=None so modern-di does not raise a duplicate-type error.
    • A separate DB_REPLICA_DSN instead of a db-retry multi-host DSN with target_session_attrs. It keeps the template dependency-free and the config obvious; db-retry also requires a separate database name.
  • The replica provider returns None rather than aliasing the primary provider, so the routing is testable by overriding each engine independently.
  • HEAD goes to the replica alongside GET, since it is equally read-only.
  • app/resources/*.py is exempted from ruff's TC rules: modern-di evaluates creator annotations at runtime, and ruff's autofix moving import fastapi under TYPE_CHECKING broke resolution with NameError.

Caveats

  • Replica lag: a client that writes and immediately reads may see stale data.
  • GET handlers must not write; they would hit the replica.

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.

Add dynamic orm session (read-write)

1 participant