Skip to main content

Djangoでデータベースを分割して利用する

ユーザー管理用のデータベースと、分析用のデータベースなどのように分離したいときがあります。 Django 2.0のドキュメントではMultiple databasesの章に説明があります。

やることは4つ

  1. settings.pyDATABASESに複数のDBの接続先を記入する
  2. settings.pyDATABASE_APPS_MAPPINGを定義し、アプリケーションごとにKV形式で接続先のDB名を記述する
  3. settings.pyDATABASE_ROUTERSを定義し、接続するDBを振り分けるためのRouterの定義を配列形式で記述する
  4. DATABASE_ROUTERSに定義されたrouterを用意する

実際に具体例を示してみます。 ここで示す具体例では、DATABASE_APPS_MAPPINGに指定したとおりにデータベースが 振り分けるという単純なことだけを達成します。

ちょっと大雑把ではありますが、下記のようなディレクトリ構成を取っているものとします。 myappはdjangoのアプリケーションです。

myapp/
├── myapp/settings.py
├── manage.py
└── routers.py

settings.py

# myapp/settings.py
from os import environ

# 接続先データベースの定義
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mydb',
'USER': environ.get('DEFAULT_MYSQL_USER'),
'PASSWORD': environ.get('DEFAULT_MYSQL_PASSWORD'),
'HOST': environ.get('DEFAULT_DB_HOST', '127.0.0.1'),
'PORT': environ.get('DEFAULT_DB_PORT', '3306'),
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
'charset': 'utf8mb4',
},
'TEST': {
'CHARSET': 'utf8mb4',
'COLLATION': 'utf8mb4_general_ci',
},
},
'analytics': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mydb',
'USER': environ.get('ANALYTICS_MYSQL_USER'),
'PASSWORD': environ.get('ANALYTICS_MYSQL_PASSWORD'),
'HOST': environ.get('ANALYTICS_DB_HOST', '127.0.0.1'),
'PORT': environ.get('ANALYTICS_DB_PORT', '3306'),
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
'charset': 'utf8mb4',
},
'TEST': {
'CHARSET': 'utf8mb4',
'COLLATION': 'utf8mb4_general_ci',
},
}
}

# アプリケーションごとの接続先DBのマッピング
DATABASE_APPS_MAPPING = {
# defaultには管理系のTable
'admin' : 'default',
'auth' : 'default',
'contenttypes' : 'default',
'sessions' : 'default',
'messages' : 'default',
'staticfiles' : 'default',
'django_celery_beat' : 'default',
# analyticsには分析計のTable
'myapp' : 'analytics',
}

# 利用するRouter, manage.pyから見ての相対パス
DATABASE_ROUTERS = [
'router.DatabaseRouter',
]

Database routers

以下の4つのメソッドが定義されたクラスを用意します。

  • db_for_read
  • db_for_write
  • allow_relation
  • allow_migrate

参考: Database routers

router.DatabaseRouterは次のように定義しています。

# routers.py
from myapp.settings import DATABASE_APPS_MAPPING

class DatabaseRouter(object):
def db_for_read(self, model, **hints):
if model._meta.app_label in DATABASE_APPS_MAPPING:
return DATABASE_APPS_MAPPING[model._meta.app_label]
return None

def db_for_write(self, model, **hints):
if model._meta.app_label in DATABASE_APPS_MAPPING:
return DATABASE_APPS_MAPPING[model._meta.app_label]
return None

def allow_relation(self, obj1, obj2, **hints):
db1 = DATABASE_APPS_MAPPING.get(obj1._meta.app_label)
db2 = DATABASE_APPS_MAPPING.get(obj2._meta.app_label)
if db1 and db2:
return db1 == db2
return None

def allow_migrate(self, db, app_label, model=None, **hints):
if db in DATABASE_APPS_MAPPING.values():
return DATABASE_APPS_MAPPING.get(app_label) == db
elif app_label in DATABASE_APPS_MAPPING:
return False

これで設定完了です

Migration

Migrationはコマンドが変わります。 通常、./manage.py migrateを行うと、defaultで指定されたDBに対してMigrationが実行されますが、 今回は分離しているので、--database引数を用いて明示的に指定する必要があります。

$ ./manage.py migrate                          # defaultに対するMigration
$ ./manage.py migrate --database=analytics # analyticsに対するMigration

これでMigrationが実行されます。 また、その他のコマンドでもDB単位で実行する必要があるので、注意が必要です。

最後に

最近、Python界隈が分析系や深層学習などで騒がれていますが、 データを蓄積するDBの管理の話が出てこないのが気になります。

みなさん、どうやって管理しているのでしょう? 分析基盤の担当の方がいらっしゃれば良いのですが、全員がそうではないと思いますので、 この記事が一人で全部やらないといけないなぁ、と思っている人のところに届けばと思います。

間違いがあればTwitterなどで報告してくれると修正いたします。