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

2018/04/16

累計閲覧数 3888 PV

ユーザー管理用のデータベースと、分析用のデータベースなどのように分離したいときがあります。 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などで報告してくれると修正いたします。