Row-based replication 내부: row event와 table map event 읽기
MySQL 행 기반 복제의 Table_map과 row event 구조를 실제 binlog로 읽고, FULL·MINIMAL 이미지와 트랜잭션 경계를 운영 관점에서 해석한다.
행 기반 복제(Row-based replication, RBR)를 운영할 때 바이너리 로그를 단순히 “변경 SQL을 모아 둔 파일”로 생각하면 장애 분석에서 판단을 잘못하기 쉽다. UPDATE를 실행했는데 로그에는 원래 조건식이 없고, 컬럼 이름 대신 @1, @2가 보이며, 같은 테이블에 대한 Table_map 이벤트가 반복되기도 한다. 이는 로그가 손상된 징후가 아니라 행 변경을 재현하기 위한 바이너리 형식을 사람이 읽도록 풀어 놓은 결과다.
이 글은 MySQL 8.0 이상을 대상으로 Table_map과 row event의 연결 관계, 변경 전후 이미지, 트랜잭션 경계를 설명한다. SQL은 MySQL 8.0 테스트 인스턴스에서 검증하고, 별도의 binlog 활성화 인스턴스에서 실제 이벤트까지 생성해 확인했다. 복제본 연결·적용 장애나 Aurora 환경 자체를 재현한 것은 아니다.
1. SQL 실행과 행 변경 기록은 다른 계층이다
Statement-based logging은 소스에서 실행한 문장을 기록하고 복제본이 그 문장을 실행하게 한다. RBR은 소스에서 문장을 실행해 결정한 실제 변경 행을 기록한다. 예를 들어 재고가 특정 범위인 행을 감소시키는 UPDATE라면, 복제본은 원래 범위 조건을 다시 평가해 대상 집합을 만드는 대신 전달받은 행 변경을 적용한다.
그렇다고 RBR이 디스크 페이지를 그대로 복사하는 것은 아니다. InnoDB의 redo는 스토리지 엔진 복구를 위한 기록이고, row event는 테이블·컬럼·행 값에 기반한 논리적 변경 기록이다. 복제본에서도 행을 찾고 변경하며, 인덱스 갱신과 제약 검사, 잠금 획득, redo 생성 같은 비용이 발생한다. 소스에서 한 문장으로 끝난 대량 UPDATE가 복제본에서도 저렴하게 끝난다고 보장할 수 없다.
InnoDB 트랜잭션의 변경 이벤트는 트랜잭션용 binlog cache에 축적되고 커밋 경로에서 바이너리 로그에 기록된다. 큰 트랜잭션은 cache가 디스크로 넘쳐 소스의 I/O 부담을 늘릴 수 있다. 복제본의 수신 경로는 이벤트를 relay log에 보관하고 적용 경로가 트랜잭션을 실행한다. 수신 완료, 적용 완료, 커밋 완료는 서로 다른 상태다.
flowchart TD
A[소스에서 SQL 실행] --> B[변경 행 결정]
B --> C[Table_map과 row event 생성]
C --> D[트랜잭션용 binlog cache]
D --> E[커밋 경로에서 binlog 기록]
E --> F[복제본 수신과 relay log 기록]
F --> G[Table_map으로 테이블과 컬럼 구조 해석]
G --> H[행 검색과 INSERT·UPDATE·DELETE 적용]
H --> I[트랜잭션 커밋]
이 그림은 역할을 구분한 개념도다. group commit, 병렬 적용, 트랜잭션 압축 등으로 실제 처리 단위와 이벤트의 외형은 달라질 수 있다. 또한 ROW 모드에서도 DDL과 트랜잭션 제어는 Query 이벤트 등으로 기록된다. 모든 이벤트가 row event로 바뀌는 것은 아니다.
2. Table_map은 행을 해석할 문맥을 제공한다
row event는 매번 데이터베이스명, 테이블명, 전체 컬럼 정의를 반복하지 않는다. 대신 table_id와 컬럼 포함 여부를 나타내는 bitmap, 행 값 등을 담고, 앞서 전달된 Table_map 이벤트가 그 table_id를 해석할 문맥을 제공한다.
| 구성 요소 | 해석할 내용 | 운영 시 주의점 |
|---|---|---|
| 이벤트 공통 헤더 | 이벤트 종류, 생성 시각, server id, 길이, 위치 정보 | 시각만으로 전체 트랜잭션 범위를 확정하지 않는다 |
| Table_map | table id, 데이터베이스·테이블명, 컬럼 수·자료형·형식별 메타데이터, NULL 허용 정보 | 테이블의 과거 구조와 연결해야 한다 |
| row event 헤더와 bitmap | 대상 table id, 플래그, 이미지에 포함된 컬럼 | 빠진 컬럼과 NULL 값을 구별한다 |
| 행 데이터 | 변경 전 이미지 또는 변경 후 이미지 | 원래 SQL의 조건식과 같은 것이 아니다 |
| 커밋 경계 | 일반적인 InnoDB 트랜잭션에서 Xid 이벤트와 COMMIT 표시 | 행 이벤트 한 건을 독립 트랜잭션으로 취급하지 않는다 |
table_id는 행의 PK도, InnoDB 데이터 딕셔너리의 영구 테이블 식별자도 아니다. 해당 이벤트 흐름에서 사용하는 매핑 식별자이므로 재시작·파일·문장 경계를 무시하고 숫자만으로 테이블을 연결하면 안 된다. Table_map은 같은 테이블에 대해서도 반복될 수 있다. 이를 중복 실행 증거로 해석하지 않는다.
binlog_row_metadata와 binlog_row_image도 별개의 설정이다. 전자는 이벤트 해석에 필요한 테이블 메타데이터의 상세 수준을, 후자는 행 이미지에 포함할 컬럼 값의 범위를 제어한다. MySQL 8.0의 확장 메타데이터는 컬럼 문자셋이나 signedness 정보를 담을 수 있고, binlog_row_metadata=FULL에서는 컬럼 이름과 PK 정보 등도 더 기록한다. 따라서 “binlog에는 컬럼 이름이나 문자셋 정보가 절대로 없다”는 설명은 지나치게 단정적이다. 다만 이 글의 mysqlbinlog -vv 출력은 컬럼을 @N으로 표시하므로, 당시 DDL과 컬럼 순서를 대조하는 절차가 여전히 중요하다.
3. Write·Update·Delete 이벤트를 읽는 순서
세 종류의 행 이벤트는 필요한 이미지가 다르다.
- Write_rows: 새 행을 만들기 위한 변경 후 이미지를 담는다.
- Update_rows: 대상 행 식별에 사용할 변경 전 이미지와 적용할 변경 후 이미지의 쌍을 담는다.
- Delete_rows: 삭제 대상 행을 찾기 위한 변경 전 이미지를 담는다.
하나의 이벤트에 여러 행이 들어갈 수 있다. 반대로 큰 문장의 행 집합은 여러 이벤트로 나뉠 수 있다. 따라서 row event 개수, 변경 행 수, SQL 문장 수, 트랜잭션 수는 서로 같지 않다.
mysqlbinlog -v가 보여 주는 ### WHERE는 변경 전 이미지이고 ### SET은 변경 후 이미지다. 이는 원래 SQL을 복원한 것이 아니다. UPDATE의 WHERE가 SET보다 먼저 보이는 것도 이 표현 방식 때문이다. STMT_END_F는 해당 문장의 행 이벤트 끝을 나타내며 트랜잭션 커밋을 뜻하지 않는다.
일반적인 GTID 활성화 InnoDB 변경 트랜잭션은 GTID 이벤트, BEGIN, Table_map과 row event들, Xid/COMMIT 순서로 읽을 수 있다. GTID 비활성화 환경, DDL, XA, 압축된 트랜잭션에서는 세부 형식이 달라진다. # at과 end_log_pos는 파일 위치 판단에 쓰되, 파일명과 함께 기록하고 실제 이벤트·트랜잭션 경계를 확인한다.
4. 재현 환경과 설정 확인
다음 예제는 운영 테이블이 없는 폐기 가능한 테스트 데이터베이스에서 위에서 아래로 실행한다. 테이블명은 rbr_item이며 마지막에 삭제한다. FULL과 MINIMAL 비교는 모두 명시적 PK가 있는 평범한 InnoDB 테이블을 대상으로 한다.
SELECT VERSION() AS mysql_version,
@@global.log_bin AS log_bin,
VARIABLE_VALUE AS binlog_format,
@@session.binlog_row_image AS row_image,
@@global.binlog_row_metadata AS row_metadata
FROM performance_schema.session_variables
WHERE VARIABLE_NAME = 'binlog_format';
실행 결과(MySQL 8.0.x):
mysql> SELECT VERSION() AS mysql_version,
-> @@global.log_bin AS log_bin,
-> VARIABLE_VALUE AS binlog_format,
-> @@session.binlog_row_image AS row_image,
-> @@global.binlog_row_metadata AS row_metadata
-> FROM performance_schema.session_variables
-> WHERE VARIABLE_NAME = 'binlog_format';
+---------------+---------+---------------+-----------+--------------+
| mysql_version | log_bin | binlog_format | row_image | row_metadata |
+---------------+---------+---------------+-----------+--------------+
| 8.0.46 | 0 | ROW | FULL | MINIMAL |
+---------------+---------+---------------+-----------+--------------+
1 row in set (0.01 sec)
SQL 실행 확인에 사용한 인스턴스는 binlog가 꺼져 있어 위 결과의 log_bin이 0이다. 이것만으로 이벤트 생성이 검증된 것은 아니다. 실제 이벤트 확인에는 별도의 mysql-rbr-lab 컨테이너를 사용하고 log_bin=1, binlog_format=ROW를 확인한 뒤 동일한 SQL을 실행했다. 별도 인스턴스의 주요 서버 시작 옵션은 다음과 같다.
--server-id=124
--log-bin=/var/lib/mysql/rbr-bin
--binlog-format=ROW
--binlog-row-image=FULL
--binlog-row-metadata=MINIMAL
--gtid-mode=ON
--enforce-gtid-consistency=ON
--binlog-transaction-compression=OFF
이는 실습 조건이지 운영 서버에 그대로 적용할 변경안이 아니다. 이미 켜진 운영 binlog를 이 예제를 위해 초기화하거나 삭제하지 않는다. 실습에서는 데이터를 입력하기 전에 바이너리 로그를 한 번 회전하고, 재현 후 다시 회전해 읽을 파일을 닫았다. 아래 파일 번호는 그 실습에서 확인한 값이며, 다른 환경에서는 실제 파일 목록과 대상 트랜잭션 위치를 확인해야 한다.
5. FULL 이미지로 삽입과 갱신 확인
SET SESSION binlog_row_image = 'FULL';
CREATE TABLE rbr_item (
id INT NOT NULL PRIMARY KEY,
name VARCHAR(20) NOT NULL,
qty INT NOT NULL
) ENGINE=InnoDB;
START TRANSACTION;
INSERT INTO rbr_item VALUES (1, 'pen', 10), (2, 'book', 20);
UPDATE rbr_item SET qty = 7 WHERE id = 1;
COMMIT;
SELECT id, name, qty FROM rbr_item ORDER BY id;
실행 결과(MySQL 8.0.x):
mysql> SET SESSION binlog_row_image = 'FULL';
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE rbr_item (
-> id INT NOT NULL PRIMARY KEY,
-> name VARCHAR(20) NOT NULL,
-> qty INT NOT NULL
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO rbr_item VALUES (1, 'pen', 10), (2, 'book', 20);
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> UPDATE rbr_item SET qty = 7 WHERE id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT id, name, qty FROM rbr_item ORDER BY id;
+----+------+-----+
| id | name | qty |
+----+------+-----+
| 1 | pen | 7 |
| 2 | book | 20 |
+----+------+-----+
2 rows in set (0.00 sec)
FULL에서는 이 예제의 UPDATE 변경 전 이미지에 (1, 'pen', 10), 변경 후 이미지에 (1, 'pen', 7)이 들어간다. 원래 WHERE는 id = 1뿐이지만, 디코딩 결과의 WHERE 쪽에는 다른 컬럼 값도 나타난다. 이 차이를 이해해야 “소스 애플리케이션이 name과 qty까지 조건으로 사용했다”는 오판을 피할 수 있다.
실습 컨테이너에 서버와 같은 8.0.46 버전의 mysqlbinlog 실행 파일을 준비한 뒤 다음 명령으로 바이너리 로그를 읽었다. 최소 구성의 MySQL Docker 이미지에는 이 도구가 없을 수 있으므로 실행 가능 여부를 먼저 확인한다. 결과 파일은 행 값이 포함된 민감 자료가 될 수 있으므로 실제 운영 분석 시에는 접근 권한과 보관 기간을 제한한다.
docker exec mysql-rbr-lab mysqlbinlog \
--base64-output=DECODE-ROWS -vv \
/var/lib/mysql/rbr-bin.000004
실제 binlog 출력 중 FULL 구간은 아래와 같다. 시각·위치·checksum을 포함한 이벤트 헤더와 반복되는 세션 설정은 생략하고, Table_map 연결과 행 값 부분을 발췌했다.
# Table_map: `mysql_tech_note`.`rbr_item` mapped to number 87
# Write_rows: table id 87 flags: STMT_END_F
### INSERT INTO `mysql_tech_note`.`rbr_item`
### SET
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2='pen' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
### @3=10 /* INT meta=0 nullable=0 is_null=0 */
### INSERT INTO `mysql_tech_note`.`rbr_item`
### SET
### @1=2 /* INT meta=0 nullable=0 is_null=0 */
### @2='book' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
### @3=20 /* INT meta=0 nullable=0 is_null=0 */
# Table_map: `mysql_tech_note`.`rbr_item` mapped to number 87
# Update_rows: table id 87 flags: STMT_END_F
### UPDATE `mysql_tech_note`.`rbr_item`
### WHERE
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2='pen' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
### @3=10 /* INT meta=0 nullable=0 is_null=0 */
### SET
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### @2='pen' /* VARSTRING(80) meta=80 nullable=0 is_null=0 */
### @3=7 /* INT meta=0 nullable=0 is_null=0 */
# Xid = 14
COMMIT/*!*/;
여기서 @1, @2, @3는 각각 id, name, qty에 해당한다. 문자열 타입 주석의 길이는 문자 수와 같다고 단정하지 않는다. 예를 들어 utf8mb4의 VARCHAR(20)은 최대 바이트 길이에 따라 VARSTRING(80)으로 표시될 수 있다. meta, nullable, is_null도 컬럼 형식과 해당 값의 상태를 구분해 읽는다.
6. MINIMAL은 NULL이 아니라 생략이다
SET SESSION binlog_row_image = 'MINIMAL';
START TRANSACTION;
UPDATE rbr_item SET qty = 5 WHERE id = 1;
DELETE FROM rbr_item WHERE id = 2;
COMMIT;
SELECT id, name, qty FROM rbr_item ORDER BY id;
SET SESSION binlog_row_image = 'FULL';
실행 결과(MySQL 8.0.x):
mysql> SET SESSION binlog_row_image = 'MINIMAL';
Query OK, 0 rows affected (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE rbr_item SET qty = 5 WHERE id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> DELETE FROM rbr_item WHERE id = 2;
Query OK, 1 row affected (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT id, name, qty FROM rbr_item ORDER BY id;
+----+------+-----+
| id | name | qty |
+----+------+-----+
| 1 | pen | 5 |
+----+------+-----+
1 row in set (0.00 sec)
mysql> SET SESSION binlog_row_image = 'FULL';
Query OK, 0 rows affected (0.00 sec)
같은 실습 binlog에서 MINIMAL 구간을 발췌하면 다음과 같다. 앞 절과 동일하게 이벤트 헤더의 시각·위치 등은 생략했다.
# Table_map: `mysql_tech_note`.`rbr_item` mapped to number 87
# Update_rows: table id 87 flags: STMT_END_F
### UPDATE `mysql_tech_note`.`rbr_item`
### WHERE
### @1=1 /* INT meta=0 nullable=0 is_null=0 */
### SET
### @3=5 /* INT meta=0 nullable=0 is_null=0 */
# Table_map: `mysql_tech_note`.`rbr_item` mapped to number 87
# Delete_rows: table id 87 flags: STMT_END_F
### DELETE FROM `mysql_tech_note`.`rbr_item`
### WHERE
### @1=2 /* INT meta=0 nullable=0 is_null=0 */
# Xid = 22
COMMIT/*!*/;
명시적 PK가 있는 이 예제에서는 UPDATE의 변경 전 이미지에 @1=1만, 변경 후 이미지에 변경한 @3=5만 보인다. DELETE에도 삭제 대상의 PK인 @1=2만 남는다. 빠진 @2는 NULL로 바뀐 것이 아니다. 이벤트에 값이 기록되지 않았다는 뜻이며 기존 값이나 외부 스냅샷 없이 그 시점의 전체 행을 재구성할 수는 없다.
| 설정 | 행 이미지의 기본 방향 | 선택 기준 |
|---|---|---|
| FULL | 전후 이미지에 모든 컬럼을 기록 | 전체 행 기반 CDC·분석·복구 요구와 로그 비용을 함께 평가 |
| MINIMAL | 식별에 필요한 변경 전 컬럼과 지정·변경한 변경 후 컬럼 중심 | 복제·CDC 소비자가 생략된 컬럼을 올바르게 처리하는지 확인 |
| NOBLOB | FULL과 유사하되 불필요한 BLOB/TEXT 값을 생략 | 큰 객체의 로그 비용과 소비자 요구를 함께 검증 |
MINIMAL이 언제나 “PK 하나만 기록”한다는 뜻은 아니다. PK가 없으면 적절한 NOT NULL UNIQUE 키 또는 더 많은 컬럼을 사용해야 하며, 식별 가능한 키가 없으면 변경 전 이미지가 크게 남을 수 있다. 행 검색 비용까지 증가할 수 있으므로 이미지 설정을 PK 설계의 대체물로 보지 않는다.
또한 FULL은 원래 SQL, 사용자 의도, 모든 업무 문맥을 남기는 감사 로그가 아니다. JSON 부분 갱신은 binlog_row_value_options의 영향까지 받아 별도 검토가 필요하다. 이 글의 작은 정수·문자열 재현 결과를 JSON·BLOB·generated column을 포함한 모든 스키마에 그대로 일반화하지 않는다.
7. 컬럼 순서는 과거 DDL과 함께 확인한다
다음 조회는 실습 테이블의 현재 컬럼 순서를 확인하고 실습 자원을 정리한다.
SELECT ORDINAL_POSITION, COLUMN_NAME, COLUMN_TYPE
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'rbr_item'
ORDER BY ORDINAL_POSITION;
DROP TABLE rbr_item;
실행 결과(MySQL 8.0.x):
mysql> SELECT ORDINAL_POSITION, COLUMN_NAME, COLUMN_TYPE
-> FROM information_schema.COLUMNS
-> WHERE TABLE_SCHEMA = DATABASE()
-> AND TABLE_NAME = 'rbr_item'
-> ORDER BY ORDINAL_POSITION;
+------------------+-------------+-------------+
| ORDINAL_POSITION | COLUMN_NAME | COLUMN_TYPE |
+------------------+-------------+-------------+
| 1 | id | int |
| 2 | name | varchar(20) |
| 3 | qty | int |
+------------------+-------------+-------------+
3 rows in set (0.01 sec)
mysql> DROP TABLE rbr_item;
Query OK, 0 rows affected (0.00 sec)
이 결과는 실습 시점의 스키마와 이벤트를 연결하는 데 사용했다. 실제 운영에서 몇 달 전 binlog를 읽는다면 현재 information_schema.COLUMNS만으로 충분하지 않다. 중간에 컬럼 추가·삭제·순서 변경·자료형 변경이 있었을 수 있기 때문이다. 해당 시점의 백업 DDL과 이후 DDL 이벤트를 함께 적용해 문맥을 복원한다. Table_map의 컬럼 수와 타입, 확장 메타데이터는 유용한 단서이지만 모든 운영 스키마 이력을 대신하지는 않는다.
원래 문장이 꼭 필요하다면 사전에 binlog_rows_query_log_events 사용 여부를 검토할 수 있다. 활성화 시 Rows_query 이벤트가 진단에 도움을 주지만, 과거에 기록하지 않은 SQL을 소급 복원할 수는 없다. SQL의 리터럴에 민감 정보가 포함될 가능성, 로그 용량, 조회 권한도 함께 고려한다.
8. 장애 분석에서 지켜야 할 경계
8.1 디코딩 출력은 재실행 스크립트가 아니다
--base64-output=DECODE-ROWS -vv는 사람이 읽기 위한 조합이다. ###로 시작하는 문장은 주석 형태의 의사 SQL이며, 이 출력은 row event를 재실행하는 데 필요한 BINLOG 문을 숨긴다. 출력에서 ###를 제거해 운영 서버에 붙여 넣거나 mysql로 바로 파이프하지 않는다.
복구가 목적이라면 원본 binlog와 백업을 보존하고, BINLOG 문을 유지한 별도 복구 경로에서 시작·종료 트랜잭션, GTID, 대상 서버 상태를 검증한다. 이 글의 명령은 읽기 전용 분석용이며 복구 절차를 검증한 명령이 아니다.
8.2 행 이벤트 중간에서 잘라 읽지 않는다
row event만 추출하면 필요한 Table_map이나 트랜잭션 시작 문맥을 잃을 수 있다. 시간 조건으로 대략적인 구간을 찾은 뒤 해당 트랜잭션 앞쪽으로 범위를 넓혀 해석한다. 바이트 위치를 쓸 때는 실제 이벤트 경계인지 확인하고, 파일 시작의 Format_description 정보와 필요한 Table_map을 읽을 수 있게 한다.
전체 파일에서 Table_map을 찾았다는 이유만으로 임의의 같은 table id를 모든 구간에 적용하지 않는다. 로그 회전과 매핑의 유효 범위를 고려해야 한다. GTID도 행 하나의 식별자가 아니라 트랜잭션 식별자다.
8.3 적용 오류를 로그 형식 문제로 단정하지 않는다
복제본에서 행을 찾지 못하거나 중복 키 오류가 나면, 원본 트랜잭션의 테이블·키·이미지와 복제본 데이터를 비교한다. 직접 쓰기, 필터 차이, 스키마 불일치, 과거 오류 건너뛰기 등으로 데이터가 이미 달라졌을 수 있다. 오류 이벤트를 건너뛰는 것은 데이터 차이를 고치는 작업이 아니며 같은 트랜잭션의 다른 변경까지 누락시킬 수 있다.
반대로 지연만 늘어나는 경우에는 row event의 크기뿐 아니라 변경 행 수, PK 유무, 보조 인덱스 비용, 잠금 대기, 트랜잭션 크기와 병렬 적용 가능성을 조사한다. binlog_row_image=MINIMAL로 네트워크 바이트가 줄어도 복제본의 행 갱신 작업이 같은 비율로 줄지는 않는다.
9. Aurora MySQL에서는 복제 경로부터 구분한다
Aurora 클러스터 내부의 writer와 reader 동기화를 일반 MySQL의 binlog→relay log 적용 경로와 동일하게 이해하면 안 된다. Aurora는 클러스터 스토리지를 공유하는 구조이며, 이 글의 row event 분석은 주로 외부 MySQL과의 binlog 복제, CDC, binlog 기반 변경 추적에 해당한다.
Aurora에서는 서버 파일시스템에 직접 접근하는 위 Docker 명령을 사용할 수 없다. DB 클러스터 파라미터 그룹의 binlog 설정과 지원되는 보관·원격 읽기 경로를 확인해야 한다. 외부 소비자의 중단 시간보다 binlog 보관 기간이 짧으면 이후에 로그를 읽는 것만으로 공백을 메울 수 없다.
Enhanced binlog는 생성·기록 경로의 성능 특성을 바꾸는 기능이지, MINIMAL 이미지에 기록하지 않은 값을 복원하는 기능이 아니다. 복원·복제한 클러스터에서 과거 enhanced binlog가 제공되지 않는 제약도 있으므로 원본 로그 확보 계획을 별도로 둔다. AWS의 Aurora zero-ETL 구성에는 binlog_row_image=FULL과 binlog_row_metadata=FULL 같은 요구 조건이 있다. 일반 복제의 용량 절감 권고를 이러한 소비자에 그대로 적용하지 말고, 사용 엔진 버전과 통합 서비스 요구 사항을 먼저 확인한다.
10. 운영 점검표
-
@N -
STMT_END_F
정리
RBR 로그는 “Table_map으로 구조를 해석하고, row image로 변경을 읽으며, 트랜잭션 경계로 적용 단위를 묶는” 순서로 접근하면 이해하기 쉽다. 전체 이미지는 원래 SQL이 아니고, 최소 이미지는 전체 행이 아니다. 이 구분이 정확해야 CDC 데이터 누락을 오해하지 않고 복제 오류와 복구 범위를 판단할 수 있다. 다음 단계에서는 이 이벤트 흐름이 relay log와 병렬 적용 경로를 통과하면서 어떤 대기와 병목을 만드는지 연결해 살펴볼 수 있다.