You will be able to do this through Automatic Database Routing. It is a rather extensive setup, but let’s use a simple and practical example (taken from the documentation).
This one’s gonna have some Databases: one for app auth
and all other apps will use a Primary/replica setting with two read-only replicates. This would be the setting for Databases:
DATABASES = {
'default': {},
'auth_db': {
'NAME': 'auth_db',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'swordfish',
},
'primary': {
'NAME': 'primary',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'spam',
},
'replica1': {
'NAME': 'replica1',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'eggs',
},
'replica2': {
'NAME': 'replica2',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'bacon',
},
}
Now we will need to deal with the routing. First we will create a router that will know how to send queries from the app auth
to the auth_db
:
class AuthRouter:
"""
Um router para controlar todas as operações de banco de
dados dos models da app auth.
"""
def db_for_read(self, model, **hints):
"""
Definição do nome do banco para leitura.
"""
if model._meta.app_label == 'auth':
return 'auth_db'
return None
def db_for_write(self, model, **hints):
"""
Definição do nome do banco para escrita.
"""
if model._meta.app_label == 'auth':
return 'auth_db'
return None
def allow_relation(self, obj1, obj2, **hints):
"""
Permite relações se um model na app auth está envolvido.
"""
if obj1._meta.app_label == 'auth' or \
obj2._meta.app_label == 'auth':
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""
Certifica que a app auth aparece apenas no banco 'auth_db'.
database.
"""
if app_label == 'auth':
return db == 'auth_db'
return None
And here we will create a router that will send everything from the other apps to our Primary/replica configuration, randomly choosing from which replica it will read:
import random
class PrimaryReplicaRouter:
def db_for_read(self, model, **hints):
"""
Leitura vai para um dos dois, aleatoriamente.
"""
return random.choice(['replica1', 'replica2'])
def db_for_write(self, model, **hints):
"""
Escrita sempre no primary
"""
return 'primary'
def allow_relation(self, obj1, obj2, **hints):
"""
Relações entre objetos são permitidas se ambos objetos
estão no primary/replica.
"""
db_list = ('primary', 'replica1', 'replica2')
if obj1._state.db in db_list and obj2._state.db in db_list:
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
return True
At the end of all this, you will add the DATABASE_ROUTERS
to your file settings.py
of the project, with the path to the two previously created classes.
DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.PrimaryReplicaRouter']
Has this other post which also addresses the same subject.
I noticed that it is possible to have only one router file, and in each method put a conditional pointing each app to a specific database. It’s a good practice?
– D.Charles
It is possible, but for reasons of organization and maintainability, it is better to separate and have a file for each router, or at least a file but each router being a class.
– mazulo