Binlog — 복제와 복구의 핵심 로그
데이터를 실수로 삭제했을 때, 또는 서버를 하나 더 복제하고 싶을 때, MySQL은 어떤 기록을 보고 이전 상태를 재현할까요?
Binlog란
Binary Log(Binlog)는 MySQL 서버에서 발생하는 모든 데이터 변경 이벤트를 순서대로 기록한 로그 파일입니다.
Binlog의 두 가지 핵심 용도:
1. 복제 (Replication): 소스 서버의 변경을 복제본에 전달
2. 시점 복구 (Point-in-Time Recovery): 백업 이후의 변경을 재적용
Binlog는 InnoDB의 Redo Log와 다릅니다. Redo Log는 스토리지 엔진 레벨의 물리적 로그이고, Binlog는 MySQL 서버 레벨의 논리적 로그입니다.
| 구분 | Binlog | Redo Log |
|---|---|---|
| 레벨 | MySQL 서버 | InnoDB 엔진 |
| 기록 대상 | 논리적 변경 (SQL 또는 행 데이터) | 물리적 변경 (페이지 수정) |
| 용도 | 복제, 시점 복구 | 크래시 복구 |
| 순환 | 파일이 계속 생성됨 | 고정 크기 순환 |
Binlog 설정
-- Binlog 활성화 확인 (MySQL 8.0에서는 기본 ON)
SHOW VARIABLES LIKE 'log_bin';
-- Binlog 파일 목록
SHOW BINARY LOGS;
-- 현재 Binlog 파일과 포지션
SHOW MASTER STATUS;
# my.cnf 설정
[mysqld]
log_bin = /var/lib/mysql/mysql-bin
server_id = 1
binlog_expire_logs_seconds = 604800 # 7일 보관
max_binlog_size = 1073741824 # 1GB 단위로 파일 교체
Binlog 포맷 — STATEMENT vs ROW vs MIXED
STATEMENT 포맷
실행된 SQL 문장을 그대로 기록합니다.
SET GLOBAL binlog_format = 'STATEMENT';
# Binlog 내용 예시
UPDATE orders SET status = 'shipped' WHERE id = 100;
장점:
- 로그 크기가 작습니다 (SQL 한 줄)
- 사람이 읽기 쉽습니다
단점:
- 비결정적 함수 문제:
NOW(),UUID(),RAND()등이 소스와 복제본에서 다른 값을 반환할 수 있습니다 LIMIT없는UPDATE/DELETE에서 행 순서가 달라질 수 있습니다
-- 위험한 예시
INSERT INTO logs (id, created_at) VALUES (1, NOW());
-- 소스와 복제본에서 NOW()의 결과가 다를 수 있음
ROW 포맷 (권장)
변경된 행의 **이전 값(before image)**과 **이후 값(after image)**을 기록합니다.
SET GLOBAL binlog_format = 'ROW';
# Binlog 내용 예시 (바이너리 → mysqlbinlog로 디코딩)
### UPDATE orders
### WHERE
### @1=100 -- id (before)
### @2='pending' -- status (before)
### SET
### @1=100 -- id (after)
### @2='shipped' -- status (after)
장점:
- 비결정적 함수 문제가 없습니다 (결과 값을 기록)
- 데이터 일관성이 보장됩니다
- MySQL 8.0 기본 포맷
단점:
- 로그 크기가 클 수 있습니다 (대량 UPDATE 시)
- 사람이 바로 읽기 어렵습니다
binlog_row_image 옵션
ROW 포맷에서 기록되는 데이터 양을 조절합니다.
SHOW VARIABLES LIKE 'binlog_row_image';
| 값 | 기록 내용 |
|---|---|
| full (기본) | 모든 컬럼의 전후 값 |
| minimal | 변경에 필요한 최소 컬럼만 |
| noblob | BLOB/TEXT 컬럼을 변경하지 않았으면 제외 |
minimal을 사용하면 Binlog 크기를 크게 줄일 수 있지만, 시점 복구 시 제약이 있을 수 있습니다.
MIXED 포맷
기본적으로 STATEMENT를 사용하고, 비결정적 함수가 포함된 쿼리만 ROW로 전환합니다.
SET GLOBAL binlog_format = 'MIXED';
실무에서는 ROW 포맷이 가장 안전하고 널리 사용됩니다.
Binlog 포지션
Binlog에서 특정 이벤트의 위치를 식별하는 방법입니다.
-- 현재 포지션 확인
SHOW MASTER STATUS;
-- File: mysql-bin.000003, Position: 1234
Binlog 파일 구조:
mysql-bin.000001 ← 파일명
├─ Position 4: Format Description Event
├─ Position 124: GTID Event
├─ Position 200: Query Event (BEGIN)
├─ Position 350: Table Map Event
├─ Position 400: Write Rows Event (INSERT)
├─ Position 600: Xid Event (COMMIT)
└─ ...
복제에서는 "어느 파일의 어느 포지션까지 적용했는지"를 추적합니다. 하지만 이 방식은 서버 토폴로지가 변경되면 관리가 복잡해집니다.
GTID — 글로벌 트랜잭션 ID
GTID란
MySQL 5.6에서 도입된 GTID는 각 트랜잭션에 전역적으로 고유한 ID를 부여합니다.
GTID 형식: server_uuid:transaction_id
예: 3E11FA47-71CA-11E1-9E33-C80AA9429562:42
-- GTID 활성화
SHOW VARIABLES LIKE 'gtid_mode'; -- ON
SHOW VARIABLES LIKE 'enforce_gtid_consistency'; -- ON
GTID의 장점
- 포지션 관리 불필요: 파일명과 포지션 대신 GTID로 복제 상태를 추적합니다
- 토폴로지 변경 용이: 마스터 전환(Failover) 시 복제본이 자동으로 올바른 위치를 찾습니다
- 복제 갭 감지: 어떤 트랜잭션이 누락되었는지 GTID 셋으로 확인할 수 있습니다
-- 현재 서버에서 실행된 GTID 셋
SHOW VARIABLES LIKE 'gtid_executed';
-- 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-1000
-- 복제본에서 적용된 GTID 셋
SELECT @@GLOBAL.gtid_executed;
GTID 기반 복제 설정
-- 복제본에서
CHANGE REPLICATION SOURCE TO
SOURCE_HOST = '10.0.1.1',
SOURCE_USER = 'repl_user',
SOURCE_PASSWORD = 'password',
SOURCE_AUTO_POSITION = 1; -- GTID 사용
START REPLICA;
SOURCE_AUTO_POSITION = 1을 사용하면 파일명/포지션을 지정하지 않아도 GTID를 기반으로 자동으로 올바른 위치를 찾습니다.
mysqlbinlog 도구
Binlog 파일을 읽고, 분석하고, 재실행할 수 있는 명령줄 도구입니다.
기본 사용법
# Binlog 내용 출력
mysqlbinlog mysql-bin.000001
# ROW 포맷을 읽기 좋게 디코딩
mysqlbinlog --verbose mysql-bin.000001
# 특정 시간 범위만 추출
mysqlbinlog --start-datetime="2026-03-19 10:00:00" \
--stop-datetime="2026-03-19 11:00:00" \
mysql-bin.000001
# 특정 포지션 범위만 추출
mysqlbinlog --start-position=1234 \
--stop-position=5678 \
mysql-bin.000001
# 특정 데이터베이스만 필터
mysqlbinlog --database=myapp mysql-bin.000001
시점 복구 (Point-in-Time Recovery)
실수로 데이터를 삭제한 경우의 복구 과정입니다.
# 1. 전체 백업을 먼저 복원
mysql < full_backup.sql
# 2. 백업 이후부터 실수 직전까지의 Binlog를 적용
mysqlbinlog --start-position=백업_포지션 \
--stop-datetime="2026-03-19 14:59:59" \
mysql-bin.000005 | mysql
# 3. 실수 쿼리 이후부터 현재까지의 Binlog를 적용
mysqlbinlog --start-datetime="2026-03-19 15:00:01" \
mysql-bin.000005 | mysql
Binlog 관리
파일 보관 기간
-- 보관 기간 설정 (초 단위, 기본 2592000 = 30일)
SET GLOBAL binlog_expire_logs_seconds = 604800; -- 7일
-- 수동 삭제
PURGE BINARY LOGS TO 'mysql-bin.000005';
PURGE BINARY LOGS BEFORE '2026-03-10 00:00:00';
파일 크기 제한
-- 파일당 최대 크기 (기본 1GB)
SHOW VARIABLES LIKE 'max_binlog_size';
-- 수동으로 새 파일로 교체
FLUSH BINARY LOGS;
sync_binlog 설정
-- 트랜잭션 커밋 시 Binlog를 디스크에 동기화하는 빈도
SHOW VARIABLES LIKE 'sync_binlog';
| 값 | 동작 | 성능 | 안전성 |
|---|---|---|---|
| 0 | OS에 위임 | 빠름 | 낮음 |
| 1 (권장) | 매 커밋마다 fsync | 느림 | 높음 |
| N | N번 커밋마다 fsync | 중간 | 중간 |
sync_binlog = 1과 innodb_flush_log_at_trx_commit = 1을 함께 설정하면 가장 안전합니다.
정리
- Binlog는 MySQL의 모든 데이터 변경을 기록하며, 복제와 시점 복구에 필수입니다
- ROW 포맷이 가장 안전하며 MySQL 8.0의 기본 포맷입니다
- GTID를 사용하면 파일/포지션 관리가 불필요해지고 복제 토폴로지 변경이 쉬워집니다
- mysqlbinlog 도구로 Binlog를 분석하고 시점 복구를 수행할 수 있습니다