multidb provides two Django database routers useful in master-slave
deployments.
With multidb.MasterSlaveRouter all read queries will go to a slave
database; all inserts, updates, and deletes will go to the default
database.
First, define SLAVE_DATABASES in your settings. It should be a list of
database aliases that can be found in DATABASES:
DATABASES = {
'default': {...},
'shadow-1': {...},
'shadow-2': {...},
}
SLAVE_DATABASES = ['shadow-1', 'shadow-2']
Then put multidb.MasterSlaveRouter into DATABASE_ROUTERS:
DATABASE_ROUTERS = ('multidb.MasterSlaveRouter',)
The slave databases will be chosen in round-robin fashion.
If you want to get a connection to a slave in your app, use
multidb.get_slave:
from django.db import connections import multidb connection = connections[multidb.get_slave()]
In some applications, the lag between the master receiving a write and its
replication to the slaves is enough to cause inconsistency for the end user.
For example, imagine a scenario with 1 second of replication lag. If a user
makes a forum post (to the master) and then is redirected to a fully-rendered
view of it (from a slave) 500ms later, the view will fail. If this is a problem
in your application, consider using multidb.PinningMasterSlaveRouter. This
router works in combination with multidb.middleware.PinningRouterMiddleware
to assure that, after writing to the default database, future reads from
the same user agent are directed to the default database for a configurable
length of time.
PinningRouterMiddleware identifies database writes primarily by request
type, assuming that requests with HTTP methods that are not GET, TRACE,
HEAD, or OPTIONS are writes. You can indicate that any view writes to
the database by using the multidb.db_write decorator. This will cause the
same result as if the request were, e.g., a POST.
You can also manually set response._db_write = True to indicate that a
write occurred. This will not result in using the default database in this
request, but only in the next request.
To use PinningMasterSlaveRouter, put it into DATABASE_ROUTERS in your
settings:
DATABASE_ROUTERS = ('multidb.PinningMasterSlaveRouter',)
Then, install the middleware. It must be listed before any other middleware which performs database writes:
MIDDLEWARE_CLASSES = (
'multidb.middleware.PinningRouterMiddleware',
...more middleware here...
)
PinningRouterMiddleware attaches a cookie to any user agent who has just
written. The cookie should be set to expire at a time longer than your
replication lag. By default, its value is a conservative 15 seconds, but it can
be adjusted like so:
MULTIDB_PINNING_SECONDS = 5
If you need to change the name of the cookie, use the MULTIDB_PINNING_COOKIE
setting:
MULTIDB_PINNING_COOKIE = 'multidb_pin_writes'
multidb.pinning.use_master is both a context manager and a decorator for
wrapping code to use the master database. You can use it as a context manager:
from multidb.pinning import use_master
with use_master:
touch_the_database()
touch_another_database()
or as a decorator:
from multidb.pinning import use_master
@use_master
def func(*args, **kw):
"""Touches the master database."""
To run the tests, you'll need to install the development requirements:
pip install -r requirements.txt ./run.sh test