Database Routing and Read/Write Replicas
Overview
OpenViper supports multiple database aliases and a router system that chooses which alias should handle a read, write, migration, or relation operation. This enables read/write splitting with replicas, per-app database selection, and tenant-specific routing.
See Installation for the full DATABASES configuration reference
including pool options, URL formats, and the nested vs flat config format.
See Database Backends for backend registration and custom backends.
DATABASES ROUTERS Setting
DATABASES['ROUTERS'] is a list of import paths or router instances.
Routers are checked in order; the first non-None result wins.
DATABASES = {
"default": {
"OPTIONS": {"URL": "postgresql+asyncpg://user:pass@primary/db"},
},
"ROUTERS": [
"myproject.db.routers.PrimaryReplicaRouter",
],
"ROUTING": {
"primary_alias": "default",
"replica_aliases": ["replica"],
},
}
Primary and Replica Databases
A primary database accepts both reads and writes. A replica database is typically read-only and receives a copy of the primary’s data via database replication.
Configure the ROLE key to declare each alias’s role:
"primary"- read/write (default)"replica"- typically read-only
Set READ_ONLY: True on replica aliases to prevent accidental writes.
PrimaryReplicaRouter
OpenViper includes a built-in PrimaryReplicaRouter:
from openviper.db.routing import PrimaryReplicaRouter
router = PrimaryReplicaRouter(
primary_alias="default",
replica_aliases=["replica"],
read_your_writes=True,
replica_selection="round_robin",
)
Behaviour:
db_for_read: Routes to a replica unless read-your-writes is active.
db_for_write: Routes to the primary and marks the context as write-used.
allow_migrate: Returns
Trueonly for the primary alias by default.allow_relation: Allows relations between primary and its replicas.
Read-Your-Writes
After a write operation, subsequent reads in the same request context are routed to the primary database to avoid replication lag bugs.
This is controlled by the read_your_writes flag on
PrimaryReplicaRouter (default: True).
The routing context is automatically reset at the end of each request or test.
QuerySet.using
Manually override the router for a specific query chain:
# Read from a specific replica
users = await User.objects.using('replica').all()
# Write to a specific alias
await User.objects.using('default').create(email='a@example.com')
The using() method is preserved across cloned QuerySets:
qs = User.objects.using('replica').filter(is_active=True)
# qs still uses 'replica'
Transactions
Use transaction() to pin a block of ORM operations to a specific
database alias:
from openviper.db.connection import transaction
async with transaction(using='default'):
await Post.objects.create(title="Hello")
await Tag.objects.create(name="python")
# Both committed on 'default'
Read-only transactions on replica aliases are allowed:
async with transaction(using='replica', read_only=True):
posts = await Post.objects.using('replica').all()
Write transactions on read-only aliases raise DatabaseReadOnlyError.
Migrations
Migrations run on the primary (default) database by default.
Replicas do not receive migrations unless a router’s allow_migrate
explicitly allows it.
openviper migrate
openviper migrate --database default
openviper migrate --database analytics
Admin Behaviour
Admin operations use the primary database by default for consistency. Admin writes and deletes always go to primary. This ensures data integrity under replica routing.
Testing Multi-Database Routing
OpenViper provides testing helpers for multi-database setups via the
DATABASES config. See Testing for fixture details.
Best Practices
Always set
READ_ONLY: Trueon replica aliases.Use
read_your_writes=Trueto avoid replication lag bugs.Keep migrations on the primary database.
Use
.using()sparingly - let routers do the routing.Reset routing context in tests using
reset_routing_context().Never log full database URLs with credentials.
Limitations
DatabaseRouteris async - all router methods are coroutines.allow_relationonly works for objects that carry a_db_aliasattribute (set by the ORM when routing is active).Replica selection strategies beyond
firstandround_robinare not yet implemented.