Delayed replica 운영: 논리적 삭제 사고 복구와 한계
MySQL 지연 복제의 적용 시점과 GTID 중단 경계를 이해하고, 논리적 삭제 사고의 복구 절차·관측 지표·Aurora 차이를 정리한다.
1. 지연 복제는 과거로 되돌리는 기능이 아니다
정상적인 비동기 replica는 source의 잘못된 DELETE도 정상 트랜잭션으로 받아 적용한다. 고가용성 구성이 잘되어 있을수록 논리적 오류가 다른 서버로 빠르게 전파될 수 있다. Delayed replica는 이 전파 과정 중 수신은 계속하되 적용을 의도적으로 늦추어, 사고를 발견한 운영자가 삭제 전 상태를 보존할 시간을 확보하는 장치다.
핵심은 이미 적용한 변경을 취소하는 것이 아니라 아직 적용하지 않은 변경 앞에서 멈추는 것이다. 지연 시간이 지나 사고 트랜잭션까지 적용되었다면 그 replica에 지연 설정을 추가하거나 늘려도 삭제 전 데이터가 돌아오지 않는다. 백업과 binary log를 이용한 시점 복구가 여전히 필요하다.
이 글의 명령 표기는 MySQL 8.0.23 이상을 기준으로 한다. 진단 SQL과 복구 경계 실험은 MySQL 8.0.46에서 실행했다. 8.4 LTS에도 적용되는 기본 개념을 설명하되, 8.4 및 Aurora에서 동일한 장애 실험을 실행한 것은 아니다. delayed는 예제의 복제 채널명이다.
2. receiver와 applier 사이에 놓이는 시간 장벽
MySQL 비동기 복제는 source binary log를 가져오는 receiver와 relay log를 읽어 실행하는 applier를 분리한다. SOURCE_DELAY는 네트워크 수신을 정지시키는 설정이 아니다. receiver가 사고 트랜잭션까지 relay log에 보관하더라도, applier가 아직 실행하지 않았다면 테이블에는 이전 데이터가 남아 있다.
flowchart LR
A[Source에서 트랜잭션 커밋] --> B[Binary log]
B --> C[Replica receiver]
C --> D[Relay log에 수신 완료]
D --> E{설정한 지연 시간이 지났는가}
E -->|아니요| F[Applier 적용 대기]
F --> E
E -->|예| G[Worker가 실행하고 커밋]
F -. 사고 발견 후 SQL_THREAD 중단 .-> H[삭제 전 데이터 보존]
G --> I[Replica 데이터 변경]
MySQL 8.0끼리 복제할 때 지연 기준은 각 트랜잭션의 immediate_commit_timestamp다. 직접 연결된 source에서 커밋된 뒤 최소한 지정한 시간만큼 지난 후 실행할 수 있다. 트랜잭션의 GTID 이벤트 또는 anonymous GTID 이벤트에서 지연을 적용하며, 같은 트랜잭션의 모든 row event마다 추가로 같은 시간을 기다리는 방식이 아니다.
original_commit_timestamp는 최초 source의 커밋 시각을 나타낸다. 중간 replica가 다시 source가 되는 연쇄 복제에서는 중간 서버의 binary log에 기록되는 immediate_commit_timestamp가 달라진다. 따라서 지연값은 언제나 최초 writer를 기준으로 한 데이터 나이와 같다고 볼 수 없다. 전체 경로의 대기와 적용 시간을 함께 해석해야 한다.
또한 SOURCE_DELAY는 최대 지연도, 정확한 지연 목표도 아니다. 이미 네트워크나 적용 병목으로 오래 밀린 트랜잭션에는 설정값만큼의 추가 유예 시간이 반드시 생기는 것이 아니다. 반대로 CPU 부족, 대형 트랜잭션, worker의 잠금 대기 때문에 실제 적용은 훨씬 늦어질 수 있다. 서버 시계 동기화도 필수다. timestamp 기반 지연을 안전장치로 사용하면서 시계 오차를 방치하면 관측과 경계 판단을 신뢰하기 어렵다.
3. 지연 시간은 탐지·중단 시간에서 역산한다
지연값은 임의의 긴 숫자보다 사고 대응 절차를 기준으로 정해야 한다. 직접 연결된 source와 정상적인 시계를 전제로 다음 관계를 계획 기준으로 사용할 수 있다.
설정 지연 시간 > 사고 탐지 시간 + 담당자 판단·접속 시간 + applier 중단 확인 시간 + 안전 여유
이 관계는 용량 계획을 위한 조건이지 복구 보증식은 아니다. 잘못된 변경의 최초 발생 시점을 늦게 추정했거나, 이미 일부 worker가 사고 트랜잭션을 실행 중이라면 설정 지연값만으로 안전을 선언할 수 없다. 응답 시간은 평균보다 야간·휴일·알림 유실을 포함한 보수적인 범위로 잡는다.
| 설계 항목 | 운영상 의미 |
|---|---|
| 탐지 경로 | 대량 삭제 감사, 업무 건수·금액 이상, 애플리케이션 경보 등으로 실제 사고를 찾아야 한다. 일반 replication lag 알림만으로는 부족하다. |
| 서버 역할 | 지연 replica는 일반 읽기 풀과 자동 승격 후보에서 분리한다. 의도적으로 오래된 데이터를 보유한다. |
| Relay log 공간 | 변경량이 많은 구간의 binary log 생성률과 보관 시간을 기준으로 추정하고, 대형 트랜잭션과 중단 장기화 여유를 둔다. |
| 로그 보존 | Source 로그 보존 기간은 receiver 단절·재수신 시간을 감당해야 한다. relay log를 받았다는 사실만으로 독립 백업을 대체할 수 없다. |
| 적용 처리량 | 지연을 해제해도 밀린 작업을 처리할 능력이 있어야 복구 시간을 줄일 수 있다. |
| 권한·자동화 | 서비스 계정의 쓰기를 차단하고, 장애조치 도구가 지연 해제나 무조건적인 복제 재시작을 하지 못하도록 한다. |
super_read_only는 로컬 사용자 쓰기를 억제하는 데 유용하지만 replication applier의 변경 적용을 막는 장치는 아니다. 또한 relay_log_space_limit에 도달하면 receiver 진행까지 제한될 수 있다. 디스크가 무한히 쌓이지 않도록 한도와 잔여 공간을 관리하되, 한도를 설정했으니 수신이 항상 계속된다고 가정해서는 안 된다.
4. 설정값, 남은 대기, 수신·적용 경계를 분리해서 본다
다음 SQL은 조회용이다. 운영에서는 실제 채널명을 확인하고, Performance Schema 조회 권한이 있는 계정으로 실행한다. 첫 번째 쿼리는 설정 지연과 applier 상태를 함께 보여준다.
SELECT c.CHANNEL_NAME, c.DESIRED_DELAY, s.SERVICE_STATE, s.REMAINING_DELAY
FROM performance_schema.replication_applier_configuration AS c
JOIN performance_schema.replication_applier_status AS s USING (CHANNEL_NAME)
WHERE c.CHANNEL_NAME = 'delayed';
실행 결과(MySQL 8.0.x):
mysql> SELECT c.CHANNEL_NAME, c.DESIRED_DELAY, s.SERVICE_STATE, s.REMAINING_DELAY
-> FROM performance_schema.replication_applier_configuration AS c
-> JOIN performance_schema.replication_applier_status AS s USING (CHANNEL_NAME)
-> WHERE c.CHANNEL_NAME = 'delayed';
+--------------+---------------+---------------+-----------------+
| CHANNEL_NAME | DESIRED_DELAY | SERVICE_STATE | REMAINING_DELAY |
+--------------+---------------+---------------+-----------------+
| delayed | 120 | ON | 121 |
+--------------+---------------+---------------+-----------------+
1 row in set (0.00 sec)
DESIRED_DELAY는 설정값이고 REMAINING_DELAY는 현재 지연 대기의 남은 시간이다. 위 결과는 실제 두 인스턴스 실험에서 얻은 순간 관측값이다. 정수 초 표시와 관측 시점 차이가 있으므로 설정값과 남은 시간을 동일한 값으로 강제 비교하지 않는다. REMAINING_DELAY가 NULL인 경우도 오류와 동의어가 아니다. 현재 지연 대기 상태인지, 새로 처리할 트랜잭션이 없는지, applier가 정지했는지를 구분해야 한다.
두 번째 쿼리는 receiver의 상태와 이미 받은 GTID 집합이 모두 적용되었는지를 확인한다.
SELECT CHANNEL_NAME, SERVICE_STATE, LAST_ERROR_NUMBER,
GTID_SUBSET(RECEIVED_TRANSACTION_SET, @@GLOBAL.gtid_executed) AS received_all_applied
FROM performance_schema.replication_connection_status
WHERE CHANNEL_NAME = 'delayed';
실행 결과(MySQL 8.0.x):
mysql> SELECT CHANNEL_NAME, SERVICE_STATE, LAST_ERROR_NUMBER,
-> GTID_SUBSET(RECEIVED_TRANSACTION_SET, @@GLOBAL.gtid_executed) AS received_all_applied
-> FROM performance_schema.replication_connection_status
-> WHERE CHANNEL_NAME = 'delayed';
+--------------+---------------+-------------------+----------------------+
| CHANNEL_NAME | SERVICE_STATE | LAST_ERROR_NUMBER | received_all_applied |
+--------------+---------------+-------------------+----------------------+
| delayed | ON | 0 | 0 |
+--------------+---------------+-------------------+----------------------+
1 row in set (0.00 sec)
실험의 SERVICE_STATE=ON, LAST_ERROR_NUMBER=0, received_all_applied=0은 receiver가 정상 동작하면서 아직 적용하지 않은 수신 트랜잭션이 있다는 뜻이다. 지연 replica에서는 예상 가능한 조합이다. 다만 이것만으로 의도한 지연만 발생 중이라고 결론 내릴 수는 없다. replication_applier_status_by_coordinator와 replication_applier_status_by_worker의 상태·마지막 오류, SHOW REPLICA STATUS의 SQL_Delay, SQL_Remaining_Delay, Replica_SQL_Running_State를 함께 확인한다.
이 두 조회는 독립된 MySQL 컨테이너에서도 문법·객체 존재를 검증했고, 본문 실행 결과는 실제 delayed 채널이 있는 두 인스턴스 실험값을 사용했다. 채널이 없는 서버에서 Empty set이 나오는 것은 지연이 0이라는 뜻이 아니라 조회할 채널이 없다는 뜻이다.
received_all_applied=1도 source 최신 상태나 데이터 무결성을 증명하지 않는다. receiver가 끊긴 채 자신이 받은 것만 전부 실행했을 수 있고, 필터·건너뛰기 때문에 GTID 이력과 데이터가 어긋날 수도 있다. Seconds_Behind_Source 한 숫자를 지연 목표와 비교해 정상·비정상을 나누기보다, 수신 중단·의도한 대기·적용 병목을 별도 상태로 관리한다.
5. 삭제 사고의 첫 조치는 조사보다 적용 중단이다
사고가 의심되면 승인된 비상 절차에 따라 지연 replica의 applier를 먼저 멈춘다. 쿼리를 분석하는 동안에도 시간 장벽은 계속 줄어든다. 다음은 해당 채널을 가진 서버에서만 실행하는 운영 명령이다. 실험에서도 동일한 채널명으로 실행했다.
STOP REPLICA SQL_THREAD FOR CHANNEL 'delayed';
이 명령은 지연 시간이 끝날 때까지 기다리는 명령이 아니다. 다만 명령을 전송했다는 사실과 중단 완료는 다르다. 진행 중인 작업과 worker 상태를 확인하고, 반환 오류·시간 초과 여부를 기록한다. 이미 실행·커밋한 트랜잭션을 되돌리는 효과는 없다.
receiver는 당장 계속 동작하게 두어 후속 binary log를 확보할 수 있다. 이후 증거와 복구용 사본을 보존하는 단계에서는 디스크 사용량과 로그 확보 범위를 확인하고, 필요하면 채널 전체를 멈춘다. 적용 중단과 네트워크 수신 중단은 목적이 다르다.
중단 직후 다음 정보를 함께 보존한다.
- 채널 상태, 수신 GTID 집합,
@@GLOBAL.gtid_executed, worker의 실행·오류 상태 - Source binary log와 replica relay log, 관련 시각의 애플리케이션·감사 기록
- 대상 테이블·PK 범위·스키마, 사고 전후 업무 수량·금액 등 검증 기준
- 자동 재시작·승격을 막은 구성과 복구 담당자의 작업 기록
STOP REPLICA만으로 재부팅 후의 자동 시작 정책까지 영구 차단했다고 간주하지 않는다. 복구 서버의 시작 옵션과 운영 자동화를 별도로 확인한다. RESET REPLICA를 정리 명령으로 먼저 실행해서도 안 된다. 복구에 필요한 relay log와 복제 메타데이터를 잃을 수 있고, 지연 설정도 초기화된다.
6. 검증 실험: 삭제 이전에 멈추고 정상 변경까지만 적용하기
폐기용 MySQL 8.0.46 두 인스턴스에서 GTID auto-position, ROW 복제, 병렬 worker 4개로 실험했다. 일반 서비스에는 연결하지 않았다. 처음에는 지연 없이 초기 데이터를 동기화하고, 4초 지연에서 새 행이 즉시 나타나지 않다가 이후 적용되는 것을 확인했다. 그다음 지연을 120초로 바꾸어 복구 경계를 검증했다. 이 짧은 값들은 실험 시간을 줄이기 위한 것이며 운영 권장값이 아니다.
이미 구성된 테스트 채널에서 applier를 멈추고 지연값을 변경한 뒤 다시 시작했다. 아래는 다중 서버 실험용 명령 기록이며, 일반 운영 서버에 일괄 실행하는 스크립트가 아니다.
STOP REPLICA SQL_THREAD FOR CHANNEL 'delayed';
CHANGE REPLICATION SOURCE TO SOURCE_DELAY=120 FOR CHANNEL 'delayed';
START REPLICA SQL_THREAD FOR CHANNEL 'delayed';
Source에는 delay_demo.accounts(id INT PRIMARY KEY, balance INT NOT NULL) 테이블과 (1,100), (2,200), (3,300) 행이 있었다. Source에서 아래 두 문장을 각각 autocommit 트랜잭션으로 실행했다. 정상 잔액 변경 다음에 잘못된 전체 삭제가 오는 순서다.
UPDATE delay_demo.accounts SET balance=250 WHERE id=2;
DELETE FROM delay_demo.accounts;
삭제 GTID까지 replica에 수신된 것을 확인한 직후 SQL thread를 중단했다. 이때 Source의 행 수는 0이지만 replica에는 3개 행이 남았다. 다음은 실제 클라이언트 출력이다.
mysql> SELECT COUNT(*) AS source_rows FROM delay_demo.accounts;
+-------------+
| source_rows |
+-------------+
| 0 |
+-------------+
1 row in set (0.00 sec)
mysql> SELECT id, balance FROM delay_demo.accounts ORDER BY id;
+----+---------+
| id | balance |
+----+---------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+---------+
3 rows in set (0.00 sec)
이 상태는 삭제는 피했지만 정상 잔액 변경도 아직 반영하지 않은 상태다. 더 최신의 정상 상태를 얻으려면 무조건 복제를 재개하는 대신, 사고 트랜잭션 직전이라는 명시적인 정지 조건을 걸어야 한다.
실험에서는 동시 쓰기가 없는 source에서 정상 변경 전후의 GTID 집합을 비교해 삭제 트랜잭션의 GTID를 식별했다. 실제 업무 시스템에서는 같은 시간에 정상 트랜잭션들이 함께 커밋되므로 집합 차이 하나만으로 사고 GTID를 단정할 수 없다. binary log나 relay log를 mysqlbinlog로 분석하고, 트랜잭션 경계·대상 테이블·업무 기록을 대조해야 한다. ROW 형식이면 원래 SQL 문장이 그대로 보이지 않을 수 있으므로 row event의 의미를 해석한다.
다음 UUID는 이번 폐기용 실험에서 생성된 값이다. 운영에 복사할 값이 아니다. 중단한 replica에서 사고 GTID가 아직 실행되지 않았음을 확인한 뒤, 지연을 해제하면서 SQL_BEFORE_GTIDS 조건으로 재개했다.
mysql> CHANGE REPLICATION SOURCE TO SOURCE_DELAY=0 FOR CHANNEL 'delayed';
Query OK, 0 rows affected (0.00 sec)
mysql> START REPLICA SQL_THREAD UNTIL SQL_BEFORE_GTIDS='e59de6e2-b161-11f1-a93b-0242c0a81002:8' FOR CHANNEL 'delayed';
Query OK, 0 rows affected (0.02 sec)
START REPLICA의 성공 응답은 정지 경계 도달을 의미하지 않는다. 별도로 applier가 OFF가 될 때까지 확인했고, 정상 변경 GTID가 모두 적용되었으며 사고 GTID는 적용되지 않았다는 조건도 검사했다. 그 결과는 다음과 같다.
mysql> SELECT id, balance FROM delay_demo.accounts ORDER BY id;
+----+---------+
| id | balance |
+----+---------+
| 1 | 100 |
| 2 | 250 |
| 3 | 300 |
+----+---------+
3 rows in set (0.00 sec)
mysql> SELECT GTID_SUBSET('e59de6e2-b161-11f1-a93b-0242c0a81002:8',@@GLOBAL.gtid_executed) AS bad_applied;
+-------------+
| bad_applied |
+-------------+
| 0 |
+-------------+
1 row in set (0.00 sec)
id=2의 잔액은 250으로 갱신됐지만 전체 삭제는 적용되지 않았다. 마지막으로 채널 전체를 중단했다. 이 실험은 단일 source·단일 채널의 작은 InnoDB 트랜잭션에서 경계를 검증한 것이다. 멀티소스 전체의 동일 시점 정합성, 대규모 복구 소요 시간, 서비스 전환까지 검증한 것은 아니다.
특히 UNTIL 조건은 영구적인 사고 차단 규칙이 아니다. 해당 실행이 끝난 뒤 조건 없이 복제를 다시 시작하면 보류한 삭제가 적용될 수 있다. 복구 사본을 보존하고 자동 재시작을 통제하기 전에는 평소 운영 절차로 되돌리지 않는다.
7. GTID 경계가 보장하는 것과 보장하지 않는 것
SQL_BEFORE_GTIDS는 지정 집합에 속하는 트랜잭션을 만나기 전에 멈춘다. 시간 문자열을 기준으로 대략 중단하는 것보다 의도를 명확하게 표현할 수 있지만, GTID는 모든 source에 걸친 전역 시각이나 업무 정합성 인증서가 아니다. 집합에 여러 GTID를 넣었다면 실제 복제 스트림에서 먼저 만나는 대상이 정지 기준이 된다. GTID 문자열의 번호 순서만으로 업무의 선후 관계를 추정하지 않는다.
판단할 때는 다음 조건을 함께 확인한다.
- 사고 GTID 미적용: 이미 적용했다면
UNTIL로 되감기할 수 없다. 다른 지연 사본이나 백업/PITR로 전환한다. - 수신 경계 확보: 정지 조건의 트랜잭션이 아직 도착하지 않았다면 원하는 중단이 즉시 일어나지 않는다. 수신 상태와 로그 보존 범위를 확인한다.
- 정상 선행 변경 확인: 보존하려는 정상 트랜잭션이 실제 적용되었는지, 데이터 값이 맞는지 확인한다.
- Worker 종료·오류 확인: 병렬 복제에서는 coordinator와 worker 상태, 적용 공백, 마지막 오류까지 확인한다. 임의의 worker 한 개만 보고 일관된 prefix라고 판단하지 않는다.
- 복수 사고의 범위 확인: 여러 문장·배치가 나누어 커밋되었다면 첫 번째 유해 트랜잭션이 어디인지 찾아야 한다. 마지막 대량 삭제 하나만 막아서는 충분하지 않다.
커밋 순서 보존은 순서 관측을 돕는 장치이지, 복제 스트림을 업무적으로 안전한 변경과 위험한 변경으로 자동 분리하는 기능은 아니다. 사고 GTID를 빈 트랜잭션으로 건너뛰고 그대로 서비스에 투입하는 것도 일반 복구 절차로 삼지 않는다. 후속 변경이 삭제된 상태를 전제로 동작했을 수 있어, 삭제 하나만 빼고 전부 실행한 결과가 올바른 데이터라는 보장은 없다.
8. 복구는 승격보다 데이터 병합 문제인 경우가 많다
사고 전 상태를 확보했다면 즉시 지연 replica를 새 writer로 승격하기보다 복구 범위를 결정한다.
| 방식 | 적합한 상황 | 반드시 확인할 사항 |
|---|---|---|
| 필요한 행만 추출해 복원 | 삭제 PK 범위를 특정할 수 있고 현재 서비스의 정상 쓰기를 유지해야 할 때 | 현재 행과 충돌, 삭제 뒤 재생성된 PK, 외래 키, 업무 상태 변경 |
| 데이터베이스 전체를 복구 사본으로 전환 | 광범위한 손상으로 개별 병합이 더 위험할 때 | 복구 지점 이후 정상 변경의 손실, 이전 writer 차단, 연결 전환, 재처리 |
| 백업과 binary log로 PITR | 지연 시간이 지났거나 안전한 replica가 없을 때 | 유효 백업, 연속 로그, 정확한 복구 경계, 격리된 검증 환경 |
부분 복구에서는 멈춘 사본에서 필요한 데이터를 별도 복구 스키마나 파일로 추출하고, 운영 데이터와 비교한 후 승인된 트랜잭션으로 병합한다. 예전 행을 REPLACE로 밀어 넣으면 현재 정상 데이터까지 덮어쓸 수 있다. INSERT IGNORE 역시 충돌을 감춰 업무 손실을 놓치게 만들 수 있으므로 편의 명령을 정합성 정책으로 사용하지 않는다.
전체 전환은 서버 가용성을 되찾는 것과 별개로 데이터 손실 승인이 필요하다. 지연된 사본에는 사고 뒤의 정상 주문·결제·상태 변경도 없을 수 있다. 단순 행 수 외에 금액 합계, 부모·자식 관계, 업무 상태 전이와 외부 시스템 처리 이력을 대조한다. 복구를 완료한 뒤에는 검증된 기준점으로 지연 replica를 다시 구성하고 동일한 중단 훈련을 반복한다.
9. Aurora MySQL에서는 같은 클러스터의 reader와 구분한다
Aurora 클러스터의 일반 reader는 같은 클러스터 스토리지를 사용하는 읽기 인스턴스다. Community MySQL의 독립 binary log replica에 적용하는 SOURCE_DELAY를 같은 클러스터 reader에 설정하여 삭제 전 사본으로 운영하는 모델이 아니다. Reader와 장애조치 기능이 있어도 논리적 삭제에 대한 독립 보존 계층이 자동으로 생기지 않는다.
Aurora를 source로 별도의 MySQL binary log replica를 운영한다면, 그 외부 replica의 지연 적용을 설계할 수 있다. 이때는 Aurora binary logging 활성화, 로그 보존, 네트워크, 엔진 버전 호환성, source 장애조치 후 연결 복구를 함께 검증해야 한다. 반대로 Aurora를 외부 source의 복제 대상으로 쓰는 구성은 AWS가 제공하는 관리 프로시저와 지원 범위를 따라야 한다. Community의 관리 명령을 권한 확인 없이 그대로 적용하지 않는다.
Aurora의 시점 복구는 별도 클러스터에 복원하는 경로로 계획한다. 자동 백업 보존 기간, 실제 복원 가능한 최신 시점, 복원 시간, 데이터 검증과 애플리케이션 전환 시간을 측정해야 한다. Aurora의 백업/PITR와 외부 지연 replica는 대체 관계로 단순화하지 말고, 탐지 시간과 복구 목표가 다른 보호 계층으로 평가한다.
10. 운영 점검표
-
RESET REPLICA
11. 정리
Delayed replica의 가치는 느린 복제 자체가 아니라 사고 트랜잭션이 적용되기 전에 대응할 시간을 확보하는 것이다. 안전한 운영은 지연 설정, 사고 탐지, applier 중단, 정확한 트랜잭션 경계 식별, 복구 데이터 검증이 연결될 때 성립한다. 이미 적용한 오류를 되돌리는 기능이나 독립 백업으로 오해하면 보호 범위를 과대평가하게 된다.
다음 복구 설계에서는 이 시간 창을 놓친 경우까지 다루어야 한다. 유효한 백업과 연속적인 binary log를 확보하고, 별도 서버에서 사고 직전까지 재생한 뒤 업무 정합성을 확인하는 시점 복구 절차가 그 기반이다.