Hi all,
I've just uploaded a new patch improving the multi-db interface. This
patch introduces a way to easily configure the database selection
process, and enables you to assign a database selection mechanism to
models not under your control (e.g., changing database usage for the
contrib.auth app).
The patch is attached to #12540; if committed it will also close #12541.
This patch goes a little further than I have previously indicated - it
actually starts to introduce public API.
I'm looking for any design feedback on this proposal.
I haven't written docs yet, but here's the short version of what I have added:
* There is a new setting - DATABASE_ROUTERS - which is a list of
routers that will be tried in turn.
* A Database Router is any class that provides three methods:
- db_for_read(model, instance=None)
Suggest the database that should be used for reads of objects of
type Model. Return None if there is no suggestion
- db_for_write(model, instance=None)
Suggest the database that should be used for writes of objects
of type Model. Return None if there is no suggestion
- allow_relation(obj1, obj2)
Return True if a relation between obj1 and obj2 should be
allowed, False if the relation should be prevented, or None if the
router has no opinion. (For those that were paying attention
yesterday: This method is a replacement for SOURCE in a database
setting)
* In these methods, 'model' is the model class that is operated upon.
'instance' is any object that can be used to provide a hint as to the
database that should be used. The may be the instance that is being
saved, or an object that is being added in a relationship. This allows
for routing based on the application, the model, or an attribute of
the model.
* Whenever a query needs to know which database to use, it can call
django.db.router, providing a model, and a hint (if available). Django
then works down the stack, trying each router in turn, until a
database suggestion can be found. If no suggestion can be found, it
tries the current _state.db of the hint instance; failing that, it
falls back to DEFAULT_DB_ALIAS.
The patch includes a whole lot of extra tests (including some tests
that were disabled just before the commit to trunk). There are some
tests that are probably still missing - especially for update and
delete queries.
So - what does this mean in practice? Say you want contrib.auth to
exist on the 'credentials' database; all other models in a
master/slave relationship between the databses 'master', 'slave1' and
'slave2'. To implement this, you would need 2 routers:
class AuthRouter(object):
def db_for_read(self, model, instance=None):
# Point all operations on auth models to 'credentials'
if model._meta.app_label == 'auth':
return 'credentials'
return None
def db_for_write(self, model, instance=None):
# Point all operations on auth models to 'credentials'
if model._meta.app_label == 'auth':
return 'credentials'
return None
def allow_relation(self, obj1, obj2):
# Allow any relation if a model in Auth is involved
if obj1._meta.app_label == 'auth' or obj2._meta.app_label == 'auth':
return True
return None
class MasterSlaveRouter(object):
def db_for_read(self, model, db=None):
# Point all read operations to a random slave
return random.choice(['slave1','slave2'])
def db_for_write(self, model, db=None):
# Point all write operations to the master
return 'master'
def allow_relation(self, obj1, obj2):
# Allow any relation between two objects in the db pool
db_list = ('master','slave1','slave2')
if obj1 in db_list and obj2 in db_list:
return True
return None
Then, in your settings file, add the following:
DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.MasterSlaveRouter']
And you're set. No need to override get_query_set() or use using(xxx)
or save(using=xxx) - the right database will be selected
automagically.
One quick point of note - the task of splitting auth onto a different
database won't actually work on Postgres or MySQL InnoDB - ForeignKeys
to a remote database won't work due to referential integrity problems.
I'm not attempting to solve this problem right now, as there are a
whole lot of issues that need to be worked out in that space. However,
on MySQL MyISAM and SQLite, it will work.
Ok - so that's the patch. Any comments, queries, or criticisms are welcome.
Yours,
Russ Magee %-)
--
You received this message because you are subscribed to the Google Groups
"Django developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/django-developers?hl=en.