---
title: "binlog_row_image 설계: FULL과 MINIMAL의 장단점"
description: "MySQL row event의 before/after image 구성과 FULL·MINIMAL 선택이 복제, CDC, 로그 용량, 장애 대응에 미치는 영향을 정리한다."
tags: [ MySQL, 복제, 운영, DBA ]
image: "mysql-report-bg.png"
published: "2026-09-03"
updated: "2026-09-03"
author: "MySQL 기술 노트"
source_url: ""
---

# binlog_row_image 설계: FULL과 MINIMAL의 장단점

`binlog_row_image`는 row-based binary logging에서 각 변경 행의 어느 열을 binary log에 기록할지 결정한다. `FULL`은 행의 전체 상태를 넓게 보존하고, `MINIMAL`은 행 식별과 변경 적용에 필요한 열만 기록한다. 전자는 CDC 호환성과 사후 분석이 단순하지만 로그가 커질 수 있고, 후자는 저장 공간과 전송량을 줄일 수 있지만 소비자가 완전한 행을 기대하거나 source와 replica의 스키마가 다르면 위험해질 수 있다.

따라서 이 변수는 단순한 용량 최적화 스위치가 아니다. **source, replica, CDC, 감사·복구 도구 사이에서 변경 데이터를 어떤 형태로 전달할지 정하는 인터페이스 계약**이다. 값 변경은 binary log 크기뿐 아니라 이벤트 해석 가능성, replica의 행 탐색, downstream 데이터 모델, 장애 조사 범위까지 함께 검토해야 한다.

## 1. Row event와 행 이미지의 구조

`binlog_format=ROW`에서 DML은 원래 SQL 문자열보다 실제로 변경된 행을 중심으로 기록된다. 행 이미지의 역할은 DML 종류마다 다르다.

- `INSERT`: 기존 행이 없으므로 after image만 필요하다.
- `DELETE`: 삭제 뒤의 행이 없으므로 before image만 필요하다.
- `UPDATE`: 기존 행을 찾는 before image와 적용할 값을 담는 after image가 모두 필요하다.

```mermaid
flowchart LR
    A[애플리케이션 DML] --> B{DML 종류}
    B -->|INSERT| C[after image]
    B -->|DELETE| D[before image]
    B -->|UPDATE| E[before image]
    B -->|UPDATE| F[after image]
    C --> G[Binary log row event]
    D --> G
    E --> G
    F --> G
    G --> H[Replica applier]
    G --> I[CDC consumer]
    G --> J[mysqlbinlog 분석]
```

before image는 replica가 갱신·삭제할 기존 행을 식별하는 자료다. after image는 삽입할 값 또는 갱신할 값을 전달한다. `binlog_row_image`는 이 두 이미지의 열 구성을 조절하지만, 트랜잭션 경계나 row event 자체를 없애지는 않는다.

이 설정은 row event에만 영향을 준다. `binlog_format=STATEMENT`로 기록되는 이벤트에는 효력이 없고, `MIXED`에서는 실제로 row 형식으로 기록된 변경에만 적용된다. DDL 또한 `ROW` 설정만 보고 행 이미지가 된다고 가정해서는 안 된다.

## 2. FULL, MINIMAL, NOBLOB의 차이

MySQL은 세 가지 값을 제공한다.

| 값 | before image | after image | 주된 성격 |
|---|---|---|---|
| `FULL` | 모든 열 | 모든 열 | 호환성과 관측성 우선 |
| `MINIMAL` | 행 식별에 필요한 최소 열 | SQL이 값을 지정했거나 자동 생성된 열 중심 | 로그 크기 절감 우선 |
| `NOBLOB` | `FULL`에 가깝지만 불필요한 `BLOB`·`TEXT` 제외 | 변경되지 않은 `BLOB`·`TEXT` 제외 | 큰 LOB 열의 비용을 줄이는 절충안 |

기본값은 `FULL`이다. `MINIMAL`의 “최소”는 단순히 primary key 한 열만 의미하지 않는다. MySQL은 다음 순서로 before image의 행 식별 열을 결정한다.

1. primary key가 있으면 primary key 열을 기록한다.
2. primary key가 없고 모든 구성 열이 `NOT NULL`인 unique key가 있으면 해당 키 열을 사용할 수 있다.
3. 둘 다 없으면 행 식별을 위해 모든 열을 기록해야 한다.

따라서 primary key가 없는 테이블에서는 `MINIMAL`을 설정해도 기대만큼 로그가 줄지 않을 수 있다. 더 중요한 문제는 replica가 효율적으로 대상 행을 찾지 못해 적용 비용이 커질 수 있다는 점이다. row image 최적화보다 안정적인 primary key 설계가 먼저다.

## 3. UPDATE 한 건으로 보는 실제 차이

다음과 같은 테이블과 변경을 가정한다.

```text
CREATE TABLE customer_profile (
    customer_id BIGINT PRIMARY KEY,
    email       VARCHAR(255) NOT NULL,
    status      VARCHAR(20)  NOT NULL,
    memo        TEXT,
    updated_at  DATETIME(6)  NOT NULL
);

UPDATE customer_profile
SET status = 'ACTIVE',
    updated_at = CURRENT_TIMESTAMP(6)
WHERE customer_id = 1001;
```

`FULL`에서는 개념적으로 before image와 after image에 `customer_id`, `email`, `status`, `memo`, `updated_at` 전체가 포함된다. 반면 `MINIMAL`에서는 before image에 `customer_id`, after image에 이번 문장에서 지정된 `status`와 `updated_at`을 중심으로 기록한다.

`mysqlbinlog --base64-output=DECODE-ROWS -vv`로 보면 핵심 형태는 다음과 유사하다. 아래 출력은 열 구성을 설명하기 위해 단순화한 예시이며, 실제 파일명·event 위치·메타데이터 표시는 환경에 따라 달라진다.

```text
# FULL의 개념적 형태
### UPDATE `app`.`customer_profile`
### WHERE
###   @1=1001
###   @2='old@example.com'
###   @3='PENDING'
###   @4='...'
###   @5='2026-09-03 08:59:00.000000'
### SET
###   @1=1001
###   @2='old@example.com'
###   @3='ACTIVE'
###   @4='...'
###   @5='2026-09-03 09:00:00.000000'

# MINIMAL의 개념적 형태
### UPDATE `app`.`customer_profile`
### WHERE
###   @1=1001
### SET
###   @3='ACTIVE'
###   @5='2026-09-03 09:00:00.000000'
```

이 차이는 행이 넓고 실제로 바뀌는 열이 적을수록 커진다. 특히 큰 `TEXT`, `BLOB`, `JSON` 열이 있는 테이블에서 작은 상태값만 자주 갱신하면 `FULL`의 before/after image 비용이 두드러질 수 있다. 그러나 절감 효과는 추정만으로 결정하지 말고 대표 트랜잭션의 실제 row event 크기와 일일 binary log 생성량으로 측정해야 한다.

## 4. 현재 설정과 적용 범위 확인

변경 전에는 `binlog_format`, global/session row image, binary logging 활성화 여부를 함께 확인한다.

```sql
SELECT VERSION() AS mysql_version,
       @@GLOBAL.binlog_format AS global_binlog_format,
       @@SESSION.binlog_format AS session_binlog_format,
       @@GLOBAL.binlog_row_image AS global_row_image,
       @@SESSION.binlog_row_image AS session_row_image,
       @@GLOBAL.binlog_row_metadata AS row_metadata,
       @@GLOBAL.log_bin AS binary_logging_enabled;
```

실행 결과(MySQL 8.0.x):

```text
mysql> SELECT VERSION() AS mysql_version,
    ->        @@GLOBAL.binlog_format AS global_binlog_format,
    ->        @@SESSION.binlog_format AS session_binlog_format,
    ->        @@GLOBAL.binlog_row_image AS global_row_image,
    ->        @@SESSION.binlog_row_image AS session_row_image,
    ->        @@GLOBAL.binlog_row_metadata AS row_metadata,
    ->        @@GLOBAL.log_bin AS binary_logging_enabled;

+---------------+----------------------+-----------------------+------------------+-------------------+--------------+------------------------+
| mysql_version | global_binlog_format | session_binlog_format | global_row_image | session_row_image | row_metadata | binary_logging_enabled |
+---------------+----------------------+-----------------------+------------------+-------------------+--------------+------------------------+
| 8.0.46        | ROW                  | ROW                   | FULL             | FULL              | MINIMAL      |                      0 |
+---------------+----------------------+-----------------------+------------------+-------------------+--------------+------------------------+
1 row in set (0.00 sec)
```

`log_bin=OFF`라면 변수 값은 조회되더라도 실제 binary log 파일은 생성되지 않는다. 또한 global 값은 새 연결의 기본값이고, 이미 열린 세션은 다른 session 값을 유지할 수 있다. connection pool을 사용하는 환경에서는 global 값 한 번의 조회만으로 모든 트랜잭션의 실제 기록 방식을 단정해서는 안 된다.

`binlog_row_image`는 global 및 session 범위에서 동적으로 변경할 수 있다. 다음 예제는 현재 연결에만 적용되는 동작을 확인한 뒤 원래 기본 방향인 `FULL`로 되돌린다.

```sql
SET SESSION binlog_row_image = 'MINIMAL';
SELECT @@SESSION.binlog_row_image AS session_row_image_after_change;

SET SESSION binlog_row_image = 'FULL';
SELECT @@SESSION.binlog_row_image AS session_row_image_after_restore;
```

실행 결과(MySQL 8.0.x):

```text
mysql> SET SESSION binlog_row_image = 'MINIMAL';

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@SESSION.binlog_row_image AS session_row_image_after_change;

+--------------------------------+
| session_row_image_after_change |
+--------------------------------+
| MINIMAL                        |
+--------------------------------+
1 row in set (0.00 sec)

mysql> SET SESSION binlog_row_image = 'FULL';

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@SESSION.binlog_row_image AS session_row_image_after_restore;

+---------------------------------+
| session_row_image_after_restore |
+---------------------------------+
| FULL                            |
+---------------------------------+
1 row in set (0.00 sec)
```

동적 변경이 가능하다는 사실과 운영 중 임의 변경이 안전하다는 판단은 다르다. session별 값이 혼재하면 같은 테이블의 이벤트도 연결에 따라 다른 image 정책으로 기록될 수 있다. 변경은 parameter/configuration을 source of truth로 관리하고, 기존 connection pool 교체와 재접속 검증까지 포함해야 한다.

## 5. FULL을 선택할 때 얻는 것

### 5.1 CDC 소비자와의 계약이 단순하다

`FULL`은 update 전후의 전체 행을 제공하므로 CDC가 완전한 before/after 레코드를 구성하기 쉽다. 필터링, 라우팅, 감사, downstream의 파생 계산이 변경되지 않은 열을 참조하더라도 source row event 자체에 값이 들어 있다.

반대로 `MINIMAL`에서는 변경되지 않은 열이 after image에 없을 수 있다. CDC 도구가 이전 상태 저장소나 schema history를 이용해 행을 보강할 수 있는지, 누락 열을 `NULL`과 구분하는지, delete의 key를 어떻게 구성하는지 확인해야 한다. 제품이 `MINIMAL`을 읽을 수 있다는 것과 현재 파이프라인의 모든 변환이 올바르게 동작한다는 것은 별개다.

AWS DMS처럼 source 설정으로 `binlog_row_image=FULL`을 요구하는 서비스가 있다. 이런 소비자가 하나라도 연결되어 있다면 로그 절감만을 이유로 `MINIMAL`을 선택해서는 안 된다.

### 5.2 사후 분석과 감사 자료가 풍부하다

`FULL` row event는 변경 전후의 전체 열을 더 많이 남기므로 `mysqlbinlog -vv`를 통한 장애 조사와 데이터 변경 분석에 유리하다. 어떤 값이 바뀌지 않았는지까지 사건 시점의 이미지에서 확인할 수 있다.

다만 binary log를 완전한 감사 시스템으로 간주해서는 안 된다. 보존 기간, 접근 권한, DDL/schema history, event 해석 도구, 암호화와 무결성 관리가 함께 있어야 한다. `FULL`이라는 설정 하나가 사용자 식별과 업무 맥락까지 기록해 주지는 않는다.

### 5.3 이기종·비대칭 스키마 위험을 줄이는 방향이다

전통적인 source-replica 구조에서도 DDL 배포 시점 차이, 열 순서·타입 차이, key 정의 불일치는 위험하다. 특히 `MINIMAL` 또는 `NOBLOB`을 사용할 때 source와 destination은 열의 존재·순서·데이터 타입, primary key 정의가 일치해야 안전한 적용을 보장하기 쉽다.

`FULL`도 임의의 스키마 차이를 정당화하지는 않는다. 다만 더 풍부한 row image는 일부 도구의 검증과 문제 분석에 유리하다. 복제 토폴로지에서 의도적으로 서로 다른 스키마를 운영한다면 실제 버전과 변환 규칙을 별도로 검증해야 한다.

## 6. FULL의 비용

`FULL`의 비용은 행 수뿐 아니라 행 폭과 변경 패턴에 따라 결정된다.

- 넓은 행을 소수 열만 갱신해도 전체 before/after image가 기록된다.
- 큰 트랜잭션은 source의 binlog cache와 임시 파일 사용을 늘릴 수 있다.
- binary log 파일 생성 속도와 보존 공간 요구가 커진다.
- replica 전송량과 relay log 쓰기량이 증가한다.
- CDC connector와 downstream queue가 처리해야 할 payload가 커진다.
- 큰 `TEXT`, `BLOB`, `JSON` 값이 반복 포함되면 효과가 특히 커질 수 있다.

여기서 “로그가 두 배가 된다”처럼 고정 배수를 가정하면 안 된다. event header, table map, 트랜잭션 크기, 압축, 실제 변경 열, key 구성, 데이터 타입에 따라 비율이 달라진다. 최소한 다음 지표를 변경 전후 동일한 업무 구간에서 비교해야 한다.

- 시간당·일일 binary log bytes
- 트랜잭션당 평균 및 상위 백분위 event 크기
- `Binlog_cache_use`와 `Binlog_cache_disk_use` 증가량
- replica network throughput과 relay log 증가량
- replica apply lag와 CDC source lag
- 보존 정책을 포함한 최대 필요 디스크 공간

## 7. MINIMAL을 선택할 때 얻는 것과 잃는 것

### 7.1 기대할 수 있는 이점

`MINIMAL`은 안정적인 primary key가 있고, 행이 넓으며, update가 일부 열에 집중되는 환경에서 효과가 크다. binary log 쓰기, 네트워크 전송, relay log, CDC 입력 payload를 줄일 가능성이 있다. 큰 변경량 때문에 replica나 CDC가 I/O 대역폭에 묶이는 환경에서는 실질적인 개선점이 될 수 있다.

### 7.2 감수해야 할 제약

- CDC event의 after image가 완전한 최신 행이 아닐 수 있다.
- 변경되지 않은 열을 필요로 하는 필터·감사·파생 로직이 깨질 수 있다.
- primary key가 없는 테이블에서는 before image가 여전히 넓고 replica 적용도 비효율적일 수 있다.
- source와 destination의 열 순서·타입·primary key 정의 차이가 더 위험해질 수 있다.
- 이미 `MINIMAL`로 기록된 과거 event에 없는 열은 나중에 `FULL`로 되돌려도 복원되지 않는다.
- 일부 관리형 CDC·zero-ETL·검색 수집 서비스는 명시적으로 `FULL`을 요구한다.

결론적으로 `MINIMAL`은 “안전한 기본값의 경량 버전”이 아니라 **소비자와 스키마 조건을 증명했을 때 선택하는 최적화**다.

## 8. Primary Key 준비 상태 점검

다음 재현 예제는 primary key, `UNIQUE NOT NULL`, nullable unique key만 가진 세 테이블을 만들고, `MINIMAL` before image의 행 식별 기반을 분류한다. 운영 전체 스키마를 점검할 때는 `TABLE_SCHEMA` 필터를 실제 데이터베이스로 바꾸고 시스템 스키마를 제외한다.

```sql
DROP TABLE IF EXISTS row_image_demo_pk;
DROP TABLE IF EXISTS row_image_demo_unique;
DROP TABLE IF EXISTS row_image_demo_nullable;

CREATE TABLE row_image_demo_pk (
    id BIGINT NOT NULL PRIMARY KEY,
    payload VARCHAR(100) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE row_image_demo_unique (
    id BIGINT NOT NULL,
    business_key VARCHAR(40) NOT NULL,
    payload VARCHAR(100) NOT NULL,
    UNIQUE KEY uk_business_key (business_key)
) ENGINE=InnoDB;

CREATE TABLE row_image_demo_nullable (
    id BIGINT NOT NULL,
    optional_key VARCHAR(40) NULL,
    payload VARCHAR(100) NOT NULL,
    UNIQUE KEY uk_optional_key (optional_key)
) ENGINE=InnoDB;

WITH index_quality AS (
    SELECT s.TABLE_SCHEMA,
           s.TABLE_NAME,
           s.INDEX_NAME,
           s.NON_UNIQUE,
           MIN(c.IS_NULLABLE = 'NO') AS all_columns_not_null
    FROM information_schema.STATISTICS AS s
    JOIN information_schema.COLUMNS AS c
      ON c.TABLE_SCHEMA = s.TABLE_SCHEMA
     AND c.TABLE_NAME = s.TABLE_NAME
     AND c.COLUMN_NAME = s.COLUMN_NAME
    WHERE s.TABLE_SCHEMA = DATABASE()
      AND s.TABLE_NAME LIKE 'row_image_demo_%'
    GROUP BY s.TABLE_SCHEMA, s.TABLE_NAME, s.INDEX_NAME, s.NON_UNIQUE
), table_quality AS (
    SELECT TABLE_SCHEMA,
           TABLE_NAME,
           MAX(INDEX_NAME = 'PRIMARY') AS has_primary_key,
           MAX(NON_UNIQUE = 0 AND all_columns_not_null = 1) AS has_nonnull_unique_key
    FROM index_quality
    GROUP BY TABLE_SCHEMA, TABLE_NAME
)
SELECT t.TABLE_NAME,
       CASE
           WHEN COALESCE(q.has_primary_key, 0) = 1 THEN 'PRIMARY KEY'
           WHEN COALESCE(q.has_nonnull_unique_key, 0) = 1 THEN 'UNIQUE NOT NULL'
           ELSE 'ALL COLUMNS'
       END AS minimal_before_image_basis
FROM information_schema.TABLES AS t
LEFT JOIN table_quality AS q
  ON q.TABLE_SCHEMA = t.TABLE_SCHEMA
 AND q.TABLE_NAME = t.TABLE_NAME
WHERE t.TABLE_SCHEMA = DATABASE()
  AND t.TABLE_NAME LIKE 'row_image_demo_%'
ORDER BY t.TABLE_NAME;

DROP TABLE row_image_demo_pk;
DROP TABLE row_image_demo_unique;
DROP TABLE row_image_demo_nullable;
```

실행 결과(MySQL 8.0.x):

아래는 준비 DDL과 핵심 조회 결과를 발췌한 것이다. 반복되는 정리 DDL의 성공 메시지는 생략했다.

```text
mysql> CREATE TABLE row_image_demo_pk (...);
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE row_image_demo_unique (...);
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE row_image_demo_nullable (...);
Query OK, 0 rows affected (0.01 sec)

mysql> WITH index_quality AS (...), table_quality AS (...)
    -> SELECT t.TABLE_NAME,
    ->        CASE ... END AS minimal_before_image_basis
    -> FROM information_schema.TABLES AS t
    -> LEFT JOIN table_quality AS q ...
    -> ORDER BY t.TABLE_NAME;

+-------------------------+----------------------------+
| TABLE_NAME              | minimal_before_image_basis |
+-------------------------+----------------------------+
| row_image_demo_nullable | ALL COLUMNS                |
| row_image_demo_pk       | PRIMARY KEY                |
| row_image_demo_unique   | UNIQUE NOT NULL            |
+-------------------------+----------------------------+
3 rows in set (0.01 sec)
```

nullable unique key는 여러 `NULL`을 허용할 수 있으므로 안정적인 단일 행 식별 기준으로 취급할 수 없다. 실제 운영 점검에서는 단순히 unique index 개수만 세지 말고 모든 key column의 null 허용 여부와 source/replica 정의의 일치 여부를 확인해야 한다.

## 9. NOBLOB과 부분 JSON 업데이트를 혼동하지 않는다

`NOBLOB`은 전체 열 중심의 관측성을 유지하면서 필요하지 않은 `BLOB`·`TEXT` 계열을 제외하는 절충안이다. 그러나 다음 한계가 있다.

- 큰 열이 `JSON`, 긴 `VARCHAR`, binary 계열 등 다른 타입이면 기대한 절감이 없을 수 있다.
- 변경된 `BLOB`·`TEXT` 값이나 행 식별에 필요한 값은 여전히 기록된다.
- CDC가 변경 전 전체 LOB 값을 요구하면 기능 요구를 충족하지 못할 수 있다.

`binlog_row_value_options=PARTIAL_JSON`도 별도의 기능이다. 이는 JSON의 partial update 표현과 관련되고, row image에서 어느 열을 포함할지 정하는 `binlog_row_image`를 대체하지 않는다. `binlog_row_metadata` 역시 테이블 메타데이터의 양을 조절하는 변수이지 행 값의 before/after 범위를 결정하는 변수가 아니다.

세 변수를 독립된 축으로 구분해야 한다.

| 변수 | 결정 대상 |
|---|---|
| `binlog_row_image` | before/after image에 포함할 열 값 |
| `binlog_row_metadata` | row event에 포함할 테이블 메타데이터 양 |
| `binlog_row_value_options` | 지원되는 값 타입의 부분 변경 표현 방식 |

## 10. 실제 binary log 검증 절차

운영 변경 전 staging에서 대표 `INSERT`, `UPDATE`, `DELETE`를 실행하고 row event를 직접 비교한다. 파일 경로와 시간 범위는 대상 환경에 맞게 조정한다.

```text
mysqlbinlog \
  --start-datetime="2026-09-03 08:50:00" \
  --stop-datetime="2026-09-03 09:10:00" \
  --base64-output=DECODE-ROWS \
  --verbose --verbose \
  /var/lib/mysql/binlog.000123
```

검증 시 다음을 확인한다.

1. 대상 DML이 실제 row event로 기록되었는가.
2. `UPDATE_ROWS_EVENT`의 before image가 어떤 key column을 포함하는가.
3. after image에 변경된 열만 있는지, 전체 열이 있는지 확인했는가.
4. 큰 LOB/JSON 열이 event 크기에 미치는 영향을 측정했는가.
5. CDC가 update와 delete event를 기대한 schema로 변환하는가.
6. replica가 keyless table에서 지연되지 않는가.
7. DDL 이후 source와 replica의 열 순서·타입·key 정의가 일치하는가.

Binary log에는 업무 데이터가 포함될 수 있다. 검증 파일을 외부로 복사할 때는 개인정보와 비밀값 노출 여부를 확인하고, 운영 파일 접근 권한과 보존·암호화 정책을 지켜야 한다.

## 11. Aurora MySQL에서의 운영 해석

Aurora cluster 내부의 storage replication과 MySQL binary log 기반 논리 복제는 서로 다른 계층이다. Aurora의 고가용성이 storage 계층에서 제공되더라도 외부 CDC, DMS, 다른 MySQL로의 binlog replication에는 row image 계약이 그대로 중요하다.

Aurora에서는 다음 사항을 추가로 확인한다.

- `binlog_row_image`를 DB cluster parameter group에서 어떻게 관리하는지 확인한다.
- parameter 적용 상태와 writer 교체·failover 뒤 값을 재검증한다.
- AWS DMS source는 일반적으로 `FULL`을 요구하므로 임의로 `MINIMAL`로 낮추지 않는다.
- Aurora zero-ETL 또는 OpenSearch Ingestion 같은 기능은 엔진 버전과 구성에 따라 `binlog_row_image=FULL`, `binlog_row_metadata=FULL` 등 명시적 선행 조건을 요구할 수 있다.
- enhanced binlog 사용 여부와 별개로 downstream이 요구하는 row image를 확인한다.
- binary logging 활성화에 따른 writer 부하와 보존 비용을 실제 change rate로 측정한다.

관리형 서비스의 요구 조건은 변경될 수 있으므로, parameter 변경 시점의 대상 서비스 문서를 기준으로 확인해야 한다. MySQL 자체에서 `MINIMAL`이 유효하다는 사실만으로 Aurora 연동 서비스가 이를 지원한다고 추론해서는 안 된다.

## 12. 대표적인 실패 모드

### 실패 모드 1: 로그는 줄었지만 CDC 필드가 사라진다

원인은 `MINIMAL` after image에 변경되지 않은 열이 없는데 downstream이 완전한 행으로 해석한 경우가 많다. connector 지원 여부, event envelope, stateful reconstruction, delete key 처리, schema history를 확인한다. 소비자가 `FULL`을 요구한다면 source 설정을 되돌리고 누락 기간의 재처리 범위를 결정해야 한다.

### 실패 모드 2: MINIMAL로 바꿨는데 용량이 거의 줄지 않는다

primary key나 non-null unique key가 없는 테이블, 전체 열을 갱신하는 SQL, 변경되는 LOB 값, 소수의 좁은 테이블이 원인일 수 있다. 테이블별 change volume과 row width를 분리해 측정한다. 설정값만 보고 절감 효과를 가정하지 않는다.

### 실패 모드 3: replica 적용이 느리거나 불일치한다

keyless table, source/replica의 key 정의 불일치, 열 순서·타입 차이, 장기 DDL 불일치 구간을 확인한다. `MINIMAL` 또는 `NOBLOB` 환경에서는 스키마가 동일하다는 전제가 특히 중요하다. 설정을 `FULL`로 되돌리는 조치만으로 이미 발생한 데이터 불일치는 복구되지 않으므로 checksum과 재동기화가 필요하다.

### 실패 모드 4: global 값과 실제 event가 다르다

기존 세션, connection pool 초기화 SQL, session override, `MIXED`에서 statement로 기록된 문장을 확인한다. global 변수는 모든 기존 연결의 현재 값을 대표하지 않는다.

### 실패 모드 5: FULL 전환 후 보존 공간이 급격히 줄어든다

대량 update/delete, 넓은 행, 긴 보존 기간, CDC 지연, replica 장애가 함께 작용할 수 있다. binary log 생성률과 purge 가능 시점을 기준으로 용량을 다시 산정하고, 큰 트랜잭션 분할과 downstream 처리량 확장을 검토한다. 저장 공간 문제를 숨기기 위해 검증 없이 `MINIMAL`로 되돌리는 것은 적절하지 않다.

## 13. 선택 기준

다음 조건이면 `FULL`이 안전한 기본 선택이다.

- CDC 또는 관리형 연동 서비스가 `FULL`을 요구한다.
- update 전후의 완전한 행이 감사·분석에 필요하다.
- 여러 downstream 소비자의 요구를 단순한 공통 계약으로 유지해야 한다.
- source와 replica의 스키마 차이 가능성을 완전히 제거하지 못했다.
- 로그 크기보다 복제 정확성과 조사 가능성이 우선한다.

다음 조건을 모두 검증했다면 `MINIMAL`을 고려할 수 있다.

- 모든 고변경 테이블에 안정적인 primary key 또는 적합한 non-null unique key가 있다.
- source와 replica의 열 순서·타입·primary key 정의가 일치한다.
- 모든 CDC 소비자가 sparse before/after image를 올바르게 처리한다.
- 완전한 before/after 행을 요구하는 감사·복구 절차가 없다.
- staging의 실제 row event와 downstream 결과를 확인했다.
- 측정 결과 binary log·네트워크·relay log 절감 효과가 유의미하다.
- 즉시 `FULL`로 되돌릴 수 있는 변경·모니터링 계획이 있다.

큰 `BLOB`·`TEXT`가 주된 비용이고 소비자 요구가 허용한다면 `NOBLOB`을 별도 후보로 검증할 수 있다. 이름만 보고 `FULL`과 동일하거나 `MINIMAL`보다 안전하다고 단정하지 말고 실제 event를 확인해야 한다.

## 14. 변경 전후 체크리스트

### 변경 전

- [ ] `binlog_format=ROW` 또는 `MIXED`에서 실제 row event 대상임을 확인했다.
- [ ] global 값과 주요 application session 값을 각각 확인했다.
- [ ] replica, CDC, DMS, zero-ETL 등 모든 소비자의 요구값을 확인했다.
- [ ] 고변경 테이블의 primary key와 non-null unique key를 점검했다.
- [ ] source와 replica의 열 순서·타입·primary key 정의를 비교했다.
- [ ] 대표 트랜잭션의 실제 row event를 `mysqlbinlog -vv`로 확인했다.
- [ ] 일일 binary log 생성량, 보존 여유, replica·CDC 지연을 기준선으로 남겼다.
- [ ] 변경되지 않은 열이 누락되어도 downstream 로직이 정확히 동작하는지 검증했다.
- [ ] parameter group/configuration과 connection pool 교체 절차를 준비했다.
- [ ] rollback 기준과 누락 기간 재처리 방안을 문서화했다.

### 변경 후

- [ ] 신규 세션의 `@@SESSION.binlog_row_image`가 의도한 값이다.
- [ ] 실제 `WRITE_ROWS`, `UPDATE_ROWS`, `DELETE_ROWS` event 열 구성을 확인했다.
- [ ] CDC update/delete event의 key와 필드가 기대와 일치한다.
- [ ] replica apply lag와 오류 로그가 허용 범위다.
- [ ] binary log 및 relay log 생성률이 예상 방향으로 변했다.
- [ ] `Binlog_cache_disk_use` 증가 추세와 임시 디스크 사용을 점검했다.
- [ ] failover 또는 재시작 뒤 parameter가 유지된다.
- [ ] schema migration 이후에도 source/replica 정의가 일치한다.
- [ ] 설정 변경 시점 이전 event의 정보 범위는 바뀌지 않음을 운영 기록에 남겼다.

## 15. 결론

`binlog_row_image`의 핵심은 로그 크기와 변경 정보의 완전성 사이의 선택이다. `FULL`은 더 많은 데이터를 기록하는 대신 CDC 호환성, 사후 분석, 운영 계약을 단순하게 만든다. `MINIMAL`은 key와 변경 열 중심으로 event를 줄일 수 있지만, primary key 품질, 동일한 복제 스키마, sparse image를 이해하는 소비자가 전제되어야 한다. `NOBLOB`은 특정 LOB 비용을 줄이는 절충안이지만 별도 검증이 필요하다.

신규 환경의 출발점은 대체로 `FULL`이 안전하다. `MINIMAL`은 binary log가 실제 병목이고, 모든 소비자와 스키마 조건을 검증했으며, 측정 가능한 절감 효과가 있을 때 적용하는 최적화로 다루는 편이 타당하다. 다음 단계에서는 `binlog_row_metadata`와 schema history가 CDC의 DDL 해석과 열 타입 복원에 어떤 영향을 주는지 함께 살펴볼 수 있다.
