읽기 전용 복제본
읽기 전용 복제본 (Read Replica)
개요
읽기 전용 복제본(Read Replica)이란 기본 데이터베이스(Primary/Source DB)의 데이터를 실시간으로 복제하여 읽기 전용 쿼리만을 처리하도록 설계된 복제 데이터베이스 인스턴스를 의미합니다.
현대적인 웹 서비스는 쓰기 작업(Create, Update, Delete)보다 읽기 작업(Read/Select)의 비중이 압도적으로 높은 경우가 많습니다. 단일 데이터베이스 서버가 모든 요청을 처리할 경우 CPU 및 메모리 자원 고갈로 인한 성능 저하가 발생하며, 이는 곧 서비스 전체의 응답 속도 저하로 이어집니다. 읽기 전용 복제본은 이러한 읽기 부하를 여러 서버로 분산시켜 시스템의 전체적인 처리량(Throughput)을 높이고 가용성을 확보하는 것을 목적으로 합니다.
동작 원리 및 메커니즘
읽기 전용 복제본은 기본 DB에서 발생하는 모든 변경 사항을 기록한 복제 로그(Replication Log)를 기반으로 동작합니다. MySQL의 경우 Binary Log를, PostgreSQL의 경우 WAL(Write Ahead Log)를 사용하여 변경 이력을 기록하고 이를 복제본으로 전송하여 데이터를 동기화합니다.
동기화 방식
데이터를 복제하는 방식은 크게 동기식과 비동기식으로 나뉩니다.
| 구분 | 동기식 복제 (Synchronous) | 비동기식 복제 (Asynchronous) |
|---|---|---|
| 동작 방식 | 기본 DB가 복제본의 수신 확인(ACK)을 받은 후 커밋 완료 | 기본 DB가 로그를 전송한 즉시 커밋 완료 (확인 대기 없음) |
| 데이터 일관성 | 매우 높음 (두 서버의 데이터가 항상 동일) | 낮음 (일시적인 데이터 불일치 발생 가능) |
| 쓰기 성능 | 낮음 (복제본 응답 대기 시간만큼 지연 발생) | 높음 (기본 DB 단독 처리 속도로 수행) |
| 가용성 | 낮음 (복제본 장애가 기본 DB 쓰기 중단으로 이어짐) | 높음 (복제본 장애와 무관하게 기본 DB 서비스 가능) |
대부분의 읽기 전용 복제본 구성은 성능 최적화를 위해 비동기식 복제를 채택합니다.
주요 활용 사례
읽기 전용 복제본은 다음과 같은 시나리오에서 핵심적인 역할을 수행합니다.
- 읽기 부하 분산 (Read Scaling): 사용자 수가 급증하여 SELECT 쿼리가 폭증할 때, 복제본의 개수를 늘려(Scale-out) 트래픽을 분산합니다.
- 분석 및 통계 쿼리 분리: 대량의 데이터를 집계하는 무거운 분석 쿼리(OLAP 성격)는 기본 DB의 자원을 과다하게 사용하여 서비스 장애를 유발할 수 있습니다. 이를 복제본에서 수행하여 서비스 안정성을 확보합니다.
- 보고서 생성 및 백업: 실시간 서비스에 영향을 주지 않고 정기적인 리포트를 생성하거나, 복제본을 이용해 데이터 백업을 수행함으로써 기본 DB의 I/O 부하를 줄입니다.
구현 및 구성 전략
읽기/쓰기 쿼리 분리 (Read/Write Splitting)
애플리케이션 레벨에서 쿼리의 성격에 따라 연결할 DB 엔드포인트를 다르게 설정해야 합니다.
- Write Endpoint: INSERT, UPDATE, DELETE 쿼리가 전송되는 기본 DB 주소
- Read Endpoint: SELECT 쿼리가 전송되는 복제본들의 로드 밸런서 주소
애플리케이션 레벨의 분리 외에도 ProxySQL, MaxScale 또는 AWS Aurora Reader Endpoint와 같은 DB 프록시/미들웨어를 도입하여 인프라 레벨에서 쿼리를 자동으로 라우팅함으로써 애플리케이션 코드의 복잡도를 낮출 수 있습니다.
⚠️ 트랜잭션 처리 주의점: 트랜잭션 내에서 데이터를 수정(Write)한 직후 동일 트랜잭션 내에서 해당 데이터를 조회(Read)해야 하는 경우, 반드시 기본 DB(Write Endpoint)에서 읽기를 수행해야 합니다. 복제본을 사용할 경우 복제 지연으로 인해 수정 전 데이터가 조회되어 비즈니스 로직에 오류가 발생할 수 있기 때문입니다.
구현 예시 (Pseudo Code)
class DatabaseManager:
def __init__(self):
self.primary_db = connect("primary-db.example.com") # 쓰기 전용
self.replica_pool = [
connect("replica-1.example.com"),
connect("replica-2.example.com")
]
def execute_query(self, query):
if query.startswith("SELECT"):
# 로드 밸런싱: 복제본 중 하나를 랜덤하게 선택하여 읽기 수행
# [주의] 실제 구현 시에는 Connection Pool 라이브러리를 사용하고,
# 복제본 장애 시 Failover 로직이 포함되어야 함
db = random.choice(self.replica_pool)
return db.execute(query)
else:
# 쓰기 작업은 반드시 기본 DB에서 수행
return self.primary_db.execute(query)
클라우드 서비스별 구현 사례
- AWS RDS: 'Read Replica' 기능을 통해 클릭 몇 번으로 복제본 생성이 가능하며, 여러 복제본을 묶어주는 'Reader Endpoint'를 제공하여 애플리케이션의 설정을 단순화합니다.
- Google Cloud SQL: 읽기 복제본을 생성하여 읽기 전용 트래픽을 분산하며, 필요에 따라 복제본을 기본 인스턴스로 승격시킬 수 있는 기능을 제공합니다.
- Azure Database for MySQL: 읽기 복제본을 통해 읽기 성능을 확장하고, 지리적 복제(Geo-replication)를 통해 지역 간 재해 복구 체계를 구축할 수 있습니다.
주의 사항 및 한계점
복제 지연 (Replication Lag)
비동기 복제 방식에서는 기본 DB에 데이터가 기록된 후 복제본에 반영되기까지 짧은 시간 차이가 발생합니다. 이를 복제 지연(Replication Lag)이라고 하며, 사용자가 데이터를 수정하자마자 조회했을 때 수정 전 데이터가 보이는 '데이터 불일치' 현상이 발생할 수 있습니다.
복제 지연 해결 기법
- 강제 읽기 경로 지정 (Critical Read): 결제 완료 직후의 주문 내역 조회와 같이 최신 데이터가 반드시 필요한 경우, 복제본이 아닌 기본 DB에서 직접 읽도록 경로를 지정합니다.
- 세션 기반 일관성 (Session Consistency): 특정 사용자가 쓴 데이터는 해당 사용자의 세션 동안 일정 시간 동안 기본 DB에서 읽도록 처리합니다.
- 버전 체크 및 대기: 데이터에 버전 번호를 부여하고, 복제본의 버전이 기본 DB의 버전과 일치할 때까지 짧은 시간 대기 후 응답합니다.
- 캐시 레이어 활용 (Caching Strategy): Redis와 같은 인메모리 DB를 기본 DB와 복제본 사이에 배치합니다. 쓰기 작업 시 기본 DB와 캐시를 동시에 업데이트하고, 읽기 작업 시 캐시를 먼저 확인하게 함으로써 복제본에 데이터가 반영되기 전까지의 공백을 메워 최신 데이터를 즉시 제공합니다.
장애 발생 시 복구 및 승격 절차
기본 DB에 장애가 발생하면 서비스의 쓰기 기능이 중단됩니다. 이때 읽기 전용 복제본 중 하나를 기본 DB로 전환하는 승격(Promotion) 절차가 필요합니다.
- 장애 감지: 모니터링 시스템을 통해 기본 DB의 헬스 체크 실패를 감지합니다.
- 복제본 선정: 복제 지연이 가장 적은(가장 최신 데이터를 가진) 복제본을 새로운 기본 DB 후보로 선정합니다.
- 승격 수행: 선정된 복제본의 '읽기 전용' 설정을 해제하고 쓰기가 가능한 상태로 변경합니다.
- 엔드포인트 업데이트: 애플리케이션의 Write Endpoint 설정을 새로운 기본 DB 주소로 변경하거나, DNS/로드 밸런서를 통해 트래픽 방향을 전환합니다.
- 나머지 복제본 재설정: 기존의 다른 복제본들이 새로운 기본 DB를 바라보도록 복제 경로를 재설정합니다.
요약 및 비교
읽기 전용 복제본은 읽기 성능 향상에 특화된 솔루션이며, 데이터 규모 자체가 커져 쓰기 성능까지 개선해야 할 때는 샤딩과 같은 다른 전략을 고려해야 합니다.
| 비교 항목 | 읽기 전용 복제본 (Read Replica) | 샤딩 (Sharding) | 멀티 마스터 (Multi-Master) |
|---|---|---|---|
| 핵심 목적 | 읽기 부하 분산 | 데이터 분산 저장 및 쓰기 확장 | 쓰기 가용성 및 성능 향상 |
| 데이터 구조 | 모든 서버가 동일한 전체 데이터 보유 | 데이터를 쪼개어 서로 다른 서버에 저장 | 모든 서버가 쓰기 가능하며 데이터 동기화 |
| 쓰기 성능 | 개선되지 않음 (기본 DB 1대 처리) | 획기적으로 개선 (여러 서버 분산 쓰기) | 개선됨 (여러 서버에서 쓰기 가능) |
| 복잡도 | 낮음 | 매우 높음 (데이터 분할 로직 필요) | 높음 (충돌 해결 메커니즘 필요) |
| 적합한 사례 | 읽기 요청이 압도적으로 많은 서비스 | 데이터 양이 너무 많아 단일 서버 불가 시 | 무중단 쓰기 서비스, 글로벌 서비스 |
이 문서는 AI 모델(gemma-4-31b)에 의해 생성된 콘텐츠입니다.
주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.