노트

읽기 전용 복제본

Read Replica

백엔드#db#aws · 연결된 개념 9개

쉽게 말하면

읽기 전용 복제본은 반장 노트를 복사해 친구들이 나눠 보게 하는 것처럼, 원본 DB(writer)는 쓰기를 맡고 읽기는 여러 복사본이 나눠 받는 구성이에요. 보는 사람이 늘면 복사본만 늘리면 되죠.

비유가 깨지는 곳 복사본은 비동기로 조금 늦게 따라와서 방금 저장한 글이 안 보일 수 있어요. 그래서 쓰기 직후 읽기, 트랜잭션 안의 읽기, SELECT ... FOR UPDATE는 writer로 보내요.

읽기 전용 복제본은 주 DB(primary, writer)의 데이터를 복제해 읽기 요청만 받는 DB 인스턴스다. 쓰기는 writer 한 곳에서 처리하고, 읽기를 여러 복제본으로 나눠 부하를 분산한다.

flowchart LR
  App["앱"] -- "쓰기" --> W[("writer")]
  App -- "읽기" --> R1[("복제본 1")]
  App -- "읽기" --> R2[("복제본 2")]
  W -. "비동기 복제 (지연)" .-> R1
  W -. "비동기 복제 (지연)" .-> R2

왜 나누나

  • 부하 분산: 대부분의 서비스는 읽기가 쓰기보다 훨씬 많다. 읽기를 복제본으로 돌리면 writer는 쓰기에 집중한다
  • 수평 확장(horizontal scaling): 복제본을 추가하는 것만으로 읽기 처리량이 늘어난다. 인스턴스를 키우는 수직 확장(vertical scaling)과 달리 재시작이 필요 없다(수직 확장과 수평 확장)
  • 가용성: writer에 문제가 생기면 복제본을 승격(promotion)해 중단 시간을 줄인다. 다른 리전에 두면 가까운 사용자의 응답이 빨라진다

복제 지연(replication lag)

보통 비동기로 복제되므로 writer에 쓴 직후 복제본에서 읽으면 옛 값이 보일 수 있다(최종 일관성).

  • "방금 저장한 내 글이 안 보인다" → 쓰기 직후의 읽기, 트랜잭션 안의 읽기는 writer로 보낸다
  • 잠금(SELECT ... FOR UPDATE)을 쓰는 흐름은 반드시 writer로

Django DB 라우터로 나누기

Django는 DATABASE_ROUTERS에 라우터 클래스를 등록해 읽기·쓰기 DB를 고른다.

class ReplicaRouter:
    def db_for_write(self, model, **hints):
        return "default"
 
    def db_for_read(self, model, **hints):
        if transaction.get_connection("default").in_atomic_block:
            return "default"            # 트랜잭션 안에서는 writer
        return random.choice(["default", "replica"])
  • 읽기를 전부 복제본으로 보내면 writer의 여유 자원을 버리게 된다
  • 복제본이 여러 개면 앱에서 고르기보다 DB 쪽의 묶음 엔드포인트(예: Aurora의 리더·커스텀 엔드포인트)에 분산을 맡기는 편이 낫다
  • 멀티 DB를 고려하지 않은 서드파티 라이브러리가 default를 하드코딩해 문제를 일으킬 수 있다

관리형 DB가 이런 복제를 어디까지 대신해 주는지는 IaaS·PaaS·SaaS, 복제 방식 전반(동기·비동기, 리더 선출)은 데이터베이스 다중화, 쓰기까지 나누는 방법은 샤딩을 본다.

출처: PyCon KR 2019 「Django DB Router로 Database Read Replicas 100% 활용기 및 Troubleshooting 경험 공유」 한종원 · Django 문서: Database routers

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 읽기 캐시 전략

    값비싼 연산 결과나 자주 읽는 데이터를 DB보다 빠른 메모리 저장소에 두고, 읽을 때 캐시를 먼저 보는 전략. 응답 속도를 높이고 DB 부하를 줄이며, 캐시 계층만 따로 확장할 수도 있다.

  • 역색인과 검색 엔진

    역색인(inverted index)은 "각 문서에 어떤 단어가 있나" 대신 거꾸로 "이 단어가 어느 문서들에 있나"를 미리 만들어 둔 자료구조다. 책 뒤의 찾아보기와 같다. OpenSearch·Elasticsearch 같은 검색 엔진은 이 구조로 전문 검색(full-text search)을 빠르게 한다.

  • localStorage와 sessionStorage

    브라우저에 문자열 키-값을 저장하는 Web Storage API의 두 가지. 쿠키와 달리 요청에 자동으로 실리지 않고, 출처 단위로 나뉘며(same-origin-policy), 보통 출처당 5MB 안팎을 쓸 수 있다. 동기 API라 큰 데이터를 자주 읽고 쓰면 메인 스레드를 막는다.

  • 데이터 무결성

    데이터 무결성(data integrity)은 저장된 데이터가 정확하고 일관되며 믿을 수 있는 상태로 유지되는 것이다. 재고가 100개로 보이는데 실제로 50개라면 무결성이 깨진 것이다. 관계형 DB는 이를 제약 조건(constraint)으로 강제한다.

  • JDBC와 JdbcTemplate

    JDBC(Java Database Connectivity)는 자바가 여러 DB와 통신하기 위한 표준 API이고, Hibernate·MyBatis·스프링 JDBC 모두 그 위에 있다. JdbcTemplate은 스프링이 JDBC의 반복 작업(연결 열고 닫기, 예외 변환, 결과 순회)을 대신해 주는 클래스다.

보기 옵션