From 5d1f09f450686c53ead6f2784ba5c94ea6dccf36 Mon Sep 17 00:00:00 2001 From: James Bennett Date: Sat, 8 Sep 2012 16:02:00 -0400 Subject: [PATCH] Ticket 18657: Fix inconsistent DB names in router example. This rewrites the entire example to use the same DB names throughout, and also is hopefully a bit more sensibly described. Additionally, the missing import of the random module for choosing a read slave is included in the example now. --- docs/topics/db/multi-db.txt | 160 ++++++++++++++++++++++++------------ 1 file changed, 108 insertions(+), 52 deletions(-) diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt index 03a7d3b7cd9..ef9c5a26487 100644 --- a/docs/topics/db/multi-db.txt +++ b/docs/topics/db/multi-db.txt @@ -201,73 +201,129 @@ An example write to propagate to the slaves). It also doesn't consider the interaction of transactions with the database utilization strategy. -So - what does this mean in practice? Say you want ``myapp`` to -exist on the ``other`` database, and you want all other models in a -master/slave relationship between the databases ``master``, ``slave1`` and -``slave2``. To implement this, you would need 2 routers:: +So - what does this mean in practice? Let's consider another sample +configuration. This one will have several databases: one for the +``auth`` application, and all other apps using a master/slave setup +with two read slaves. Here are the settings specifying these +databases:: - class MyAppRouter(object): - """A router to control all database operations on models in - the myapp application""" + DATABASES = { + 'auth_db': { + 'NAME': 'auth_db', + 'ENGINE': 'django.db.backends.mysql', + 'USER': 'mysql_user', + 'PASSWORD': 'swordfish', + }, + 'master': { + 'NAME': 'master', + 'ENGINE': 'django.db.backends.mysql', + 'USER': 'mysql_user', + 'PASSWORD': 'spam', + }, + 'slave1': { + 'NAME': 'slave1', + 'ENGINE': 'django.db.backends.mysql', + 'USER': 'mysql_user', + 'PASSWORD': 'eggs', + }, + 'slave2': { + 'NAME': 'slave2', + 'ENGINE': 'django.db.backends.mysql', + 'USER': 'mysql_user', + 'PASSWORD': 'bacon', + }, + } - def db_for_read(self, model, **hints): - "Point all operations on myapp models to 'other'" - if model._meta.app_label == 'myapp': - return 'other' - return None +Now we'll need to handle routing. First we want a router that knows to +send queries for the ``auth`` app to ``auth_db``:: - def db_for_write(self, model, **hints): - "Point all operations on myapp models to 'other'" - if model._meta.app_label == 'myapp': - return 'other' - return None + class AuthRouter(object): + """ + A router to control all database operations on models in the + auth application. + """ + def db_for_read(self, model, **hints): + """ + Attempts to read auth models go to auth_db. + """ + if model._meta.app_label == 'auth': + return 'auth_db' + return None - def allow_relation(self, obj1, obj2, **hints): - "Allow any relation if a model in myapp is involved" - if obj1._meta.app_label == 'myapp' or obj2._meta.app_label == 'myapp': - return True - return None + def db_for_write(self, model, **hints): + """ + Attempts to write auth models go to auth_db. + """ + if model._meta.app_label == 'auth': + return 'auth_db' + return Non - def allow_syncdb(self, db, model): - "Make sure the myapp app only appears on the 'other' db" - if db == 'other': - return model._meta.app_label == 'myapp' - elif model._meta.app_label == 'myapp': - return False - return None + def allow_relation(self, obj1, obj2, **hints): + """ + Allow relations if a model in the auth app is involved. + """ + if obj1._meta.app_label == 'auth' or \ + obj2._meta.app_label == 'auth': + return True + return None + + def allow_syncdb(self, db, model): + """ + Make sure the auth app only appears in the 'auth_db' + database. + """ + if db == 'auth_db': + return model._meta.app_label == 'auth' + elif model._meta.app_label == 'auth': + return False + return None + +And we also want a router that sends all other apps to the +master/slave configuration, and randomly chooses a slave to read +from:: + + import random class MasterSlaveRouter(object): - """A router that sets up a simple master/slave configuration""" - def db_for_read(self, model, **hints): - "Point all read operations to a random slave" - return random.choice(['slave1','slave2']) + """ + Reads go to a randomly-chosen slave. + """ + return random.choice(['slave1', 'slave2']) - def db_for_write(self, model, **hints): - "Point all write operations to the master" - return 'master' + def db_for_write(self, model, **hints): + """ + Writes always go to master. + """ + return 'master' - def allow_relation(self, obj1, obj2, **hints): - "Allow any relation between two objects in the db pool" - db_list = ('master','slave1','slave2') - if obj1._state.db in db_list and obj2._state.db in db_list: - return True - return None + def allow_relation(self, obj1, obj2, **hints): + """ + Relations between objects are allowed if both objects are + in the master/slave pool. + """ + db_list = ('master', 'slave1', 'slave2') + if obj1.state.db in db_list and obj2.state.db in db_list: + return True + return None - def allow_syncdb(self, db, model): - "Explicitly put all models on all databases." - return True + def allow_syncdb(self, db, model): + """ + All non-auth models end up in this pool. + """ + return True -Then, in your settings file, add the following (substituting ``path.to.`` with -the actual python path to the module where you define the routers):: +Finally, in the settings file, we add the following (substituting +``path.to.`` with the actual python path to the module(s) where the +routers are defined):: - DATABASE_ROUTERS = ['path.to.MyAppRouter', 'path.to.MasterSlaveRouter'] + DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.MasterSlaveRouter'] The order in which routers are processed is significant. Routers will be queried in the order the are listed in the :setting:`DATABASE_ROUTERS` setting . In this example, the -``MyAppRouter`` is processed before the ``MasterSlaveRouter``, and as a -result, decisions concerning the models in ``myapp`` are processed +``AuthRouter`` is processed before the ``MasterSlaveRouter``, and as a +result, decisions concerning the models in ``auth`` are processed before any other decision is made. If the :setting:`DATABASE_ROUTERS` setting listed the two routers in the other order, ``MasterSlaveRouter.allow_syncdb()`` would be processed first. The @@ -276,11 +332,11 @@ that all models would be available on all databases. With this setup installed, lets run some Django code:: - >>> # This retrieval will be performed on the 'credentials' database + >>> # This retrieval will be performed on the 'auth_db' database >>> fred = User.objects.get(username='fred') >>> fred.first_name = 'Frederick' - >>> # This save will also be directed to 'credentials' + >>> # This save will also be directed to 'auth_db' >>> fred.save() >>> # These retrieval will be randomly allocated to a slave database