티스토리 뷰
이 주제에 대해서 알아보게 된 것은 최근 제가 다니고 있는 회사에서 하나의 DB로 데이터들을 처리할려고 하니 이에 대한 해결책이 뭐가 있을 까 하고 고민을 해봤습니다.
처음에 내가 생각한 것은 다음과 같습니다.
1. MSA (MIcro Soft Arhitecture) : 마 ~ 우리가 자존심이 있지 쪼개보자!!
2. DB 분산 작업: 기존 애플리케이션 내부 DB를 나누게 되면 이에 대한 쓰기 작업을 분산 시킬 수 있지 않을까?
1번은 우리 회사에서 하기에는 인원도 부족하고, 장기적으로 바라보는 작업이기에 현실적으로 바로 접근하기에는 문 턱이 높기 때문에 저는 2번을 먼저 해보는 게 좋지 않을까 해서 조사하게 되었습니다.
제가 찾아본 공식문서는 다음과 같습니다.
https://docs.djangoproject.com/en/4.0/topics/db/multi-db/
멀티 DB로 쪼개는 것에 대한 저의 예제를 보여드리도록 하겠습니다.
프로젝트의 디렉토리는 다음과 같습니다.
DB - docker-compose.yml
version: '3.3'
services:
postgres1:
container_name: primary-db
image: postgres:13.4
restart: unless-stopped
ports:
- "35432:5432"
environment:
POSTGRES_DB: "primary_db"
POSTGRES_HOST_AUTH_METHOD: "md5"
POSTGRES_PASSWORD: "primarydb"
POSTGRES_USER: "primary_user"
volumes:
- default-db:/var/lib/postgresql/data
postgres2:
container_name: log-db
image: postgres:13.4
restart: unless-stopped
ports:
- "45432:5432"
environment:
POSTGRES_DB: "log_db"
POSTGRES_HOST_AUTH_METHOD: "md5"
POSTGRES_PASSWORD: "logdb"
POSTGRES_USER: "log_user"
volumes:
- log-db:/var/lib/postgresql/data
volumes:
default-db:
log-db:
DB 구성은 다음 도커 컴포즈로 실행해주세요
deocker-compose up -d --build
account.models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.base_user import BaseUserManager
class UserManger(BaseUserManager):
def create_user(self, email, username, password, **extra_fields):
if not email:
raise ValueError('The email must be set.')
email = self.normalize_email(email)
user = Account()
user.set_email(email)
user.username = username
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, username, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True')
return self.create_user(email, username, password, **extra_fields)
class Account(AbstractUser):
email = models.EmailField(unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
objects = UserManger()
def set_email(self, email):
self.email = email
def __repr__(self):
return f'아이디: {self.id}, 이름 : {self.username}, 이메일: {self.email}'
해당 모델은 간단한 유저 모델입니다.
blog.models.py
from django.db import models
from account.models import Account
class Blog(models.Model):
account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='blogs')
title = models.CharField(max_length=25)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
objects = models.Manager()
def __repr__(self):
return f'title : {self.title}'
log.models.py
from django.db import models
from django.utils.translation import gettext_lazy as _
from account.models import Account
class LogType(models.TextChoices):
LOGIN = 'login', _('LOGIN'), # NAME / VALUE / LABEL
LOGOUT = 'logout', _('LOGOUT')
@staticmethod
def index_to_type(index):
if index == 0:
return LogType.LOGIN
else:
return LogType.LOGOUT
class Log(models.Model):
account = models.ForeignKey(Account, related_name='logs', null=True, on_delete=models.CASCADE)
model_type = models.CharField(max_length=20, choices=LogType.choices, default=LogType.LOGIN)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = models.Manager()
def __repr__(self):
return f'model_type: {self.model_type}'
모델들은 다음과 같이 간단하게 구성하였습니다.
이제 중요한 settings.py를 보도록 할까요?
settings.py (✌゚∀゚)☞
INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 추가된 항목
'rest_framework',
'log.apps.LogConfig',
'blog.apps.BlogConfig',
'account.apps.AccountConfig'
]
DATABASES
DATABASES = {
'default': {},
'primary_db': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'primary_db',
'USER': 'primary_user',
'PASSWORD': 'primarydb',
'HOST': '127.0.0.1',
"PORT": "35432",
},
'log_db': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'log_db',
'USER': 'log_user',
'PASSWORD': 'logdb',
'HOST': '127.0.0.1',
"PORT": "45432",
},
}
🌟🌟🌟DATABASE_ROUTES 🌟🌟🌟
DATABASE_ROUTERS = ['config.defaultRouter.DefaultRouter', 'config.logRouter.LogRouter']
해당 설정은 각 모델에 따른 DB를 어떤 DB로 라우팅 할 것인지 설정하는 환경변수입니다.
정말 중요한 설정이니 Multiple Database을 할 때에는 필수적입니다.
AUTH_USER_MODEL
AUTH_USER_MODEL = 'account.Account'
⭐⭐- Router -⭐⭐
config.defaultRouter.py
class DefaultRouter:
route_app_labels = {'account', 'blog'}
db_name = 'primary_db'
def db_for_read(self, model, **hints):
"""
Attempts to read logs and others models go to primary_db.
"""
if model._meta.app_label in self.route_app_labels:
return self.db_name
return None
def db_for_write(self, model, **hints):
"""
Attempts to write logs and others models go to primary_db.
"""
if model._meta.app_label in self.route_app_labels:
return self.db_name
return None
def allow_relation(self, obj1, obj2, **hints):
"""
Allow relations if a model in the obj1 or obj2 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
'primary_db' database.
"""
if app_label in self.route_app_labels:
return self.db_name
return None
저는 기본적인 구성 테이블들을 defaultRouter 에 설정해놨습니다.
route_app_labels = {'account', 'blog'}
이 부분에 독자들이 원하는 앱들을 적어주시면 됩니다.
db_name = 'primary_db'
이 부분에는 db 이름을 적어주세요
config.logRouter.py
class LogRouter:
route_app_labels = {'log'}
db_name = 'log_db'
def db_for_read(self, model, **hints):
"""
Attempts to read logs and others models go to log_db.
"""
if model._meta.app_label in self.route_app_labels:
return self.db_name
return None
def db_for_write(self, model, **hints):
"""
Attempts to write logs and others models go to log_db.
"""
if model._meta.app_label in self.route_app_labels:
return self.db_name
return None
def allow_relation(self, obj1, obj2, **hints):
"""
Allow relations if a model in the obj1 or obj2 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
'log_db' database.
"""
if app_label in self.route_app_labels:
return self.db_name
return None
같은 설명이니 생략하겠습니다.
이제 실제로 한 번 사용해볼까요?
아 잠시 실제로 따라할 때 migrate 부분은 다음과 같이 해주세요( 마이그레이트 진행 전에 makemigrations는 잊지 마세요~)
./manage.py migrate --database=primary_db
./manage.py migrate --database=log_db
shell로 확인해보기
이렇게 저장했을 때 primary_db의 상황
log_db는 ?
보시다시피 텅 비어 있다.
하지만 강제로 log_db에 데이터에 넣어줄 수 있습니다. 어떻게 하냐구요? 바로 보여드리겠습니다 ㅎㅎ
보시다시피 log_db에 저장되는 것을 보실 수 있습니다.
하지만 유의해야 할 점은 save()를 할 때 default도 건들게 되므로 굳이 db_manager()를 통해 변경할려는 시도를 하지는 말자!
default는 라우터로 설정한 것으로 된다는 것을 아시면 됩니다.
그러면 all() 하면 몇개 가져올까? 볼까요?
우려와 달리 default의 갯수만 가져옵니다.
그러면 log도 한 번 볼까요?
위의 경우는 위 테스트에서 db_manager를 통해 log_db에 account 데이터가 저장되어 있기 때문에 된 걸로 확인되었습니다.
다시 확인해본 결과 다음과 같은 결과가 나왔습니다. 결론적으로 db를 쪼개서 관리하는 것은 아예 독립적인 테이블일 때만 가능한 것으로 확인되었습니다. ㅜㅜㅜㅜ
primary_db : log_log
log_db: log_log
우리 생각대로 라우터 방향으로 알맞게 들어갔다.
마지막으로 blog를 확인해보자!
log_db : blog_blog
primary_db: blog_blog
코드 첨부 : https://github.com/VIXXPARK/django-remind/tree/main/django_multiple_db
'파이썬 > 장고' 카테고리의 다른 글
[Django][Http] get의 caching을 이용한 데이터베이스 부하 줄이기 (0) | 2022.01.18 |
---|---|
[Python][Django][Celery] Serializer를 pickle로!! (0) | 2021.12.21 |
[django][shell] 깔끔하게 정리한 django 관련 스크립트 (0) | 2021.08.18 |
[django][serializer] serializer 와 deserializer 구분하기 (0) | 2021.08.04 |
[drf][viewset] @action (0) | 2021.08.04 |
- Total
- Today
- Yesterday
- docker-compose
- 그래프
- Python
- setattr
- Celery
- DRF
- ubuntu
- postgres
- BFS
- PostgreSQL
- Java
- 카카오
- 알고리즘
- Linux
- 프로그래머스
- docker
- dockerignore
- Collections
- 면접
- 2021 KAKAO BLIND RECRUITMENT
- headers
- 파이썬
- Spring
- 자바
- Command Line
- 백준
- django
- env
- thread
- Pattern
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |