이 글은 Django Docs를 읽고 정리한 것입니다.
DATABASE_ROUTERS = [<path to your custom router>]
instance._state.db
를 실행해 맞는 데이터베이스를 찾는다.DATABASE_ROUTERS를 특별히 지정하는 경우는 여러 개의 데이터베이스를 사용하는 상황이 될 것이다. 그렇다면 장고에서는 여러 개의 데이터베이스를 어떻게 구성해 사용할 수 있을까? 위의 <path to your custom router>
에 들어가는 정보를 알아 보자.
DATABASES = {
'default': {
'NAME': 'app_data',
'ENGINE': 'django.db.backends.postgresql',
'USER': 'postgres_user',
'PASSWORD': 's3krit'
},
'users': {
'NAME': 'user_data',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'priv4te'
}
}
'default' = {}
으로 비워 두어야 한다.DATABASE_ROUTERS
환경 변수를 정의해 각 앱에서 사용되는 모델이 어느 데이터베이스로 라우팅 되어야 하는 지를 정의해야 한다.django.utils.connection.ConnectionDoesNotExist
database router 클래스는 아래와 같이 4개의 메소드로 이루어져 있다. 이를 오버라이딩 해서 커스텀 라우터를 생성하면 된다. 이 때, 아래의 4개 메소드가 반드시 주어지지는 않아도 된다. 단, 메소드가 주어지지 않는 경우에 대해서는 장고가 관련성 체크시 해당 라우터를 건너 뛴다는 점은 주의해야 한다.
**hints
hints
파라미터에 담아서 줄 수 있다.instance
이다. 이 instance는 진행중인 읽기 또는 쓰기 쿼리에 관련 있는 객체 인스턴스로, 새롭게 저장해야 하는 인스턴스이거나 혹은 다대다 관계에 추가 되어야 하는 인스턴스 등 여러 가지가 있을 수 있다(물론 hint가 아예 없을 수도 있다).**hints
db_for_read
메소드에서와 마찬가지로 write 쿼리 실행시 model 타입의 객체에 사용해야 하는 데이터베이스를 알려주기 위해 사용 된다.**hints
**hints
db
를 alias로 갖는 데이터베이스에 migration을 해도 되는 지를 결정한다.app_label
은 migration 되는 앱의 label이다.model_name
은 migration 되는 모델의 모델명에 의해 결정 된다.model._meta.model_name
(model의 __name__
의 소문자 버전과 동일함) 값에 해당한다.model_name
인자 값이 주어지는 경우 hints는 일반적으로 'model' 키 값을 갖는다. 이 값은 보통 어떠한 속성이나 메소드 등을 갖지 않아 'model' 키의 _meta
를 통해 model_name
을 알아내야 한다.allow_migrate()
이 False를 반환하면 주어진 model_name
에 대한 마이그레이션은 실행 되지 않는다.지금부터 나열되는 내용은 실제로 어떻게 사용할 수 있을 지에 대한 하나의 예시이며 독스에 나와 있는 예시이다.
독스에서는 auth db를 따로 관리하고 있는데 실제로 이 방법이 좋은 지는 모르겠다. 우리 서비스에서는 Aurora를 쓰며 라이터와 리더를 분리해 라이터는 읽기 + 쓰기 모두를, 리더는 읽기 작업만 할 수 있도록 하고 있다.
이 방법에 대해서는 파이콘 강의에서 본 내용을 토대로 정리해 놓았으며 추후 우리 서비스에서 사용한 방법과 결합해 조금 더 자세한 사용 방법을 적어 보려 한다.
아래와 같이 사용할 라우터를 추가하면 된다.
DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.PrimaryReplicaRouter']
AuthRouter
-> PrimaryReplicaRouter
순으로 실행 된다.사용할 데이터베이스 예시는 다음과 같다.
DATABASES = {
'default': {},
'auth_db': {
'NAME': 'auth_db_name',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'swordfish',
},
'primary': {
'NAME': 'primary_name',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'spam',
},
'replica1': {
'NAME': 'replica1_name',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'eggs',
},
'replica2': {
'NAME': 'replica2_name',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'bacon',
},
}
auth
와 contenttypes
의 경우 장고의 기본 앱으로, 각각 Auth
, ContentType
을 모델로 가지며 서로 연결 되어 있다.auth_db
를 사용 한다.아래 예시에서 auth
, contenttypes
관련된 operation은 모두 auth_db
로 향하도록 라우팅 되어 있다.
class AuthRouter:
"""
auth와 contenttypes 앱의 모델에 일어나는 DB operation에 대한 라우터이다.
"""
route_app_labels = {'auth', 'contenttypes'}
def db_for_read(self, model, **hints):
"""
Attempts to read auth and contenttypes models go to auth_db.
"""
if model._meta.app_label in self.route_app_labels:
return 'auth_db'
return None
def db_for_write(self, model, **hints):
"""
Attempts to write auth and contenttypes models go to auth_db.
"""
if model._meta.app_label in self.route_app_labels:
return 'auth_db'
return None
def allow_relation(self, obj1, obj2, **hints):
"""
Allow relations if a model in the auth or contenttypes apps is
involved.
"""
if (
obj1._meta.app_label in self.route_app_labels or
obj2._meta.app_label in self.route_app_labels
):
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""
Make sure the auth and contenttypes apps only appear in the
'auth_db' database.
"""
if app_label in self.route_app_labels:
return db == 'auth_db'
return None
PrimaryReplicaRouter
는 primary와 두 개의 replica를 사용한다.import random
class PrimaryReplicaRouter:
def db_for_read(self, model, **hints):
"""
replica를 랜덤하게 골라 사용한다(부하 분산).
"""
return random.choice(['replica1', 'replica2'])
def db_for_write(self, model, **hints):
"""
읽기 작업은 primary로 라우팅 한다.
"""
return 'primary'
def allow_relation(self, obj1, obj2, **hints):
"""
primary 또는 replica pool에 있는 객체들은 서로 관계를 갖고 있다(auth와 관련 되어 있지 않으면 전부 관계를 갖고 있다).
"""
db_set = {'primary', 'replica1', 'replica2'}
if obj1._state.db in db_set and obj2._state.db in db_set:
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""
auth와 관련된 모델이 아니라면 migrate가 허용 된다.
auth는 auth_db에서 따로 관리한다.
"""
return True
쿼리를 짤 때 수동으로 원하는 데이터베이스를 선택해야 할 때가 있다. 예를 들어, 간단한 로직이 있는데 그 안에 들어가는 쿼리가 리더만 조회하도록 하고 싶을 수 있다. 그러면 이 때는 using
을 이용하면 된다.
Author.objects.all()
Author.objects.using('default').all()
Author.objects.using('replica').all()