MySQL Replication 복제 운영

MySQL Replication이란?

MySQL Replication은 하나의 소스(Master) 서버에서 발생한 데이터 변경을 하나 이상의 레플리카(Slave) 서버로 자동 복제하는 기능이다. 읽기 부하 분산, 고가용성, 백업 분리, 지리적 분산을 위해 프로덕션 환경에서 거의 필수적으로 사용한다. MySQL 8.0부터 Master/Slave 용어 대신 Source/Replica를 공식 사용한다.

Replication 방식 비교

방식 동작 원리 장점 단점
비동기(Async) Source가 binlog 기록 후 즉시 커밋 가장 빠름, 지연 없음 데이터 유실 가능
반동기(Semi-sync) Replica 1대 이상 수신 확인 후 커밋 유실 최소화 네트워크 지연 영향
그룹(Group) Paxos 합의 기반 멀티 소스 자동 페일오버, 멀티 마스터 설정 복잡, 오버헤드
GTID 기반 글로벌 트랜잭션 ID로 위치 추적 페일오버 간편 제약사항 존재

Binlog 기반 비동기 Replication 설정

Source 서버 설정

# my.cnf (Source)
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-format = ROW           # ROW가 가장 안전
binlog-row-image = FULL
sync-binlog = 1               # 크래시 안전
innodb-flush-log-at-trx-commit = 1
expire-logs-days = 7
max-binlog-size = 500M

# 복제 전용 계정 생성
CREATE USER 'repl_user'@'10.0.0.%' IDENTIFIED BY 'strong_password';
GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'10.0.0.%';
FLUSH PRIVILEGES;

Replica 서버 설정

# my.cnf (Replica)
[mysqld]
server-id = 2
relay-log = relay-bin
read-only = ON
super-read-only = ON          # SUPER 권한도 쓰기 차단
log-slave-updates = ON        # 체이닝 복제 시 필요
skip-slave-start = ON         # 수동 시작

# Source 연결 설정
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST = '10.0.0.100',
  SOURCE_USER = 'repl_user',
  SOURCE_PASSWORD = 'strong_password',
  SOURCE_LOG_FILE = 'mysql-bin.000001',
  SOURCE_LOG_POS = 154;

START REPLICA;

GTID Replication

GTID(Global Transaction ID)를 사용하면 binlog 파일명과 포지션 대신 고유 트랜잭션 ID로 복제 위치를 추적한다. 페일오버 시 새 Source를 찾아가는 과정이 훨씬 간단해진다.

# my.cnf (Source + Replica 공통)
[mysqld]
gtid-mode = ON
enforce-gtid-consistency = ON
log-bin = mysql-bin
binlog-format = ROW

# Replica에서 GTID 기반 연결
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST = '10.0.0.100',
  SOURCE_USER = 'repl_user',
  SOURCE_PASSWORD = 'strong_password',
  SOURCE_AUTO_POSITION = 1;   # GTID 자동 위치 추적

START REPLICA;

GTID 제약사항: CREATE TABLE ... SELECT, 비트랜잭션 엔진(MyISAM)과 트랜잭션 엔진(InnoDB)의 혼합 사용이 제한된다. 신규 프로젝트라면 반드시 GTID를 사용하자.

Semi-Synchronous Replication

비동기 복제의 데이터 유실 위험을 줄이려면 반동기 복제를 활성화한다.

# Source
INSTALL PLUGIN rpl_semi_sync_source SONAME 'semisync_source.so';
SET GLOBAL rpl_semi_sync_source_enabled = 1;
SET GLOBAL rpl_semi_sync_source_timeout = 1000;  -- 1초 타임아웃

# Replica
INSTALL PLUGIN rpl_semi_sync_replica SONAME 'semisync_replica.so';
SET GLOBAL rpl_semi_sync_replica_enabled = 1;

# 상태 확인
SHOW STATUS LIKE 'Rpl_semi_sync%';

타임아웃 내에 Replica 확인을 못 받으면 비동기로 자동 전환된다. 이는 가용성을 포기하지 않으면서 내구성을 높이는 절충안이다.

Replication 모니터링

# Replica 상태 확인 (가장 중요한 명령)
SHOW REPLICA STATUSG

# 핵심 체크 포인트
# Replica_IO_Running: Yes       ← binlog 수신 스레드
# Replica_SQL_Running: Yes      ← relay log 적용 스레드
# Seconds_Behind_Source: 0      ← 복제 지연 (0이 정상)
# Last_Error:                   ← 에러 확인

# GTID 실행 상태
SELECT @@gtid_executed;
SELECT @@gtid_purged;

# 성능 스키마로 상세 모니터링
SELECT * FROM performance_schema.replication_connection_status;
SELECT * FROM performance_schema.replication_applier_status_by_worker;

Seconds_Behind_Source가 지속적으로 증가하면 Replica의 InnoDB Buffer Pool이나 디스크 I/O를 점검해야 한다.

복제 지연 해결 전략

원인 해결
단일 SQL 스레드 병목 replica_parallel_workers = 4 + replica_parallel_type = LOGICAL_CLOCK
대량 DDL (ALTER TABLE) pt-online-schema-change 또는 gh-ost 사용
네트워크 대역폭 binlog 압축: binlog_transaction_compression = ON
느린 디스크 I/O Replica SSD 업그레이드, sync_relay_log = 0
# 병렬 복제 설정 (MySQL 8.0.27+)
[mysqld]
replica_parallel_workers = 4
replica_parallel_type = LOGICAL_CLOCK
replica_preserve_commit_order = ON
binlog_transaction_dependency_tracking = WRITESET

애플리케이션 레벨 읽기/쓰기 분리

Replication을 설정했다면 애플리케이션에서 읽기는 Replica로, 쓰기는 Source로 분리해야 실질적인 효과를 얻는다.

// Spring — AbstractRoutingDataSource
public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return TransactionSynchronizationManager.isCurrentTransactionReadOnly()
            ? "replica"
            : "source";
    }
}

// @Transactional(readOnly = true) → Replica
// @Transactional → Source
// NestJS + TypeORM
TypeOrmModule.forRoot({
  type: 'mysql',
  replication: {
    master: { host: '10.0.0.100', port: 3306, username: 'app', password: 'pw', database: 'mydb' },
    slaves: [
      { host: '10.0.0.101', port: 3306, username: 'app', password: 'pw', database: 'mydb' },
      { host: '10.0.0.102', port: 3306, username: 'app', password: 'pw', database: 'mydb' },
    ],
  },
})

주의: 복제 지연으로 인해 쓰기 직후 읽기에서 이전 데이터가 반환될 수 있다. 쓰기 후 즉시 읽어야 하는 경우(예: 주문 생성 후 상세 조회)는 트랜잭션을 readOnly=false로 설정해 Source에서 읽어야 한다.

페일오버 전략

# Replica를 새 Source로 승격 (GTID 기반)
# 1. Source 장애 확인 후 가장 최신 Replica 선택
SHOW REPLICA STATUSG  -- Executed_Gtid_Set 비교

# 2. Replica 복제 중단 및 승격
STOP REPLICA;
RESET REPLICA ALL;
SET GLOBAL read_only = OFF;
SET GLOBAL super_read_only = OFF;

# 3. 다른 Replica들을 새 Source로 재연결
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST = '10.0.0.101',  -- 승격된 Replica
  SOURCE_AUTO_POSITION = 1;
START REPLICA;

수동 페일오버는 복잡하고 실수하기 쉽다. 프로덕션에서는 MySQL InnoDB Cluster + MySQL Router 또는 Orchestrator 같은 자동 페일오버 도구를 사용하자.

정리

MySQL Replication은 읽기 확장과 고가용성의 기본이다. GTID 기반 복제로 시작하고, 데이터 중요도에 따라 Semi-sync를 적용하며, 병렬 복제로 지연을 줄이자. 애플리케이션에서 읽기/쓰기를 분리하지 않으면 Replica를 만들어도 의미가 없다. 인프라 설정과 애플리케이션 코드 변경을 함께 계획해야 한다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux