マルチデータベース (Multiple databases)

revision-up-to:17812 (1.4)
リリースノートを参照してください

このトピックガイドでは、 Django がマルチデータベースをサポートしたことについて 述べられています。他の多くの Django ドキュメントでは単一のデータベースを扱う ことを想定しています。マルチデータベースを扱いたいなら、いくつか追加の手順を行う 必要があります。

データベースを定義する

Django で複数のデータベースを扱うための最初のステップは、使用したいデータベース サーバについて Django に伝えることです。これには DATABASES 設定を使い ます。データベースへのエイリアスを設定し、 Django 全体にわたって特定のデータ ベースを参照できるようにします。特定のデータベースとの接続を設定する辞書です。 内部の辞書の設定については、すべて DATABASES ドキュメントで述べられて います。

データベースにはどんなエイリアスでも設定できます。しかし、 default は特別な 意味を持っています。Django は他のデータベースが一切選ばれていない場合に、 default によってデータベースを使います。 default データベースが無い 場合は、常に使うデータベースの指定に気をつけてください。

以下の例は settings.py の一部で、 2 つのデータベースを定義しています。デフォ ルトが PostgreSQL データベースで、 users は MySQL データベースを呼び出し ます:

DATABASES = {
    'default': {
        'NAME': 'app_data',
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'USER': 'postgres_user',
        'PASSWORD': 's3krit'
    },
    'users': {
        'NAME': 'user_data',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'priv4te'
    }
}

DATABASES 設定に定義していないデータベースにアクセスすると、 django.db.utils.ConnectionDoesNotExist 例外が送出されます。

データベースの同期

syncdb 管理コマンドは 1 度に 1 つのデータベースを操作します。デフォ ルトでは default データベースが操作されます。 --database 引数 を与えることで、 syncdb に他のデータベースを同期するよう指示できます。例における 全データベースの全モデルを同期するには、以下を実行する必要があります:

$ ./manage.py syncdb
$ ./manage.py syncdb --database=users

どのアプリケーションも特定のデータベースに同期させたくないときは、 database router を定義できます。これによって 特定のモデルに利用制限を設けられます。

他にも、粒度の小さい同期の制御をしたいなら、特定アプリケーションの sqlall の出力のすべてか一部を、直接データベースプロンプトにパイプし ます。こんなかんじです:

$ ./manage.py sqlall sales | ./manage.py dbshell

他の管理コマンドを使う

データベースを扱う他の django-admin.py コマンドは、 syncdb と 同じように動作します。今までと同じく、データベースを制御するための --database を使用します。は一度に 1 つのデータベース上でのみ動作し ます。

自動データベースルーティング

マルチデータベースを扱う最も簡単な方法は、データベースルーティングスキーム (a database routing scheme) を設定することです。デフォルトのルーティングスキーム は、オブジェクトが元のデータベースに ‘ベットリ (sticky)’ なままであることを保証 します (例えば foo データベースから取ってきたオブジェクトが、同じ foo データベースに保存されるということ) 。デフォルトのルーティングスキームは、データ ベースが特定されていないと、全クエリを default データベースに返します。

デフォルトのルーティングスキームを有効にする必要はありません。 ‘設定する必要なく (out of the box)’ 全 Django プロジェクトで提供されています。より面白いデータ ベースの割り当てをしたいなら、個別のデータベースルータを定義、導入しましょう。

データベースルータ

データベースルータは、 4 つのメソッドを提供するクラスです。

db_for_read(model, **hints)

model タイプのオブジェクトの読み込み操作に使われるデータベースを提示 します。

データベース操作においてデータベースを選ぶ助けになる追加の情報を提示できる なら、 hints 辞書に提供します。妥当なヒントの詳細は こちら に書かれています。

提示するものが無い場合は None を返します。

db_for_write(model, **hints)

Model タイプのオブジェクトの書き込みに使われるデータベースを提示します。

データベース操作においてデータベースを選ぶ助けになる追加の情報を提示できる なら、 hints 辞書に提供します。妥当なヒントの詳細は こちら に書かれています。

提示するものが無い場合は None を返します。

allow_relation(obj1, obj2, **hints)

obj1 、 obj2 間のリレーションが許可されている場合 True 、リレーションが 許可されていない場合 False 、 ルータに設定がない場合 None を返します。単なる 検証の操作です。外部キー、多対多の操作において、リレーションが 2 オブジェク ト間で許可されているかどうか判別するために使用されます。

allow_syncdb(db, model)

modeldb で与えられたデータベースと同期されるべきかどうかを判別し ます。モデルが同期されるべきとき True 、 そうでないとき False 、 ルータに 設定がない場合 None を返します。このメソッドは与えられたデータベース上に モデルが存在するかどうかを判別するのに使えます。

ルータはこれら すべて のメソッドを提供する必要はありません。 1 つかそれ以上は 省略されるでしょう。あるメソッドが省略されれば、 Django は該当のチェックを行う とき、そのルータをスキップします。

ヒント

データベースルータから受け取ったヒントによって、与えられたリクエストをどのデータ ベースで受け取るべきかを決めます。

今のところ、提供される唯一のヒントは instance です。実行中の読み込み、もしく は書き込み操作に関連づけられたオブジェクトのインスタンスです。これは保存された インスタンスか、多対多のリレーションに追加されたインスタンスになります。場合に よっては、インスタンスヒントは全く提供されません。ルータはインスタンスヒントの 存在をチェックします。ルーティングの振る舞いを変えるためにヒントが使われるべきか どうか判断します。

ルータを使う

データベースルータは DATABASE_ROUTERS 設定によって導入されます。 この設定ではクラス名のリストを定義します。マスタールータ (django.db.router) に使われるべきルータを指定します。

マスタールータは Django のデータベース操作に使われます。これはデータベースに処理 を割り当てるために使われます。クエリがどのデータベースを使うべきか知る必要がある ときは常にマスタールータが呼び出されます。その際モデルと (有効な場合は) ヒントが ルータに提供されます。データベースが提示されるまで、 Django は順番に各ルータを 試します。提示されない場合、現在のヒントインスタンスの _state.db を試し ます。ヒントインスタンスが提供されないか、インスタンスがデータベースステートを 保持していない場合、マスタールータは default データベースを割り当てます。

例の狙い!

この例はデモンストレーションを意図しています。ルータインフラストラクチャ (the router infrastructure) が、データベースの使い方を変えるために、どの ように使えるかのデモンストレーションです。デモンストレーションに際して、 いくつかの複雑な問題はあえて無視します。

myapp 中のモデルのうちのどれかが other データベース外のモデルとの関係 を含んでいれば、この例は動きません。 Cross-database relationships では、 Django が今のところ扱えない参照整合性の問題を紹介しています。

記載されているマスター/スレーブ構成にも欠陥があります。応答の遅延を扱う上で、 なんの解法も提供しません (つまり、クエリ不一致によって、スレーブに伝播させる ための書き込みに時間が取られるからです) 。

実際どんな意味があるのでしょう? myappother データベースに存在し、 その他のモデルは masterslave1slave2 間の、マスター/スレーブ 構成に存在して欲しいとしましょう。これを実現するには、 2 つのルータが必要です:

class MyAppRouter(object):
   """myapp アプリケーションのモデルの全データベース操作を制御するルータ"""

   def db_for_read(self, model, **hints):
       "myapp モデルの全操作を 'other' に向ける"
       if model._meta.app_label == 'myapp':
           return 'other'
       return None

    def db_for_write(self, model, **hints):
       "myapp モデルの全操作を 'other' に向ける"
       if model._meta.app_label == 'myapp':
           return 'other'
       return None

    def allow_relation(self, obj1, obj2, **hints):
       "myqpp 中のモデルが含まれている場合、全リレーションを許可"
       if obj1._meta.app_label == 'myapp' or obj2._meta.app_label == 'myapp':
           return True
       return None

    def allow_syncdb(self, db, model):
       "myapp アプリケーションが 'other' DB にのみあることを確かなものとする"
       if db == 'other':
           return model._meta.app_label == 'myapp'
       elif model._meta.app_label == 'myapp':
           return False
       return None

class MasterSlaveRouter(object):
   """単純なマスター/スレーブ構成を設けるルータ"""

   def db_for_read(self, model, **hints):
       "全読み込み操作をランダムなスレーブに向ける"
       return random.choice(['slave1','slave2'])

    def db_for_write(self, model, **hints):
       "全書き込み操作をマスターに向ける"
       return 'master'

    def allow_relation(self, obj1, obj2, **hints):
       "DB プール内で 2 オブジェクト間のリレーションを許可"
       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):
       "全データベース上に全モデルを明示的に配置する"
       return True

ここで設定ファイルに、以下を追加します (path.to. は、ルータを定義した モジュールへの実際の Python パスで置き換えます):

DATABASE_ROUTERS = ['path.to.MyAppRouter', 'path.to.MasterSlaveRouter']

ルータが処理される順序は重要です。ルータは DATABASE_ROUTERS 設定に 記述されている順序で問い合わせされます。この例では MyAppRouterMasterSlaveRouter よりも前に処理されます。結果として、 myapp 中のモデル に関する決定は、他の決定が行われるより前に処理されます。もし DATABASE_ROUTERS 設定が、違う順序で 2 つのルータを並べている場合、 MasterSlaveRouter.allow_syncdb() が最初に処理されるでしょう。 MasterSlaveRouter の多目的な性質が示すのは、全モデルが全データベース上で利用可能 であろうということです。

このセットアップが導入できたら、 Django コードをいくつか走らせてみましょう:

>>> # この検索は 'credentials' データベース上で行われるでしょう
>>> fred = User.objects.get(username='fred')
>>> fred.first_name = 'Frederick'

>>> # この保存も 'credentials' に直接行われます
>>> fred.save()

>>> # これらの検索はスレーブデータベースにランダムに割り当てられます
>>> dna = Person.objects.get(name='Douglas Adams')

>>> # 新しいオブジェクトが作られた際はデータベースに対する割り当てがありません
>>> mh = Book(title='Mostly Harmless')

>>> # この割り当てはルータに助言を求め、 mh を author オブジェクトと同様に、
    # 同じデータベースに割り当てます
>>> mh.author = dna

>>> # この保存は、マスターデータベース上に 'mh' インスタンス強制します
>>> mh.save()

>>> # しかし再度オブジェクトを取り出すと、再びスレーブに戻ってきます
>>> mh = Book.objects.get(title='Mostly Harmless')

データベースの手動選択

さらに Django は、コードの中でデータベース処理を完全にコントロールできる API を 提供しています。手動でのデータベース割り当ては、ルータによるデータベース割り当て よりも優先されます。

QuerySet のためのデータベース手動選択

QuerySet “チェーン (chain)” のどの点に置いても、 QuerySet のためにデータ ベースを選択できます。ただ、 QuerySet から using() を呼び出すだけで、 特定のデータベースを使う他の QuerySet を取得できます。

using() は引数を 1 つだけ受け取ります。クエリを走らせたいデータベースへ名を 受け取ります。例えば:

>>> # これは 'default' データベースで実行されます
>>> Author.objects.all()

>>> # So will this.
>>> Author.objects.using('default').all()

>>> # これは 'other' データベースで実行されます
>>> Author.objects.using('other').all()

save() のためのデータベース選択

Model.save()using キーワードを使って、どのデータベースにデータを 保存するかを特定できます。

例えば、 legacy_users データベースへオブジェクトを保存としたいとすると、 このようにします:

>>> my_object.save(using='legacy_users')

using によって特定しない場合は、 save() はルータに割り当てられたデフォル トのデータベースに保存します。

あるデータベースから他のデータベースにオブジェクを移動する

あるデータベースにインスタンスをセーブした場合は、新しいデータベースにインスタ ンスを移行する手段として、 save(using=...) を使うためことは魅了的かもしれ ません。

以下の例を考えてください:

>>> p = Person(name='Fred')
>>> p.save(using='first')  # (命令 1)
>>> p.save(using='second') # (命令 2)

命令 1 において、新しい Person オブジェクトは first データベースに保存 されています。この場合、 p はプライマリキーを持たないので、 Django は SQL の INSERT 命令を発行します。これはプライマリキーを生成し、 Django は p に プライマリキーを付与します。

命令 2 によって保存が行われるとき、 p はすでにプライマリキーの値を保持して おり、 Djagno はそのプライマリキーを新しいデータベースで使用しようとしています。 そのプライマリキーが second データベースで使われていない場合は、何の問題は ありません。新しいデータベースにそのオブジェクトがコピーされます。

しかし、 p のプライマリキーがすでに second データベースで使われている 場合は、 second データベースの現行のオブジェクトは p が保存された際に 上書きされます。

これ避けるには 2 つの方法があります。 1 つめは、インスタンスのプライマリキーを 消してしまうことです。オブジェクトにプライマリキーが無い場合は、 Django はそれを 新しいオブジェクトとして扱います。これによって second データベースからは 何も消えません:

>>> p = Person(name='Fred')
>>> p.save(using='first')
>>> p.pk = None # Clear the primary key.
>>> p.save(using='second') # Write a completely new object.

2 つめの選択肢は、 save()force_insert オプションを使うことです。 これは Django に SQL INSERT を実行させます。

>>> p = Person(name='Fred')
>>> p.save(using='first')
>>> p.save(using='second', force_insert=True)

これは Fred という名の人物 (person) が、同じプライマリキーを両方のデータ ベース上で保有していることを保証します。その second データベースに書き込もう としたときにプライマリキーがすでに使われている場合、エラーが送出されます。

削除用のデータベースを選択する

デフォルトでは、既存のオブジェクトを削除するという要求は、オブジェクトの取得に 使用されたデータベース上で実行されます:

>>> u = User.objects.using('legacy_users').get(username='fred')
>>> u.delete() # will delete from the `legacy_users` database

モデルを削除するデータベースを特定するために、 using キーワード引数を Model.delete() メソッドに渡します。この引数は save()using キーワード引数と似たように機能します。

例えば、 legacy_users データベースから new_users データベースへ移行して いるとすると、以下のコマンドが使えます:

>>> user_obj.save(using='new_users')
>>> user_obj.delete(using='legacy_users')

マルチデータベースでマネジャを使う

マネジャの db_manager() メソッドを使って、マネジャアクセスを非デフォルト データベースに渡してください。

例えば User.objects.create_user() という、データベースに関するカスタムマネ ジャメソッドを持ってるとします。 create_user() はマネジャメソッドであって QuerySet メソッドではないので、 User.objects.using('new_users').create_user() とはできません (create_user() メソッドは User.objects で上のみ有効です。マネジャで あって、マネジャから得られた QuerySet オブジェクト上ではありません) 。この 解法は以下のようになります:

User.objects.db_manager('new_users').create_user(...)

db_manager() は指定したデータベースに紐づけたマネジャのコピーを返します。

マルチデータベースで get_query_set() を使う

マネジャの get_query_set() をオーバーライドする際には、 (super() を 使って) 親を呼び出すか、マネージャの _db のつく属性 (使うデータベース名 を含む文字列) を適切に扱ってください。

例えば get_query_set メソッドからカスタムされた QuerySet クラスを返し たいなら、こうしてください:

class MyManager(models.Manager):
    def get_query_set(self):
        qs = CustomQuerySet(self.model)
        if self._db is not None:
            qs = qs.using(self._db)
        return qs

Django admin インタフェースにマルチデータベースを表示する

Django の admin は、マルチデータベースの明示的なサポートはしていません。ルータ チェインによって指定された他のデータベス上のモデルのため、 admin インタフェース を提供したいなら、カスタム ModelAdmin クラスを 書く必要があります。特定のデータベースを admin インタフェースのコンテンツに 使用するように命令します。

ModelAdmin オブジェクトは 5 つのメソッドを持っています。マルチデータベースを サポートするためカスタマイズできます:

class MultiDBModelAdmin(admin.ModelAdmin):
    # A handy constant for the name of the alternate database.
    using = 'other'

    def save_model(self, request, obj, form, change):
        # Tell Django to save objects to the 'other' database.
        obj.save(using=self.using)

    def delete_model(self, request, obj):
        # Tell Django to delete objects from the 'other' database
        obj.delete(using=self.using)

    def queryset(self, request):
        # Tell Django to look for objects on the 'other' database.
        return super(MultiDBModelAdmin, self).queryset(request).using(self.using)

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        # Tell Django to populate ForeignKey widgets using a query
        # on the 'other' database.
        return super(MultiDBModelAdmin, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs)

    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
        # Tell Django to populate ManyToMany widgets using a query
        # on the 'other' database.
        return super(MultiDBModelAdmin, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)

この実装はここでは、マルチデータベースストラテジー機能を提供します。与えられた タイプのすべてのオブジェクトが、特定のデータベースに格納されます (例えば、 全 User オブジェクトを other データベースに)。 マルチデータベースの 使い方がより複雑なら、 ModelAdmin にそのストラテジーの反映が必要になります。

インラインは同様の方法で処理できます。メソッドを 3 つカスタマイズします:

class MultiDBTabularInline(admin.TabularInline):
    using = 'other'

    def queryset(self, request):
        # Tell Django to look for inline objects on the 'other' database.
        return super(MultiDBTabularInline, self).queryset(request).using(self.using)

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        # Tell Django to populate ForeignKey widgets using a query
        # on the 'other' database.
        return super(MultiDBTabularInline, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs)

    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
        # Tell Django to populate ManyToMany widgets using a query
        # on the 'other' database.
        return super(MultiDBTabularInline, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)

モデル admin 定義を書いた後は、それらは任意の Admin インスタンスに登録でき ます:

from django.contrib import admin

# Specialize the multi-db admin objects for use with specific models.
class BookInline(MultiDBTabularInline):
    model = Book

class PublisherAdmin(MultiDBModelAdmin):
    inlines = [BookInline]

admin.site.register(Author, MultiDBModelAdmin)
admin.site.register(Publisher, PublisherAdmin)

othersite = admin.Site('othersite')
othersite.register(Publisher, MultiDBModelAdmin)

この例は 2 つの admin サイトをセットアップします。 1 つめのサイトでは、 AuthorPublisher オブジェクトが見られます。 Publisher オブジェクト は、その出版社が発行した書籍を表示するインライン表を持っています。

マルチデータベースで生のカーソルを使う

2 つ以上のデータベースを使っているなら、 django.db.connections を、特定の データベースへの接続(およびカーソル)を取得するために使えます。 django.db.connections は辞書ライクオブジェクトです。これによってエイリアスを 使用して特定の接続を取得できます:

from django.db import connections
cursor = connections['my_db_alias'].cursor()

マルチデータベースの制限

クロスデータベースリレーション

Django は今のところ、複数のデータベースに及ぶ外部キーか多対多リレーションを サポートを提供していません。別のデータベースにモデルを分配するルータを使って いたなら、それらのモデルに定義される、すべての外部キーと多対多リレーションは 1 つのデータベースに入れられます。

これは参照整合のためです。2 オブジェクト間のリレーションを維持するために、 Django は正当に参照されたオブジェクトのプライマリキー知る必要があります。簡単に プライマリキーの妥当性を評価することはできません。

InnoDB で Postgres 、 Oracle 、 MySQL を使っているなら、データベースの整合性 レベルで強制されます。データベースレベルのキーの制約は、検証できないリレーション の生成を防ぎます

MyISAM テーブルで SQLite か MySQL を使っているなら、強制される参照整合性はあり ません。結果として、 ‘偽の (fake)’ クロスデータベースの外部キーができるかも しれません。しかしこの設定は Django で公式にサポートされているものではありま せん。