---
title: "Doublewrite Buffer의 필요성: torn page 방지와 스토리지 신뢰성"
description: "InnoDB Doublewrite Buffer가 torn page를 방지하는 원리와 스토리지 신뢰성, 운영 진단 기준, Aurora MySQL 차이를 정리한다."
tags: [ MySQL, InnoDB, 운영, DBA ]
image: "mysql-report-bg.png"
published: "2026-06-11"
updated: "2026-06-11"
author: "MySQL 기술 노트"
source_url: ""
---

## 1. 왜 Doublewrite Buffer를 이해해야 하는가

InnoDB는 트랜잭션을 안전하게 처리하기 위해 redo log, undo log, buffer pool, checkpoint, page flush가 서로 맞물려 동작한다. 이 구조에서 운영자가 자주 놓치는 지점이 있다. redo log가 있으면 장애 복구가 충분할 것처럼 보이지만, 디스크에 기록된 데이터 페이지 자체가 절반만 쓰인 상태로 남으면 redo log만으로 복구가 어려워질 수 있다. 이때 문제가 되는 현상이 `torn page`이며, InnoDB가 이를 방지하기 위해 사용하는 대표적인 장치가 `Doublewrite Buffer`다.

`torn page`는 하나의 InnoDB page가 스토리지에 원자적으로 기록되지 못하고 일부만 새 내용으로, 나머지는 이전 내용으로 남는 상태를 말한다. InnoDB의 기본 page 크기는 보통 16KiB이지만, 운영체제와 스토리지는 더 작은 단위로 쓰기를 처리할 수 있다. 전원 장애, 커널 패닉, 스토리지 컨트롤러 장애, 가상화 계층의 쓰기 중단이 page flush 중간에 발생하면 하나의 page 안에 서로 다른 시점의 조각이 섞일 수 있다.

Doublewrite Buffer는 성능을 약간 희생하더라도 장애 복구 가능성을 높이는 안전장치다. 특히 범용 SSD, 클라우드 블록 스토리지, RAID 컨트롤러, 가상 디스크처럼 실제 원자 쓰기 보장이 애플리케이션에서 직접 확인하기 어려운 환경에서는 기본값을 함부로 끄지 않는 것이 일반적인 운영 원칙이다.

## 2. Torn page가 redo log만으로 해결되지 않는 이유

InnoDB redo log는 page에 적용할 변경 내용을 물리적 또는 생리적 로그 형태로 남긴다. 장애 복구 시 InnoDB는 checkpoint 이후의 redo log를 읽고, 데이터 파일의 page에 변경을 다시 적용한다. 이 설명만 보면 page가 조금 망가져도 redo log를 재적용하면 될 것처럼 보인다. 그러나 redo log 적용에는 전제가 있다. 복구 대상 page가 적어도 InnoDB page로 해석 가능한 일관된 구조를 유지해야 한다는 점이다.

InnoDB page에는 page header, record 영역, page directory, checksum, LSN 같은 내부 메타데이터가 들어 있다. torn page가 발생하면 다음과 같은 문제가 생길 수 있다.

- page header는 새 버전인데 record 영역 일부는 이전 버전으로 남는다.
- page checksum이 맞지 않아 page를 신뢰할 수 없다.
- page LSN이 실제 record 내용과 맞지 않는다.
- B-tree page의 record directory가 깨져 page traversal 자체가 위험해진다.
- redo log를 어느 위치부터 적용해야 하는지 판단하기 어려워진다.

즉 redo log는 “정상적인 page 이미지에 변경을 재적용하는 장치”이지, 임의로 절반만 써진 page를 항상 재구성하는 만능 복구 장치가 아니다. Doublewrite Buffer는 page를 데이터 파일의 원래 위치에 쓰기 전에 별도의 안전한 위치에 한 번 더 기록하여, 장애 후 원래 page가 찢어진 상태이면 안전한 사본으로 대체할 수 있게 한다.

## 3. Doublewrite Buffer의 기본 동작 경로

InnoDB가 dirty page를 디스크로 내보낼 때의 단순화된 흐름은 다음과 같다.

```mermaid
flowchart TD
    A[Buffer Pool의 dirty page] --> B[Flush 대상 page 선정]
    B --> C[Doublewrite 영역에 page 묶음 기록]
    C --> D[doublewrite write 완료 보장]
    D --> E[데이터 파일의 원래 page 위치에 기록]
    E --> F[Checkpoint 진행 가능]
    G[장애 발생] --> H{원래 page checksum/LSN 정상?}
    H -- 정상 --> I[redo log 적용]
    H -- torn page 의심 --> J[Doublewrite 사본으로 page 복구]
    J --> I
```

핵심은 “두 번 쓴다”는 이름 그대로 page를 먼저 doublewrite 영역에 기록하고, 그 다음 실제 tablespace 위치에 기록한다는 점이다. 장애가 실제 위치에 쓰는 중간에 발생하더라도 doublewrite 영역에 완전한 page 사본이 남아 있으면 복구 과정에서 이를 이용할 수 있다.

MySQL 8.0에서는 doublewrite 관련 구현이 이전 버전보다 개선되었다. 전통적으로 doublewrite buffer는 system tablespace 내부 영역과 강하게 연결되어 설명되었지만, MySQL 8.0 계열에서는 doublewrite file 기반 구조와 여러 인스턴스, batch 처리 최적화가 포함된다. 운영자가 반드시 내부 파일 배치를 외울 필요는 없지만, 다음 원칙은 유지된다.

1. dirty page는 원래 위치에 쓰이기 전에 doublewrite 영역에 먼저 안전하게 기록된다.
2. 장애 후 page checksum 또는 LSN 불일치가 발견되면 doublewrite 사본이 복구 후보가 된다.
3. doublewrite는 redo log, checksum, checkpoint와 함께 crash recovery 체인을 구성한다.
4. 쓰기량이 많은 워크로드에서는 doublewrite가 추가 I/O로 관찰될 수 있다.

## 4. Doublewrite Buffer와 성능 비용

Doublewrite Buffer는 데이터 page를 두 번 기록하므로 이름만 보면 쓰기 I/O가 단순히 두 배가 될 것처럼 보인다. 실제 운영 영향은 그렇게 단순하지 않다. InnoDB는 page를 묶어서 순차적으로 기록하고, 스토리지 캐시, 운영체제 I/O 스케줄러, SSD 내부 병렬성, checkpoint 정책이 함께 작용한다. 따라서 doublewrite 때문에 모든 쓰기 성능이 절반으로 떨어진다고 단정하면 안 된다.

다만 다음 조건에서는 doublewrite 비용이 더 뚜렷하게 보일 수 있다.

- buffer pool에서 dirty page flush가 지속적으로 높게 발생한다.
- 작은 랜덤 쓰기가 많은 OLTP 워크로드다.
- 스토리지 쓰기 지연 시간이 이미 높거나 IOPS 한계에 근접했다.
- checkpoint age가 커져 aggressive flushing이 자주 발생한다.
- 대량 `UPDATE`, `DELETE`, `ALTER TABLE`, index build가 동시에 수행된다.

반대로 읽기 위주의 시스템이나 충분한 I/O 여유가 있는 시스템에서는 doublewrite 비용이 주요 병목으로 드러나지 않을 수 있다. 성능 문제를 분석할 때는 `innodb_doublewrite` 하나만 보지 말고 redo log fsync, dirty page 비율, checkpoint 진행, storage latency, flush list 길이, foreground thread 대기까지 함께 보아야 한다.

## 5. 설정값과 상태 확인

다음 쿼리는 MySQL 8.0 이상에서 doublewrite 설정과 관련 상태 변수를 확인하는 기본 예제다. 운영 환경에서는 이 값을 단발성으로 보지 말고, 쓰기 부하가 높은 시간대의 증가량과 스토리지 지연 시간을 함께 관찰해야 한다.

```sql
SELECT VERSION() AS mysql_version;

SHOW VARIABLES LIKE 'innodb_doublewrite';

SHOW GLOBAL STATUS LIKE 'Innodb_dblwr%';
```

실행 결과(MySQL 8.0.46):

```text
mysql> SELECT VERSION() AS mysql_version;

+---------------+
| mysql_version |
+---------------+
| 8.0.46        |
+---------------+
1 row in set (0.00 sec)

mysql> SHOW VARIABLES LIKE 'innodb_doublewrite';

+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| innodb_doublewrite | ON    |
+--------------------+-------+
1 row in set (0.00 sec)

mysql> SHOW GLOBAL STATUS LIKE 'Innodb_dblwr%';

+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Innodb_dblwr_pages_written | 53    |
| Innodb_dblwr_writes        | 15    |
+----------------------------+-------+
2 rows in set (0.00 sec)
```

`innodb_doublewrite`는 보통 `ON`으로 두는 것이 안전하다. 일부 배포판이나 버전에서는 `DETECT_ONLY` 같은 값이 제공될 수 있는데, 이는 page corruption 탐지 성격을 강화하되 일반적인 doublewrite 보호와 동일하게 해석해서는 안 된다. 값의 의미는 사용 중인 MySQL minor version 문서를 기준으로 확인해야 한다.

상태 변수는 doublewrite page 수, write 횟수 같은 누적 카운터를 보여준다. 누적값 자체보다 다음 질문이 더 중요하다.

- 쓰기 피크 시간대에 doublewrite write 증가 속도가 급격히 상승하는가?
- 같은 시간대에 redo log write, fsync, checkpoint flush도 함께 증가하는가?
- storage latency 또는 cloud block volume queue depth가 증가하는가?
- 애플리케이션 latency가 doublewrite 증가와 같은 시간에 악화되는가?

## 6. Page checksum과 Doublewrite Buffer의 관계

Doublewrite Buffer는 page 손상을 복구하기 위한 사본을 제공하고, page checksum은 손상 여부를 감지하는 근거를 제공한다. 둘 중 하나만으로 전체 보호가 완성되는 것은 아니다.

```sql
SHOW VARIABLES LIKE 'innodb_checksum_algorithm';

CREATE TABLE doublewrite_note_sample (
  id BIGINT PRIMARY KEY,
  payload VARCHAR(100) NOT NULL,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;

INSERT INTO doublewrite_note_sample (id, payload)
VALUES (1, 'first version');

UPDATE doublewrite_note_sample
SET payload = 'updated version'
WHERE id = 1;

SELECT id, payload
FROM doublewrite_note_sample;

DROP TABLE doublewrite_note_sample;
```

실행 결과(MySQL 8.0.46):

```text
mysql> SHOW VARIABLES LIKE 'innodb_checksum_algorithm';

+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| innodb_checksum_algorithm | crc32 |
+---------------------------+-------+
1 row in set (0.00 sec)

mysql> CREATE TABLE doublewrite_note_sample (
    ->   id BIGINT PRIMARY KEY,
    ->   payload VARCHAR(100) NOT NULL,
    ->   updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    -> ) ENGINE=InnoDB;

Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO doublewrite_note_sample (id, payload)
    -> VALUES (1, 'first version');

Query OK, 1 row affected (0.00 sec)

mysql> UPDATE doublewrite_note_sample
    -> SET payload = 'updated version'
    -> WHERE id = 1;

Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT id, payload
    -> FROM doublewrite_note_sample;

+----+-----------------+
| id | payload         |
+----+-----------------+
|  1 | updated version |
+----+-----------------+
1 row in set (0.00 sec)

mysql> DROP TABLE doublewrite_note_sample;

Query OK, 0 rows affected (0.00 sec)
```

위 예제는 torn page를 직접 재현하는 것이 아니다. 단일 테스트 컨테이너에서 전원 장애와 스토리지 partial write를 안전하게 재현하는 것은 적절하지 않다. 대신 InnoDB 테이블에 변경이 발생하고, dirty page가 만들어지며, 이후 page flush와 redo 기록, checksum 검증 대상이 된다는 흐름을 확인하기 위한 최소 예제다. 실제 torn page 검증은 장애 주입 테스트, 파일 시스템 계층 실험, 백업본 복구 검증처럼 별도의 통제된 환경에서 수행해야 한다.

## 7. Doublewrite Buffer를 끄면 안 되는 경우

운영 현장에서 `innodb_doublewrite=OFF`를 권하는 문맥은 대개 벤치마크나 특수 스토리지 전제를 포함한다. 그러나 그 전제가 실제 서비스 환경에 그대로 적용되는 경우는 제한적이다. 다음 조건 중 하나라도 해당하면 doublewrite를 끄지 않는 것이 안전하다.

- 스토리지의 16KiB page 원자 쓰기 보장을 명확히 검증하지 못했다.
- 클라우드 블록 스토리지, 네트워크 스토리지, 가상 디스크 위에서 동작한다.
- 장애 후 데이터 무결성이 성능보다 중요하다.
- replica나 백업으로 복구할 수 있더라도 데이터 손상 감지와 복구 시간을 최소화해야 한다.
- 운영체제, 파일 시스템, RAID 컨트롤러, 디스크 캐시 설정이 자주 변경된다.
- 성능 테스트가 장애 복구 테스트를 포함하지 않았다.

Doublewrite를 끄는 결정은 “쓰기 성능이 조금 더 필요하다”는 수준으로 내려서는 안 된다. 최소한 다음 검증이 필요하다.

1. 대상 스토리지가 InnoDB page 크기 수준의 atomic write를 보장하는지 공급자 문서와 실험으로 확인한다.
2. 전원 차단 또는 강제 종료 장애 주입 후 MySQL crash recovery와 `CHECK TABLE` 또는 논리 검증을 반복한다.
3. 백업 복구와 replica 승격 절차가 page corruption 상황에서도 운영 목표 시간 안에 동작하는지 확인한다.
4. 장애 시 데이터 손실과 복구 비용을 성능 이득과 비교하여 명시적으로 승인한다.

## 8. Aurora MySQL에서의 해석 차이

Aurora MySQL은 Community MySQL과 스토리지 아키텍처가 다르다. Aurora는 로그 구조 기반의 분산 스토리지 계층을 사용하고, 데이터 복제와 복구 책임의 상당 부분을 데이터베이스 인스턴스 로컬 파일 시스템이 아니라 Aurora storage service가 담당한다. 따라서 doublewrite를 Community MySQL의 로컬 InnoDB tablespace 쓰기 경로와 완전히 같은 방식으로 해석하면 안 된다.

운영 관점의 차이는 다음과 같다.

- Community MySQL에서는 로컬 또는 블록 스토리지에 InnoDB page를 flush하는 경로에서 torn page 위험을 직접 고려한다.
- Aurora MySQL에서는 redo/log record 중심의 분산 스토리지 반영과 quorum 기반 내구성이 핵심이므로, 로컬 doublewrite 비용과 보호 의미가 다르게 나타날 수 있다.
- Aurora에서 성능 병목을 볼 때는 `innodb_doublewrite` 상태보다 storage commit latency, redo 전송, replica lag, Performance Insights의 wait event, CloudWatch 지표를 우선 확인하는 경우가 많다.
- Aurora는 관리형 서비스이므로 일부 InnoDB 내부 파일 배치나 파라미터가 Community MySQL과 동일하게 노출되지 않거나 의미가 다를 수 있다.

따라서 Aurora MySQL 운영자는 “doublewrite를 켜야 하는가”보다 “Aurora storage 계층이 어떤 내구성 모델을 제공하며, 장애 복구와 백업 검증을 어떻게 수행할 것인가”를 중심으로 판단해야 한다. 반대로 EC2 위에 직접 설치한 MySQL이나 온프레미스 MySQL은 Community MySQL의 doublewrite 원칙을 그대로 적용하는 것이 일반적이다.

## 9. 장애 모드와 흔한 오해

### 9.1 “배터리 백업 RAID가 있으므로 doublewrite가 필요 없다”는 주장

배터리 백업 캐시는 전원 장애 시 캐시 내용을 보호할 수 있지만, 모든 partial write 위험을 자동으로 제거한다고 단정할 수 없다. RAID 펌웨어, 디스크, 파일 시스템, 커널, 가상화 계층까지 전체 경로가 page 원자성을 보장해야 한다. 특정 계층 하나의 보호 기능만으로 InnoDB page 단위 안전성이 완성되는 것은 아니다.

### 9.2 “redo log가 있으니 page가 깨져도 복구된다”는 오해

redo log는 crash recovery의 핵심이지만, 손상된 page를 항상 해석 가능한 상태로 되돌리는 장치는 아니다. page checksum 불일치나 page 구조 손상이 발생하면 redo 적용 전에 정상 page 이미지가 필요할 수 있다. Doublewrite Buffer는 이 지점을 보완한다.

### 9.3 “벤치마크에서 빨라졌으므로 운영에서도 꺼도 된다”는 판단

doublewrite를 끄면 일부 쓰기 벤치마크에서 성능이 좋아질 수 있다. 그러나 벤치마크가 장애 중단, crash recovery, page checksum 오류, 백업 복구, replica 재구축 시간을 포함하지 않았다면 운영 결정을 내리기에는 부족하다. 데이터베이스 성능은 정상 상태 처리량뿐 아니라 장애 후 신뢰성까지 포함한다.

### 9.4 “스토리지가 빠르면 doublewrite 비용은 무시해도 된다”는 반대 오해

최신 NVMe나 고성능 클라우드 볼륨에서는 doublewrite 비용이 작게 보일 수 있다. 그러나 checkpoint가 몰리거나 대량 변경이 실행되면 doublewrite 경로가 여전히 flush 패턴과 tail latency에 영향을 줄 수 있다. 안전장치를 유지하되, 병목 분석에서는 실제 지표로 확인해야 한다.

## 10. 운영 점검표

Doublewrite Buffer와 torn page 위험을 점검할 때는 다음 항목을 기준으로 판단한다.

- [ ] `innodb_doublewrite`가 의도한 값으로 설정되어 있는가?
- [ ] MySQL 버전과 배포판에서 doublewrite 관련 값의 의미를 확인했는가?
- [ ] 쓰기 피크 시간대의 `Innodb_dblwr%` 상태 변수 증가량을 관찰했는가?
- [ ] redo log write/fsync, checkpoint age, dirty page flush, storage latency를 함께 보았는가?
- [ ] 스토리지 계층의 atomic write 보장 여부를 문서와 장애 주입 테스트로 확인했는가?
- [ ] doublewrite를 끄는 경우 crash recovery 테스트와 백업 복구 테스트를 수행했는가?
- [ ] Aurora MySQL인지 Community MySQL인지에 따라 스토리지 모델을 분리해서 해석했는가?
- [ ] page corruption 감지 시 replica 승격, 백업 복원, 테이블 재생성 절차가 준비되어 있는가?

## 11. 결론

Doublewrite Buffer는 InnoDB의 보수적인 안전 설계가 드러나는 대표적인 기능이다. redo log가 트랜잭션 변경을 재적용하는 기록이라면, doublewrite는 데이터 파일 page가 찢어진 상태로 남았을 때 복구 가능한 page 이미지를 제공하는 보호막이다. 이 기능은 무료가 아니다. 쓰기 I/O 경로에 추가 비용을 만들고, 일부 워크로드에서는 성능 지표로 관찰된다. 그러나 범용 운영 환경에서는 그 비용이 장애 후 데이터 무결성과 복구 가능성에 비해 작을 때가 많다.

운영자는 doublewrite를 단순한 성능 튜닝 스위치로 보지 말고, redo log, page checksum, checkpoint, storage durability, 백업 복구 전략과 함께 하나의 신뢰성 체인으로 이해해야 한다. 다음 글에서는 InnoDB page flush와 checkpoint가 실제 쓰기 지연, redo log 재사용, 복구 시간에 어떤 영향을 주는지 더 세밀하게 연결해 볼 수 있다.
