본문 바로가기
Django/Django개념

Django replica 적용기(RDS + Postgresql)

by hyun-am 2023. 1. 8.

Database 이용 시 Replica를 사용하는 이유

  1. High availability(고가용성) : replica 중 하나가 다운되더라도 데이터베이스를 항상 사용할 수 있도록 복제본을 사용할 수 있습니다. 예를 들면 Django에서 사용할 때 어떤 쿼리가 데이터베이스를 길게 잡아먹어서 데이터베이스가 다운되어도 읽기전용 Replica가 살아 있으면 해당 데이터베이스로 대신 사용 가능해서 고가용성으로 사용할 수 있습니다.
  2. Load balancing(로드벨런싱) : replica를 사용하여 들어오는 요청의 로드를 여러 시스템에 분산하여 데이터베이스의 성능과 확장성을 향상 시킬 수 있습니다. 예를 들면 데이터베이스 한개에서 Read와 Write를 사용하는데 특정 API를 Read하는 전용 데이터베이스를 생성시키면 작업을 분산 시킬 수 있습니다.
  3. Disaster recovery(재해 복구) : replica은 재해(Disaster) 발생 시 장애 조치 옵션을 제공하는 데 사용할 수 있으므로 주 서버가 다운되더라도 데이터베이스가 계속 작동할 수 있습니다.
  4. Data localization(데이터 로컬라이제이션) : replica는 여러 location에 저장하는데 사용할 수 있으며, 이는 규정 준수 또는 규제 목적에 유용할 수 있습니다.
  5. Improved performance(향상된 성능) : replica를 사용하여 기본 데이터베이스에서 읽기 전용 쿼리를 오프로드 하여 쓰기 작업을 위한 기본 데이터베이스의 성능을 향상 시킬 수 있습니다.

 

Django에서 사용하기

Django에서 사용하기 위해 먼저 아래 주소에 있는 공식문서를 참고하고 진행했습니다.

링크 : https://docs.djangoproject.com/en/4.1/topics/db/multi-db/

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

개념

Django에서 Replica는 일반적으로 들어오는 요청의 부하를 여러 데이터베이스 서버에 분산시켜 데이터베이스의 성능과 확장성을 향상시키는데 사용됩니다. Django에서 Replica를 사용하려면 데이터베이스 서버를 설정하고 또한 Django에서도 이것을 사용하도록 구성해야 합니다.

 

여기서 Django의 데이터베이스 라우팅 기능을 사용하면 replica를 사용할 수 있습니다. 이를 통해 다양한 유형의 데이터베이스 작업에 대해 서로 다른 데이터베이스 서버를 지정할 수 있습니다. 예를들면 위에서 설명한 것 처럼 쓰기는 default 데이터베이스에서 작업하게 하고 특정 읽기 API는 replica database에서 사용할 수 있게할 수 있습니다.

 

Django에서 데이터베이스 라우팅을 설정하려면 사용자 지정 라우팅 클래스를 정의하고 Django 설정 파일의 DATABASE_ROUTERS 설정에 지정해야 합니다. 라우팅 클래스는 읽기 전용 쿼리에 사용할 데이터베이스의 이름을 반환하는 db_for_read라는 메서드를 정의해야 합니다. 그런 다음 db_for_write메서드를 사용하여 쓰기 작업을 위한 기본 데이터베이스 서버를 지정할 수 있습니다.

 

Django code를 통해 확인하기 - local db 사용할 경우 코드

settings.py에서 DATABASES에 replica 추가하기

DATABASES = {
    'default': {
        'NAME': 'postsgres1',
        'ENGINE': 'django.db.backends.postgresql',
        'USER': 'postgres',
        'PASSWORD': 'postgres'
    },
    'replica': {
        'NAME': 'postsgres2',
        'ENGINE': 'django.db.backends.postgresql',
        'USER': 'postgres2',
        'PASSWORD': 'postgres'
    }
}
  • 이런식으로 postgres2라는 replica database를 등록해 줬습니다.

특정 database query에 replica 사용하기

 

유저들이 제일 많이 사용하고, 읽기 점유율이 높은 쿼리를 조사해서 해당 ORM을 사용할 때 replica를 이용할 수 있게 코드를 변경했습니다.

 

예를 들면 FoodInformation이라는 식품 정보가 담긴 모델이 있고 그 모델을 불러 올 때 원래 코드는 아래와 같습니다.

FoodInformation.objects.filter(Q(name__startswith=search_keyword))

이제 replica를 사용한 코드는 다음과 같습니다.

FoodInformation.objects.using('replica').filter(Q(name__startswith=search_keyword))

using()을 추가해서 사용했습니다.

using() 메서드는 특정 쿼리에 사용할 데이터베이스 서버를 지정할 수 있는 Django의 Model 메서드입니다.

이것은 주어진 쿼리에 사용할 데이터베이스 서버를 지정할 수 있으므로 replica작업을 할 때 유용하게 사용할 수 있습니다.

Testcode 개선하기

이 상태로 테스트코드를 돌리면 에러가 발생하는것을 확인 할 수 있습니다.

 

에러코드 예시

AssertionError: Database queries to 'replica' are not allowed in this test. Add 'replica' to test.ObjectAPITestCase.databases to ensure proper test isolation and silence this failure.

이러한 문제가 계속 발생해서 해당 에러를 해결한 블로그가 있어서 참고했습니다.

참고 블로그 링크 : https://sjquant.tistory.com/52

 

Django에서 Replica DB가 있을 때의 테스트 코드 문제 해결하기

Django에서 ReplicaDB (ReadonlyDB)가 있을 때 테스트 코드를 작성하면 다음과 같은 문제가 발생할 수 있습니다. 분명 데이터베이스 쓰기에 성공했는데, 해당 데이터를 읽어보면 데이터가 없는 경우가

sjquant.tistory.com

 

먼저 이러한 에러가 나오는 이유는 ObjectAPITestCase Test Case Class에서 replica에 대한 attribute가 추가 안 되어서 나오는 에러입니다 이것을 추가하기 위해 다음과 같이 code를 수정했습니다.

 

class ObjectAPITestCase(APITestCase):
    databases = ["default", "replica"]

	... 생략
	...
		@classmethod
    def setUpClass(cls):
        connections["replica"]._orig_cursor = connections["replica"].cursor
        connections["replica"].cursor = connections["default"].cursor
        super().setUpClass()

    @classmethod
    def tearDownClass(cls):
        connections["replica"].cursor = connections["replica"]._orig_cursor
        super().tearDownClass()

이렇게 추가하면 에러가 사라지는 것을 확인할 수 있습니다.

RDS에서 replica 생성하기

RDS에서 복제하고 싶은 데이터베이스를 선택한 후 읽기 전용 복제본 생성을 하겠습니다.

그 후 replica-db이름을 정해주고 생성하면 다음과 같이 복제본이 생성됩니다.

이제 정말로 이게 read only인지 확인하기 위해 insert query를 실행시켜보겠습니다. 이것은 tableplus에서 진행했습니다.

 

그러면 다음과 같은 경고창이 나오는 것을 확인할 수 있습니다.

 

이제 위에서 local에 적용한 것 처럼 QA server RDS도 적용하겠습니다.

여기서 host는 rds replica를 생성하면서 생성된 주소입니다

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql_psycopg2",
        "NAME": "비밀_qa",
        "USER": "비밀",
        "PASSWORD": "비밀",
        "HOST": "비밀.rds.amazonaws.com",
        "PORT": 5432,
        "CONN_MAX_AGE": 60,
        "OPTIONS": {
            "connect_timeout": 10,
        },
    },
    "replica": {
        "ENGINE": "django.db.backends.postgresql_psycopg2",
        "NAME": "비밀_qa",
        "USER": "비밀",
        "PASSWORD": "비밀",
        "HOST": "비밀-replica.rds.amazonaws.com",
        "PORT": 5432,
        "CONN_MAX_AGE": 60,
        "OPTIONS": {
            "connect_timeout": 10,
        },
    },
}

이런식으로 설정하면 local code와 똑같이 동작하는 것을 확인할 수 있습니다.

댓글