Django database read-write separation router with transaction-aware and consistent hash routing support
Project description
Django Read-Write Router
Django database read-write separation router with transaction-aware and consistent hash routing support.
Features
- Basic Read-Write Separation: Route writes to primary, reads to replicas
- Transaction-Aware Routing: Automatically route reads to primary when in a transaction
- Consistent Hash Routing: Same user always reads from same replica (monotonic reads)
- Request Context Tracking: Automatic context management via middleware
- Primary-Only QuerySet: Force specific queries to use primary database
Installation
pip install django-rw-router
Or with uv:
uv add django-rw-router
Quick Start
1. Configure Databases
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydb',
'USER': 'user',
'PASSWORD': 'password',
'HOST': 'primary.db.example.com',
'PORT': '5432',
},
'readonly': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydb',
'USER': 'readonly_user',
'PASSWORD': 'password',
'HOST': 'replica.db.example.com',
'PORT': '5432',
},
}
2. Add Router
# settings.py
DATABASE_ROUTERS = [
'django_rw_router.routers.TransactionPrimaryReplicaRouter',
]
3. Add Middleware (Optional)
# settings.py
MIDDLEWARE = [
'django_rw_router.middleware.RequestContextMiddleware',
# ... other middleware
]
Router Options
PrimaryReplicaRouter
Basic read-write separation. Writes go to primary, reads are randomly distributed among replicas.
DATABASE_ROUTERS = [
'django_rw_router.routers.PrimaryReplicaRouter',
]
TransactionPrimaryReplicaRouter (Recommended)
Extends basic router with transaction awareness. Reads inside a transaction go to primary.
DATABASE_ROUTERS = [
'django_rw_router.routers.TransactionPrimaryReplicaRouter',
]
HashPrimaryReplicaRouter
Uses consistent hashing based on user_id from RequestContext. Same user always reads from same replica.
DATABASE_ROUTERS = [
'django_rw_router.routers.hash.HashPrimaryReplicaRouter',
]
Configuration
Database Aliases
# settings.py
# Read replica aliases (single or list)
DJANGO_RW_ROUTER_READ_DBS = ['readonly', 'readonly2']
# Write database alias
DJANGO_RW_ROUTER_WRITE_DB = 'default'
# Hash ring virtual nodes (for HashPrimaryReplicaRouter)
DJANGO_RW_ROUTER_HASH_VIRTUAL_NODES = 40
Middleware Configuration
# settings.py
# How to extract user_id from request (nested attributes supported)
DJANGO_RW_ROUTER_USER_ID_ATTR = 'user.id' # Default
# How to extract request_id from request
DJANGO_RW_ROUTER_REQUEST_ID_ATTR = 'id' # Default
QuerySet Configuration
# settings.py
# Force all PrimaryQuerySet reads to use primary
DJANGO_RW_ROUTER_QUERYSET_USING_ENABLE = True
# Enable @use_primary_db decorator for specific methods
DJANGO_RW_ROUTER_METHOD_USING_ENABLE = True
Usage Examples
Basic Usage
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
# Writes go to 'default', reads go to 'readonly'
book = Book.objects.create(title="Django Guide") # Write to primary
books = Book.objects.all() # Read from replica
Transaction-Aware Reads
from django.db import transaction
with transaction.atomic():
book = Book.objects.create(title="New Book")
# This read goes to PRIMARY (not replica) because we're in a transaction
fresh_book = Book.objects.get(id=book.id)
Using PrimaryManager for Critical Queries
from django.db import models
from django_rw_router.managers import PrimaryManager
class ImportantModel(models.Model):
objects = PrimaryManager()
# When DJANGO_RW_ROUTER_QUERYSET_USING_ENABLE=True,
# all reads through this manager go to primary
obj = ImportantModel.objects.first()
Manual Database Selection
You can always override the router:
# Force read from primary
obj = Book.objects.using('default').first()
# Force write to replica (not recommended)
book.save(using='readonly')
How It Works
- Write Operations: Always routed to the primary database (
default) - Read Operations:
- Outside transaction: Routed to random read replica
- Inside transaction: Routed to primary (prevents stale reads)
- Hash Routing:
user_idis hashed to consistently select the same replica - Context Tracking: Middleware sets/clears request context automatically
Requirements
- Python >= 3.8
- Django >= 3.2
- uhashring >= 2.1
License
MIT License
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_rw_router-0.0.1.tar.gz.
File metadata
- Download URL: django_rw_router-0.0.1.tar.gz
- Upload date:
- Size: 56.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb409f4df01d6675cd9baf36966d305a124b734ff960eb04addaa8a04d66873a
|
|
| MD5 |
c617f8ac7b42d667e618e11d27cbeaa0
|
|
| BLAKE2b-256 |
44afdbeb3a5be03e700f7d135f1e1aca945d725be9cb4fd8b5f35c9e75c66b64
|
File details
Details for the file django_rw_router-0.0.1-py3-none-any.whl.
File metadata
- Download URL: django_rw_router-0.0.1-py3-none-any.whl
- Upload date:
- Size: 13.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62c5600d5545669968bf62b092ddf72676cd024f054b7818bcaa4689eb3d0945
|
|
| MD5 |
14ad9dc58f85b9181705d782402832b4
|
|
| BLAKE2b-256 |
62dbeeb3117747e7f5a0622dc2be608af43f531cc78fd59637df0d1d3c187908
|