MySQL Multi-Source Replication 오류 — error 13122 근본 원인과 재현
조사 배경
10대 서버를 하나로 통합하는 작업에서 multi-source 리플리케이션을 구성함. 각 소스는 서로 다른 논리 데이터베이스를 보유하고 있어 데이터 중복은 없었음. 소스 서버들은 관리형 MySQL(Managed MySQL)이었음.
12대 통합까지는 문제 없었으나 34대째부터 갑자기 복제 중단 오류(error 13122) 발생. replica_parallel_workers = 0으로 변경 후 오류가 해소됨.
이후 조사에서 실제 원인은 semi-sync + 멀티소스 조합으로 확인됨. 인프라 기본값으로 소스 전체에 rpl_semi_sync_source_enabled = 1이 설정되어 있었고, 레플리카도 rpl_semi_sync_replica_enabled = 1이었음. semi-sync는 멀티소스 환경에서 공식 미지원 구성으로, 채널 추가 조작 시 기존 채널의 I/O 스레드를 끊는 문제가 있음(Bug #102994, Bug #80122).
이 글은 error 13122의 근본 원인 분석, Docker 환경 재현 결과, replica_parallel_workers 0 vs 1 아키텍처 차이를 기록함.
실제 에러 증상
SHOW SLAVE STATUS 결과에서 채널 game1~game4 모두 동일 시각에 중단됨:
Slave_IO_Running: No
Slave_SQL_Running: Yes
Last_IO_Errno: 13122
Last_IO_Error: Relay log write failure: could not queue event from source
Master_Server_Id: 1 ← 전 채널 동일
Master_UUID: (채널마다 상이) ← 각기 다른 서버임을 확인 가능
Channel_Name: game1 / game2 / game3 / game4
Last_IO_Error_Timestamp: (4개 채널 동일 시각)- SQL 스레드는 정상(
Yes) — 데이터 적용 문제가 아님 - I/O 스레드만 중단 — 소스로부터 이벤트를 릴레이 로그에 기록하는 단계에서 실패
- 에러 메시지
Relay log write failure: could not queue event from source는 릴레이 로그 쓰기 실패만을 나타냄 - 에러 메시지만으로는 원인 특정이 어려움 — 디스크 공간 부족, 네트워크 문제, 파일 권한 등을 먼저 의심하게 되는 메시지
근본 원인: semi-sync + 멀티소스 (공식 미지원)
실제 이슈 환경(replica_parallel_workers = 1, ANONYMOUS 트랜잭션)과 직접 일치하는 버그가 공식 트래커에 존재함.
Bug #102994 — MySQL Verified → Feature Request
버그 리포트의 재현 조건이 실제 이슈와 정확히 일치함.
멀티소스 구성(채널 c3444, c3445)에서 semi-sync 활성화 상태로 STOP SLAVE FOR CHANNEL 'c3444'를 실행하면 다른 채널(c3445)의 I/O 스레드가 즉시 실패:
Relay log write failure: could not queue event from master이것이 에러 13122의 실제 발생 경로임. Docker 재현 테스트(테스트 2)에서 STOP/START REPLICA FOR CHANNEL 조작이 기존 채널의 I/O 스레드를 즉시 중단시키는 것을 확인함.
MySQL 8.4 공식 문서에 명시:
“There must not be multiple replication channels configured. Semisynchronous replication is only compatible with the default replication channel.”
MySQL 개발팀(Sven Sandberg)은 “지원하지 않는 구성”으로 공식 확인하고 Feature Request로 분류함 (2022년 6월). 8.4 기준으로도 여전히 미지원 상태.
실제 이슈 환경:
- 레플리카 my.cnf:
rpl_semi_sync_replica_enabled = 1 - 소스 서버들: 인프라 기본값으로
rpl_semi_sync_source_enabled = 1(전 서버 공통)
Bug #80122 에 따르면 MSR 환경에서 소스 하나라도 semi-sync가 활성화되면 전체 채널에 semi-sync가 강제 적용됨. 소스 10대 전부 semi-sync ON이므로 모든 채널이 semi-sync 상태였음.
3→4대 채널 추가 시 START REPLICA FOR CHANNEL 조작이 기존 채널의 I/O 스레드 실패(에러 13122)를 유발함 — Docker 재현 테스트(테스트 2)에서 확인.
우회법
| 방법 | 내용 | 비고 |
|---|---|---|
rpl_semi_sync_replica_enabled = 0 | 근본 원인 제거 | 멀티소스에서 semi-sync는 공식 미지원 구성. 비활성화가 올바른 해결책. 관리형 MySQL이면 가능 여부 확인 필요 |
replica_parallel_workers = 0 | MTS 코드 경로 완전 우회 | Docker 재현 테스트에서 일부 채널이 여전히 에러 13122 발생. 부분적 완화 효과는 있으나 완전한 해결책이 아님 |
도커 환경 재현 테스트
소스 코드 분석만으로는 인과관계를 확정할 수 없어 Docker로 동일 조건을 구성해 테스트함.
테스트 환경: MySQL 8.0.44, source × N, replica, tmpfs
| 테스트 | workers | semi-sync | 채널 수 | 트리거 조작 | 결과 |
|---|---|---|---|---|---|
| 1 | 1 | ON | 4 | 지속 write만 (채널 조작 없음) | 에러 없음 |
| 2 | 1 | ON | 4 | 부하 중 STOP/START REPLICA FOR CHANNEL | 에러 13122 재현 (game1~3 전부 중단) |
| 3 | 0 | ON | 4 | 부하 중 STOP/START REPLICA FOR CHANNEL | 에러 13122 발생 (game1 중단, 완전 해소 아님) |
| 4 | 1 | OFF | 4 | 부하 중 STOP/START REPLICA FOR CHANNEL | 에러 없음 (10회 반복 전부 정상) |
테스트 1과 2의 조건은 동일(workers=1, semi-sync ON)하며 채널 조작 유무만 다름. semi-sync ON 상태에서 write 부하만으로는 에러가 발생하지 않고, 운영 중 STOP/START REPLICA FOR CHANNEL을 실행하는 순간 기존 채널의 I/O 스레드가 즉시 중단됨.
초기 채널 연결(START REPLICA FOR CHANNEL) 시에는 semi-sync 트래픽이 없는 상태라 에러가 발생하지 않음. active semi-sync ACK 교환 중에 채널을 조작하는 것이 트리거임.
테스트 2는 Bug #102994 재현 조건: source × 4 (rpl_semi_sync_source_enabled=1), replica (rpl_semi_sync_replica_enabled=1, workers=1). 채널 game13 가동 중 부하를 주면서 3 전부 에러 13122로 즉시 중단**됨.STOP/START REPLICA FOR CHANNEL 'game4'를 반복 실행하자 **2회차에 game1
=== attempt 2: STOP/START game4 ===
game1 OFF 13122 Relay log write failure: could not queue event from source
game2 OFF 13122 Relay log write failure: could not queue event from source
game3 OFF 13122 Relay log write failure: could not queue event from source
game4 ON 0- STOP/START한 game4 자신은 정상 — 다른 채널만 피해를 받음
- 이후 attempt 3~5에서도 계속 에러 상태 유지 (복구 안 됨)
테스트 3(workers=0)에서도 game1이 에러 13122로 중단됨. replica_parallel_workers=0은 semi-sync 문제의 완전한 해결책이 아님.
테스트 4(semi-sync OFF)에서는 동일 조건에서 10회 반복 전부 에러 없음. rpl_semi_sync_replica_enabled=0이 근본 해결책임을 확인.
재현 방법
검증 버전: MySQL 8.0.44
필수 설정:
- 소스:
rpl_semi_sync_source_enabled = 1 - 레플리카:
rpl_semi_sync_replica_enabled = 1,replica_parallel_workers = 1 - 멀티소스 채널 3개 이상 가동 중
트리거 조작:
소스에 지속적인 write 부하를 주는 상태에서, 기존 채널 외에 채널을 하나 추가하고 STOP/START REPLICA FOR CHANNEL을 반복함. 1~2회만에 기존 채널 전체가 에러 13122로 중단됨.
game1 OFF 13122 Relay log write failure: could not queue event from source
game2 OFF 13122 Relay log write failure: could not queue event from source
game3 OFF 13122 Relay log write failure: could not queue event from source
game4 OFF 0 ← 조작한 채널은 에러 없음우회법 확인: rpl_semi_sync_replica_enabled = 0으로 설정하면 동일 조작을 10회 반복해도 에러 없음.
error 13122 소스 코드 분석
발생 경로
Last_IO_Errno: 13122는 I/O 스레드의 릴레이 로그 쓰기 실패임. 소스 코드상 두 가지 경로로 발생함:
handle_slave_io()
① ANONYMOUS_GTID_LOG_EVENT 수신
→ Gtid_log_event 파싱 실패 (is_valid() == false)
→ ER_REPLICA_RELAY_LOG_WRITE_FAILURE 보고 후 중단
② queue_event() 호출
→ relay_log.write_buffer() 실패
→ QUEUE_EVENT_ERROR_QUEUING 반환
→ ER_REPLICA_RELAY_LOG_WRITE_FAILURE 보고 후 중단에러 메시지(Relay log write failure)만으로는 원인이 semi-sync 충돌인지 디스크/네트워크 문제인지 구분할 수 없음 — 실제 원인 특정에는 Bug #102994와의 조건 대조가 필요함.
replica_parallel_workers 0 vs 1 아키텍처
한 줄 요약
= 0: SQL 스레드가 직접 이벤트를 적용 (Single-Threaded Applier)= 1: Coordinator 스레드 + Worker 1개로 분리 (MTS 아키텍처)
핵심 분기: rpl_replica.cc — slave_start_workers()
// slave_start_workers() — n은 replica_parallel_workers 값
if (n == 0 && rli->mts_recovery_group_cnt == 0) {
rli->workers.clear(); // Worker 배열 비움
rli->clear_processing_trx();
goto end; // MTS 초기화 전부 건너뜀
}
// 이 분기에 진입하면 아래 MTS 초기화 블록이 실행되지 않는다.
// n >= 1 이면 여기부터 실행
*mts_inited = true;
rli->gaq = new Slave_committed_queue(...); // GAQ 생성
for (uint i = 0; i < n; i++) {
slave_start_single_worker(rli, i); // Worker 스레드 생성
}= 0 | = 1 이상 | |
|---|---|---|
| Worker 스레드 | 생성 안 함 | n개 생성 |
| GAQ (Global Assigned Queue) | 없음 | 생성 |
is_parallel_exec() | false | true |
실행 구조 비교
replica_parallel_workers = 0
SQL Thread (read + apply)
← relay log 읽기 + 이벤트 적용 모두 담당replica_parallel_workers = 1
Coordinator Thread (read + schedule)
→ Worker Thread (apply)
Coordinator: GAQ, checkpoint, dependency trackingis_parallel_exec() — 모든 MTS 로직의 게이트
rpl_rli.h (MySQL 8.0 기준, 버전마다 라인 번호 상이):
inline bool is_parallel_exec() const {
bool ret = (replica_parallel_workers > 0) && !is_mts_recovery();
return ret;
}이 함수가 false(= 0)이면 MTS 관련 코드 경로가 전부 비활성화됨:
- Worker 할당 (
get_slave_worker()) 호출 안 함 - Logical Clock 기반 스케줄링 안 함
- checkpoint 로직 안 함
- commit ordering 안 함
동작 비교
| 관점 | = 0 | = 1 |
|---|---|---|
| 스레드 수 | SQL 스레드 1개 | Coordinator 1 + Worker 1 = 2개 |
| 오버헤드 | 없음 | GAQ, 작업 큐, checkpoint, 스레드 간 통신 |
| 이벤트 순서 | relay log 순서 그대로 | Logical Clock 의존성 추적 후 결정 |
| ANONYMOUS 트랜잭션 | 문제 없음 | GTID 중복 감지 없음 — 멀티소스 환경에서 주의 필요 |
| 트랜잭션 갭 | 발생 불가 | Worker 장애 시 갭 발생 가능 → recovery 필요 |
| 성능 | 낮은 지연, 단일 코어 | 1개 Worker면 오버헤드만 추가, 병렬 이득 없음 |
= 1은 병렬 처리 이득 없이 MTS 오버헤드만 추가됨. Worker가 1개뿐이면 Coordinator→Worker 직렬 파이프라인이 되므로 구조적으로 동시 실행 불가. 실질적인 병렬 처리 이득은 = 2 이상부터 발생함.
반면 = 0은 MTS 코드 경로를 완전히 우회하므로 가장 단순하고 안정적인 모드임.