컴퓨터 구조 운영체제 네트워크 자료구조 알고리즘 데이터베이스 내 학습
db · 08

복제

DB복제가용성

DB가 한 대뿐이면 장애가 났을 때 서비스가 완전히 멈추고, 읽기 트래픽이 몰리면 그 한 대가 병목이 됩니다. 복제(Replication)는 Primary의 데이터를 하나 이상의 Replica에 계속 복사해 두는 것으로, Replica는 장애 대비책이자 읽기 부하를 나눠 받는 창구가 됩니다.

핵심 개념

Primary-Replica 구조

Primary가 모든 쓰기를 전담하고, Replica는 Primary의 변경사항을 복사해 읽기 전용으로 서비스합니다.

핵심 개념

동기 복제 (Synchronous)

Primary가 Replica의 쓰기 확인(ack)까지 기다린 뒤에야 클라이언트에 커밋을 알립니다. 유실 위험은 낮지만 느립니다.

핵심 개념

비동기 복제 (Asynchronous)

Primary가 Replica의 확인을 기다리지 않고 바로 커밋을 알립니다. 빠르지만 장애 시 최근 데이터가 유실될 수 있습니다.

핵심 개념

복제 지연 (Replication Lag)

Replica가 Primary를 따라잡는 데 걸리는 시간차입니다. 비동기 복제에서 특히 커질 수 있습니다.

핵심 개념

읽기 확장 (Read Scaling)

읽기 쿼리를 여러 Replica로 분산시켜 Primary의 부하를 줄이는 전략입니다.

핵심 개념

장애 조치 (Failover)

Primary에 장애가 발생하면 Replica 중 하나를 새 Primary로 승격시켜 서비스를 이어갑니다.

동기 vs 비동기, 결국 트레이드오프 — 동기는 일관성을 얻는 대신 지연시간을 감수하고, 비동기는 빠른 대신 유실 위험을 감수합니다. 둘 다 "정답"은 아니고 서비스 요구사항에 달려 있습니다.
복제 지연이 문제되는 순간 — "방금 쓴 데이터를 바로 조회했는데 안 보인다"는 현상은 Replica가 아직 Primary를 따라잡지 못했을 때 생길 수 있습니다(read-your-writes 문제).
Failover의 숨은 비용 — 비동기 복제 중이던 Replica를 새 Primary로 승격하면, 아직 복제되지 않았던 최근 쓰기 몇 건은 유실될 수 있습니다.
이 시각화에서 확인할 것 — "동기 복제"와 "비동기 복제" 탭을 오가며 CLIENT가 커밋 완료 응답을 받는 타이밍이 REPLICA의 ack 여부에 따라 어떻게 달라지는지 STEP으로 확인하세요.

복제는 어떻게 이루어지나요

많은 관계형 DB는 Primary에서 일어난 변경을 로그로 기록하고, Replica가 그 로그를 받아 같은 순서로 적용하는 방식으로 복제합니다. PostgreSQL은 WAL(Write-Ahead Log)을 스트리밍으로 전달하고, MySQL은 바이너리 로그를 씁니다. 복제 모델은 쓰기를 받는 곳이 하나인 단일 리더, 여러 곳인 다중 리더, 정해진 리더가 없는 리더리스로 나눕니다. 이 토픽은 단일 리더(Primary-Replica)를 다룹니다.

자주 하는 오해

복제를 해 두었으니 백업은 필요 없다 — 복제는 변경을 그대로 따라 하므로 실수로 지운 데이터나 잘못된 UPDATE도 Replica에 똑같이 반영됩니다. 복제는 장애 대비이고 백업은 과거 시점으로 되돌리는 별도의 수단입니다.
Replica를 늘리면 쓰기도 빨라진다 — 단일 리더 구성에서는 쓰기를 모두 Primary가 받으므로 Replica는 읽기 부하만 나눕니다. 쓰기를 늘리려면 샤딩 같은 다른 방법이 필요합니다.
동기 복제면 아무 문제가 없다 — PostgreSQL처럼 동기 복제를 쓰는 DB에서는 동기 Replica가 응답하지 않으면 Primary의 커밋이 기다리게 되어 쓰기가 멈출 수 있습니다(읽기는 영향이 없습니다). 가용성과 유실 방지 사이의 선택입니다.
비동기 Replica도 항상 최신이다 — 복제 지연 때문에 Replica는 Primary보다 과거의 상태일 수 있습니다. 방금 쓴 값을 Replica에서 바로 읽으면 안 보일 수 있습니다.
자동 장애 조치는 켜 두면 안전하다 — 네트워크 문제로 Primary가 죽었다고 오판하면 Primary가 두 개가 되는 스플릿 브레인(split-brain)이 생길 수 있어, 장애 판단 방식을 신중히 설계해야 합니다.

면접에서 이렇게 물어요

면접 질문

복제 지연이 있을 때 사용자가 방금 쓴 글이 안 보이는 문제는 어떻게 해결하나요?

자신이 쓴 데이터를 읽을 때만 Primary에서 읽게 하거나(read-your-writes), 쓴 직후 일정 시간은 Primary로 읽기를 보냅니다. 같은 사용자의 읽기를 항상 같은 Replica로 보내면 시간이 거꾸로 흐르는 것처럼 보이는 문제도 줄일 수 있습니다.

면접 질문

동기, 비동기, 반동기 복제를 비교해 보세요.

동기는 Replica 확인까지 기다려 유실 위험이 낮지만 느리고, 비동기는 기다리지 않아 빠르지만 장애 시 최근 데이터를 잃을 수 있습니다. 반동기는 Replica 한 곳이 받았다는 확인만 기다리는 절충안으로 일부 DB가 지원합니다.

면접 질문

스플릿 브레인이란 무엇인가요?

네트워크 단절 등으로 둘 이상의 노드가 자신이 Primary라고 믿어 서로 다른 쓰기를 받는 상황입니다. 데이터가 갈라지므로, 과반수 투표나 이전 Primary 격리(펜싱) 같은 방법으로 막습니다.

면접 질문

복제와 샤딩은 무엇이 다른가요?

복제는 같은 데이터를 여러 서버에 복사해 읽기 확장과 장애 대비를 얻고, 샤딩은 데이터를 나눠서 서버마다 일부만 저장해 용량과 쓰기를 확장합니다. 두 가지를 함께 쓰는 구성도 있습니다.

실제 시스템에서는

PostgreSQL — 스트리밍 복제를 지원하고, 동기 복제는 synchronous_standby_names로 지정합니다. Primary에서 pg_stat_replication 뷰로 Replica의 상태와 복제 지연을 볼 수 있습니다.
MongoDB — 데이터가 복제된 노드 묶음을 레플리카 셋이라 하고, Primary가 장애가 나면 나머지 노드 중 선거 자격이 있는 노드가 선거를 열고, 과반수 표를 얻은 노드가 새 Primary가 됩니다.
Spring — AbstractRoutingDataSource로 쓰기용과 읽기용 DataSource 중 하나를 고르도록 구성하고, LazyConnectionDataSourceProxy를 함께 쓰면 읽기 전용 트랜잭션 여부를 기준으로 읽기용(예: Replica) DataSource로 보내는 구성도 만들 수 있습니다.

함께 보면 좋은 토픽

샤딩 — 데이터를 나눠 쓰기까지 확장하는 방법을 봅니다.
트랜잭션 — 커밋이 언제 확정되는지와 복제 확인의 관계를 봅니다.
NoSQL — 최종 일관성을 택하는 저장소가 많은 NoSQL을 봅니다.
RDBMS 기초 — 복제의 대상이 되는 관계형 DB의 기본을 봅니다.

참고 자료

MongoDB 복제 — Replication — MongoDB Docs
교재 — Kleppmann, "Designing Data-Intensive Applications" (O'Reilly, 1판) 5장 "Replication"

PostgreSQL — 스트리밍 복제 기본 설정

-- Primary: postgresql.conf
wal_level = replica
max_wal_senders = 10
synchronous_commit = on       -- 동기 복제 시 'remote_write' 이상으로 설정
synchronous_standby_names = ''   -- 동기 복제 사용 시 Replica 이름 지정

-- Replica: standby.signal 파일 생성 + primary_conninfo 설정
-- pg_basebackup으로 Primary 데이터를 복제한 뒤 Replica로 기동

synchronous_standby_names가 비어 있으면 비동기 복제입니다. 여기에 Replica 이름을 지정하면 Primary가 그 Replica의 ack을 기다리는 동기 복제로 바뀝니다 — 이 시각화의 두 모드 차이가 실제로는 설정 한 줄 차이입니다.

SQL — 복제 지연(Replication Lag) 확인하기

-- Primary에서 실행: 각 Replica가 얼마나 뒤처져 있는지 확인
SELECT client_addr, state,
       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes
FROM pg_stat_replication;

-- lag_bytes가 계속 커진다면 Replica가 Primary를 못 따라가고 있다는 신호

pg_stat_replication은 PostgreSQL이 각 Replica의 복제 상태를 보여주는 시스템 뷰입니다. 이 시각화의 "뒤늦게 기록" 스텝이 실제 운영 환경에서는 이 lag_bytes 값으로 관측됩니다.

Java — 읽기 쿼리를 Replica로 분산하기(개념)

// 쓰기는 Primary, 읽기는 Replica로 — 데이터소스 라우팅 개념 예시
public class ReplicaRoutingDataSource extends AbstractRoutingDataSource {
    protected Object determineCurrentLookupKey() {
        return TransactionSynchronizationManager.isCurrentTransactionReadOnly()
                ? "replica" : "primary";
    }
}

// Service 쪽에서는 @Transactional(readOnly = true) 여부만 신경 쓰면 됨
@Transactional(readOnly = true)
public List<Topic> getPublishedTopicsPageable(...) { ... }  // → replica로 라우팅

readOnly = true인 트랜잭션은 Replica로, 그 외에는 Primary로 보내는 방식입니다. 다만 방금 쓴 데이터를 바로 읽어야 하는 화면(예: 글쓰기 직후 상세 페이지)은 복제 지연 때문에 안 보일 수 있어 Primary로 강제 라우팅하는 예외 처리가 흔히 필요합니다.

Q1

동기 복제(Synchronous Replication)의 특징은?

일관성을 우선하는 대신 지연시간을 감수합니다.
동기 복제는 Primary가 Replica의 쓰기 확인(ack)까지 기다린 후에야 클라이언트에 커밋 완료를 알립니다. 유실 위험은 낮지만 그만큼 응답이 느려집니다.

Q2

비동기 복제에서 Failover 시 데이터가 유실될 수 있는 이유는?

복제 지연(Replication Lag) 동안의 데이터를 떠올려보세요.
비동기 복제는 Primary가 Replica를 기다리지 않고 바로 커밋을 알리므로, 그 사이 Primary에 장애가 나 Replica가 승격되면 아직 복제되지 않았던 최근 쓰기가 유실될 수 있습니다.

Q3

복제 지연(Replication Lag)이 실무에서 문제가 되는 대표적인 상황은?

read-your-writes 문제라고도 부릅니다.
Replica가 아직 Primary의 최신 변경사항을 따라잡지 못한 상태에서 읽기 쿼리가 Replica로 가면, 방금 쓴 데이터가 안 보이는 현상이 발생합니다.