Theme:

서버 한 대로 모든 트래픽을 감당할 수 없을 때, 또는 서버가 죽었을 때 서비스가 멈추면 안 될 때, 데이터베이스를 어떻게 복제할 수 있을까요?

레플리케이션이란

레플리케이션(Replication)은 소스(Source) 서버의 데이터를 하나 이상의 복제본(Replica) 서버에 복사하는 기능입니다.

PLAINTEXT
소스 서버 (Source)         복제본 서버 (Replica)
┌──────────────┐         ┌──────────────┐
│  쓰기 + 읽기  │ ──────→ │   읽기 전용    │
└──────────────┘  Binlog  └──────────────┘
                  전달          ↑
                          복제본 2, 3...도 가능

레플리케이션의 목적은 다음과 같습니다.

  • 읽기 분산: SELECT 트래픽을 복제본에서 처리하여 소스의 부하를 줄입니다
  • 고가용성: 소스 장애 시 복제본을 새로운 소스로 승격(Failover)합니다
  • 백업: 복제본에서 백업을 수행하여 소스에 영향을 주지 않습니다
  • 지역 분산: 사용자와 가까운 지역에 복제본을 배치합니다

복제의 내부 동작

세 개의 스레드

MySQL 복제는 세 개의 스레드로 동작합니다.

PLAINTEXT
소스 서버                    복제본 서버
┌─────────────┐            ┌─────────────────────┐
│             │            │  I/O Thread          │
│ Binlog Dump │ ─Binlog──→ │  (Binlog 수신)        │
│ Thread      │  이벤트     │       │              │
│             │            │       ▼              │
│             │            │  Relay Log           │
│             │            │       │              │
│             │            │       ▼              │
│             │            │  SQL Thread          │
│             │            │  (이벤트 적용)         │
└─────────────┘            └─────────────────────┘
  1. Binlog Dump Thread (소스): 복제본이 연결되면 생성되어 Binlog 이벤트를 전송합니다
  2. I/O Thread (복제본): 소스에서 Binlog 이벤트를 수신하여 relay log에 기록합니다
  3. SQL Thread (복제본): relay log의 이벤트를 읽어 실제 데이터에 적용합니다

복제 과정 상세

PLAINTEXT
1. 소스에서 트랜잭션 커밋 → Binlog에 기록
2. 복제본의 I/O Thread가 소스에 연결
3. Binlog Dump Thread가 이벤트를 전송
4. I/O Thread가 수신한 이벤트를 relay log에 기록
5. SQL Thread가 relay log를 읽어 순서대로 적용
6. 적용 완료된 포지션/GTID를 기록

비동기 복제 (Asynchronous)

MySQL의 기본 복제 방식입니다.

PLAINTEXT
소스: COMMIT → Binlog 기록 → 클라이언트에 성공 반환 (여기서 끝)

              (비동기로) 복제본에 전달 → 복제본 적용
  • 소스는 복제본의 응답을 기다리지 않습니다
  • 소스 장애 시 전달되지 않은 이벤트가 유실될 수 있습니다
  • 성능 영향이 없습니다

반동기 복제 (Semi-synchronous)

소스가 최소 하나의 복제본으로부터 "수신 완료" 응답을 받은 후에 커밋을 완료합니다.

PLAINTEXT
소스: COMMIT → Binlog 기록 → 복제본에 전달 → ACK 대기

복제본: 이벤트 수신 → relay log 기록 → ACK 전송 (적용은 아직 안 함)

소스: ACK 수신 → 클라이언트에 성공 반환
SQL
-- 소스에서 반동기 복제 플러그인 설치
INSTALL PLUGIN rpl_semi_sync_source SONAME 'semisync_source.so';
SET GLOBAL rpl_semi_sync_source_enabled = ON;

-- 복제본에서
INSTALL PLUGIN rpl_semi_sync_replica SONAME 'semisync_replica.so';
SET GLOBAL rpl_semi_sync_replica_enabled = ON;

-- 타임아웃 설정 (ACK 대기 시간, 기본 10초)
SET GLOBAL rpl_semi_sync_source_timeout = 10000;

ACK 대기 시간이 초과되면 자동으로 비동기 모드로 전환됩니다.

주의: "수신 완료"이지 "적용 완료"가 아닙니다. 복제본이 relay log에 기록했다는 응답일 뿐, 실제 적용은 아직 진행 중일 수 있습니다.

그룹 복제 (Group Replication)

MySQL 5.7.17에서 도입된 고급 복제 방식입니다.

PLAINTEXT
┌──────┐  ┌──────┐  ┌──────┐
│ 멤버1 │──│ 멤버2 │──│ 멤버3 │
│(R/W)  │  │(R/O)  │  │(R/O)  │
└──────┘  └──────┘  └──────┘
    ↕          ↕          ↕
    합의 프로토콜 (Paxos 기반)

특징:

  • 다중 소스 가능: 멀티프라이머리 모드에서 모든 멤버가 쓰기 가능
  • 자동 멤버십 관리: 멤버 추가/제거 자동 처리
  • 충돌 감지: 동시 쓰기 시 충돌을 감지하고 하나를 롤백
  • 자동 페일오버: 프라이머리 장애 시 자동으로 새 프라이머리 선출
SQL
-- 그룹 복제 설정 (각 멤버)
SET GLOBAL group_replication_group_name = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
SET GLOBAL group_replication_local_address = "10.0.1.1:33061";
SET GLOBAL group_replication_group_seeds = "10.0.1.1:33061,10.0.1.2:33061,10.0.1.3:33061";

-- 그룹 시작 (첫 번째 멤버)
SET GLOBAL group_replication_bootstrap_group = ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group = OFF;

-- 다른 멤버 추가
START GROUP_REPLICATION;

복제 지연 (Replication Lag)

복제본이 소스의 변경을 아직 다 적용하지 못한 상태입니다.

지연 확인

SQL
-- 복제본에서 실행
SHOW REPLICA STATUS\G
-- Seconds_Behind_Source: 복제본이 소스보다 뒤처진 초 수
-- 0이면 동기화 완료, NULL이면 복제 중단

지연 원인

  1. 대량 DML: 소스에서 100만 행을 한 번에 UPDATE하면 복제본도 100만 행을 적용해야 합니다
  2. 단일 스레드 적용: 기본적으로 SQL Thread가 하나이므로 소스의 병렬 쓰기를 순차 처리합니다
  3. 복제본 하드웨어 부족: 소스보다 성능이 낮은 하드웨어를 사용하면 처리 속도가 따라가지 못합니다
  4. 네트워크 지연: 소스와 복제본 사이의 네트워크 대역폭이 부족합니다

지연 해결

멀티스레드 복제 (MTA)

SQL
-- 복제본에서 병렬 적용 활성화
SET GLOBAL replica_parallel_workers = 8;           -- 병렬 워커 수
SET GLOBAL replica_parallel_type = 'LOGICAL_CLOCK'; -- 소스의 병렬성 재현
SET GLOBAL replica_preserve_commit_order = ON;      -- 커밋 순서 보장

MySQL 8.0에서는 LOGICAL_CLOCK 방식으로 소스에서 동시에 커밋된 트랜잭션을 복제본에서도 병렬로 적용합니다.

대량 DML 분할

SQL
-- 나쁜 예: 한 번에 100만 행 업데이트
UPDATE orders SET status = 'archived' WHERE created_at < '2024-01-01';

-- 좋은 예: 배치로 나눠서 실행
REPEAT
    UPDATE orders SET status = 'archived'
    WHERE created_at < '2024-01-01' AND status != 'archived'
    LIMIT 10000;
UNTIL ROW_COUNT() = 0 END REPEAT;

읽기 분산 구성

애플리케이션 레벨

JAVA
// 쓰기는 소스, 읽기는 복제본으로 라우팅
@Transactional(readOnly = false)
public void createOrder(Order order) {
    // 소스 서버에 연결
    orderRepository.save(order);
}

@Transactional(readOnly = true)
public List<Order> findOrders() {
    // 복제본 서버에 연결
    return orderRepository.findAll();
}

프록시 레벨

ProxySQL이나 MySQL Router를 사용하여 투명하게 라우팅합니다.

PLAINTEXT
클라이언트 → ProxySQL → 쓰기: 소스
                      → 읽기: 복제본 (라운드 로빈)

복제 지연 고려

읽기 분산 시 복제 지연으로 인한 문제를 고려해야 합니다.

PLAINTEXT
1. 주문 생성 (소스에 쓰기)
2. 주문 조회 (복제본에서 읽기) → 아직 반영 안 됨!

해결 방법:

  • 쓰기 직후 읽기는 소스에서 수행
  • 반동기 복제 사용
  • Seconds_Behind_Source를 모니터링하여 지연이 큰 복제본은 라우팅에서 제외

복제 모니터링

SQL
-- 복제 상태 전체 확인
SHOW REPLICA STATUS\G

-- 핵심 지표
-- Replica_IO_Running: I/O Thread 상태 (Yes/No)
-- Replica_SQL_Running: SQL Thread 상태 (Yes/No)
-- Seconds_Behind_Source: 지연 시간
-- Last_IO_Error: I/O Thread 에러
-- Last_SQL_Error: SQL Thread 에러
SQL
-- Performance Schema로 상세 모니터링
SELECT * FROM performance_schema.replication_connection_status\G
SELECT * FROM performance_schema.replication_applier_status_by_worker\G

정리

  • MySQL 복제는 Binlog를 기반으로 소스의 변경을 복제본에 전달합니다
  • 비동기 복제는 성능 영향 없이 동작하지만, 소스 장애 시 데이터 유실 가능성이 있습니다
  • 반동기 복제는 최소 한 복제본의 수신 확인을 요구하여 안전성을 높입니다
  • 멀티스레드 복제를 활성화하면 복제 지연을 크게 줄일 수 있습니다
  • 읽기 분산 시 복제 지연으로 인한 일관성 문제를 반드시 고려해야 합니다
댓글 로딩 중...