본문 바로가기
database

📘 읽기전용 DB분산구조는 왜필요한가(Read Replication) – Primary / Replica 구조 | 읽기 부하 분산 | 실시간 복제 이슈

by 죄니안죄니 2025. 4. 7.

✅ 왜 읽기 전용 분산 구조가 필요한가? Replica - "읽기 성능"을 위한 분산 구조

서비스 트래픽이 늘어나면 DB에 큰 부하가 발생합니다. 그런데, 예시로 쇼핑몰 서비스에서 트래픽의 90% 이상은 상품 조회, 카테고리, 리뷰 보기 와 같은 SELECT 요청이고, 그 나머지가 장바구니 담기, 결제, 후기 작성 등의 INSERT / UPDATE 요청입니다. 트래픽이 증가하게 되면 한 개의 DB서버에서는  모든 SELECT가 Primary DB로 몰리게 되고, 결제, 재고 감소 등의 요청은 밀려서 지연이 되게 됩니다. 그러면 DB CPU 100%, 슬로우 쿼리 증가, 전체 서비스가 느려지고 "상품은 잘 보이는데 결제가 안 돼요" 같은 문제 발생하게 됩니다.

이처럼 **SELECT 요청(읽기)**이 많을 경우, 이를 분산시켜야 성능과 안정성을 확보할 수 있습니다. Read Replica는 SELECT(읽기) 요청을 다른 서버로 분산하기 위한 목적에서 등장한 구조입니다. 

Primary-Replica(Write-Read) DB Replication 구조에서 Replica는 SELECT만 받고, Primary는 쓰기를 담당해서 여유있는 트랜잭션 처리가 가능하고 전체 서비스 응답속도도 향상되게 됩니다. 

🔧 실무에서 쓰이는 상황 예시

상황 이유
앱에서 게시글, 상품 목록, 댓글 등 대량 조회 Replica에 분산
관리자 리포트/통계 시스템 오래 걸리는 SELECT는 Replica 전용
Primary 과부하 이슈 SELECT 트래픽만 따로 분산
결제/회원 정보는 최신 데이터 필요 이때는 Primary만 사용 (정합성 중요)

📌 참고로, 레플리카와 서버이중화는 전혀 다른 개념입니다. 레플리카는 성능 향상 (읽기 부하 분산), 서버 이중화 (HA)는 장애 대비 (서버가 죽어도 자동 전환) 를 목적으로 합니다.

레플리카는 1인 주방장 주문 다받는 음식점에서 홀만 담당하는 직원을 하나 더 채용해서 속도를 빠르게 한 것이라면

서버이중화는 주방장을 복제해서 한 명이 일을 못할 때 다른 사람이 대체할 수있도록 끊임없는 서비스 가능하게 하는 것입니다. 전혀 다른 목적이고 실제 동작도 다릅니다. 

"레플리카가 Primary 죽으면 자동 전환돼요?" → ❌ 안 됨

"이중화 구성이면 SELECT도 나눠서 쓰나요?" → ❌ 주 목적이 다름


🏗️ Primary / Replica 구조란?

📌 구조 개요

역할 설명
Primary (Master) 모든 쓰기(INSERT/UPDATE/DELETE) 작업 담당
Replica (Slave) 읽기(SELECT) 전용 DB, 실시간으로 Primary의 데이터를 복제받음 (약간의 지연 존재)
데이터 복제 Primary의 binlog → Replica가 비동기/반동기 방식으로 받아서 반영

📊 아키텍처 예시

애플리케이션
   ├── 쓰기 요청 → Primary DB
   └── 읽기 요청 → Replica DB1, DB2 ...
Read Replica는 "DB의 읽기 트래픽을 분산시켜서, 성능 향상과 장애 예방을 동시에 노리는 구조"입니다.

✅ 읽기 부하 분산 전략

📌 방법 1: DB Driver 수준에서 라우팅

  • 예: MySQL Connector/J 8.x 이상 → replicationDriver 지원
jdbc:mysql:replication://primary-db,replica-db1,replica-db2/mydb
  • 자동으로 쓰기는 Primary로, 읽기는 Replica로 분산

📌 방법 2: 애플리케이션 코드에서 라우팅 (Spring 기준)

  • AbstractRoutingDataSource 활용하여 쿼리 유형에 따라 DataSource 분기
public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {
    protected Object determineCurrentLookupKey() {
        return TransactionSynchronizationManager.isCurrentTransactionReadOnly()
            ? "read"
            : "write";
    }
}
  • @Transactional(readOnly = true) → Replica
  • 일반 트랜잭션 → Primary

🧨 실시간 복제의 이슈들

⚠️ 1. Replication Lag (지연)

  • 복제 지연으로 인해 쓰기 직후 읽으면 최신 데이터가 안 보일 수 있음
  • 예: 사용자가 회원가입을 했는데, 직후 프로필을 조회하니 없다고 뜨는 상황

해결 전략:

  • 쓰기 직후 특정 요청은 강제로 Primary 사용
  • 세션/캐시 기반의 Read-After-Write 처리

⚠️ 2. 일관성 보장 문제

  • 트랜잭션 내에서 SELECT → Replica 사용 시,
  • 동시에 다른 트랜잭션이 INSERT한 값이 보이지 않을 수 있음

해결 전략:

  • 트랜잭션 내에서는 무조건 Primary 사용
  • @Transactional(readOnly = true) → 트랜잭션 외부에서만 적용 권장

🔍 read-replica 사용할 때의 주의점

 

항목 설명
❗ 트랜잭션 내 SELECT Primary만 사용해야 정합성 유지
❗ JOIN, Aggregation Replica 성능 낮으면 병목 발생 가능
❗ 읽기 결과 캐싱 지연된 데이터 반영 시 주의 필요
✅ 백오피스 / 보고서 / 통계 Replica 사용 적합한 케이스

🧠 실무 운영 팁

  • Replica는 반드시 모니터링해야 함 (지연 초 단위, replication 상태)
  • MySQL: SHOW SLAVE STATUS\\G / SHOW REPLICA STATUS
  • Prometheus + Grafana → Seconds_Behind_Master 지표 모니터링
  • Primary 장애 발생 대비 → 자동 Failover 구성 고려 (MHA, Orchestrator 등)

✅ 마무리 정리

 

구성 요소 역할
Primary 쓰기 전용, 단일 노드 중심
Replica 읽기 전용, 다수 구성 가능
라우팅 방식 JDBC Driver or Application 코드
주의할 점 지연, 정합성, 트랜잭션 내 SELECT 처리

👉 다음 글에서는 WAS 레벨에서의 사용자 세션 처리 방식을 정리합니다.

댓글