---
title: "InnoDB crash recovery 단계: redo 적용, undo rollback, purge 재개"
description: "InnoDB crash recovery가 redo 적용, undo rollback, purge 재개 순서로 일관성을 회복하는 과정을 운영 관점에서 정리한다."
tags: [ MySQL, InnoDB, 백업복구, 운영 ]
image: "mysql-report-bg.png"
published: "2026-06-17"
updated: "2026-06-17"
author: "MySQL 기술 노트"
source_url: ""
---

## 1. crash recovery를 단계로 이해해야 하는 이유

MySQL 운영에서 장애 복구는 단순히 `mysqld` 프로세스를 다시 띄우는 절차가 아니다. 특히 InnoDB는 커밋된 변경을 잃지 않으면서도, 커밋되지 않은 변경은 보이지 않게 되돌려야 한다. 이 두 요구는 서로 반대 방향처럼 보인다. 커밋된 변경은 최대한 앞으로 재생해야 하고, 미완료 트랜잭션은 뒤로 되돌려야 하기 때문이다. InnoDB crash recovery는 이 모순을 redo log, undo log, purge의 역할 분담으로 해결한다.

운영자가 crash recovery 단계를 이해해야 하는 이유는 명확하다.

- 장애 후 MySQL 기동 시간이 길어질 때 어느 단계가 오래 걸리는지 추정할 수 있다.
- `innodb_force_recovery` 같은 위험한 옵션을 언제 피해야 하는지 판단할 수 있다.
- 긴 트랜잭션, 큰 DML, purge 지연, redo log 압력이 복구 시간에 어떤 영향을 주는지 연결해서 해석할 수 있다.
- Aurora MySQL처럼 스토리지 계층이 다르게 구현된 환경에서도 “복구가 빠른 이유”와 “여전히 남는 위험”을 구분할 수 있다.

이 글은 InnoDB crash recovery를 세 단계로 나누어 설명한다. 첫째, checkpoint 이후의 redo log를 적용하여 데이터 페이지를 최신 커밋 상태에 가깝게 만든다. 둘째, 장애 시점에 끝나지 않은 트랜잭션을 undo log로 rollback한다. 셋째, rollback과 MVCC 정리 이후 남은 undo 이력은 purge가 재개되면서 점진적으로 정리된다.

## 2. InnoDB가 장애 직후 보는 것은 “완성된 데이터 파일”이 아니다

InnoDB는 정상 운영 중 모든 변경 페이지를 즉시 데이터 파일에 쓰지 않는다. 커밋 경로는 redo log 내구성을 중심으로 빠르게 완료되고, 데이터 페이지 flush는 page cleaner와 checkpoint 정책이 뒤따라 수행한다. 따라서 장애 직후 데이터 파일은 다음 상태가 섞여 있을 수 있다.

| 상태 | 의미 | 복구에서 필요한 작업 |
|---|---|---|
| 데이터 파일에 이미 반영된 커밋 변경 | dirty page가 장애 전에 flush됨 | 추가 작업이 거의 없다 |
| redo log에는 있으나 데이터 파일에는 없는 커밋 변경 | 커밋은 완료됐지만 page flush 전 장애 발생 | redo 적용 필요 |
| 데이터 파일 일부에 반영된 미커밋 변경 | 트랜잭션 진행 중 dirty page가 flush된 상태 | undo rollback 필요 |
| 오래된 undo 이력 | MVCC read view 또는 purge 지연 때문에 남은 이력 | purge 재개 후 정리 |

이 표에서 핵심은 데이터 파일만 보고는 일관성을 판단할 수 없다는 점이다. InnoDB는 checkpoint LSN을 기준으로 redo log를 재생하고, 트랜잭션 시스템 메타데이터와 undo log를 이용해 미완료 트랜잭션을 식별한다. 그 다음 purge가 MVCC 관점에서 더 이상 필요 없는 undo 이력을 제거한다.

## 3. 전체 흐름: redo, undo, purge의 역할 분리

crash recovery는 내부적으로 더 많은 세부 단계를 포함하지만, 운영 해석에는 다음 흐름으로 이해하는 것이 유용하다.

```mermaid
flowchart TD
  A[장애 발생] --> B[mysqld 재시작]
  B --> C[checkpoint LSN 확인]
  C --> D[redo log scan]
  D --> E[redo 적용: 커밋된 변경 재생]
  E --> F[transaction table 확인]
  F --> G{장애 시점 미완료 트랜잭션 존재?}
  G -- 예 --> H[undo log 기반 rollback]
  G -- 아니오 --> I[purge thread 재개]
  H --> I
  I --> J[history list 정리와 정상 서비스]
```

각 단계의 목적은 다르다.

1. **redo 적용**은 “이미 durable하게 기록된 변경을 잃지 않는 것”이 목적이다.
2. **undo rollback**은 “커밋되지 않은 변경을 사용자에게 남기지 않는 것”이 목적이다.
3. **purge 재개**는 “MVCC를 위해 남겨 둔 과거 버전과 undo 이력을 정리하는 것”이 목적이다.

redo는 앞으로 전진하는 작업이고, undo는 미완료 트랜잭션을 되돌리는 작업이다. purge는 즉시 일관성을 만들기 위한 필수 전제라기보다, 복구 이후 스토리지와 메타데이터 부채를 줄이는 후속 정리 작업이다. 이 차이를 구분하지 않으면 “복구가 끝났다”와 “undo 이력이 모두 정리됐다”를 혼동하게 된다.

## 4. 1단계: redo 적용

redo log는 InnoDB가 page 변경을 다시 구성할 수 있도록 기록한 물리적 또는 물리-논리적 변경 기록이다. 정상 커밋에서는 `innodb_flush_log_at_trx_commit` 설정에 따라 redo log buffer가 redo log file과 스토리지에 내려가는 방식이 달라진다. 장애 복구에서는 checkpoint LSN 이후의 redo log를 읽어, 데이터 파일에 아직 반영되지 않은 변경을 다시 적용한다.

redo 적용이 오래 걸리는 대표 원인은 다음과 같다.

- checkpoint가 오래 밀려 checkpoint age가 컸다.
- 장애 직전 대량 쓰기 작업으로 redo 생성량이 많았다.
- 스토리지 읽기/쓰기 성능이 낮거나, 재시작 직후 I/O contention이 크다.
- redo log 파일과 데이터 파일 접근이 동시에 병목을 만든다.

redo 적용은 커밋된 변경만 다룬다고 단순화해서 말할 수 있지만, 데이터 페이지 관점에서는 미커밋 변경이 일부 포함된 페이지도 있을 수 있다. 그래서 redo 적용만으로는 SQL 격리 수준에서의 일관성이 완성되지 않는다. redo는 page를 crash-consistent하게 만들고, 그 다음 undo rollback이 트랜잭션 일관성을 완성한다.

운영 환경에서는 redo 관련 상태를 직접 완벽하게 재현하기 어렵지만, MySQL 8.0에서 확인 가능한 기본 객체와 지표는 다음처럼 점검할 수 있다.

```sql
SELECT VERSION() AS mysql_version;
SHOW TABLES FROM information_schema LIKE 'INNODB_METRICS';
SELECT NAME, SUBSYSTEM, STATUS, TYPE
FROM information_schema.INNODB_METRICS
WHERE NAME IN ('log_lsn_current', 'log_lsn_checkpoint', 'trx_rseg_history_len')
ORDER BY NAME;
```

실행 결과(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 TABLES FROM information_schema LIKE 'INNODB_METRICS';

+-----------------------------------------------+
| Tables_in_information_schema (INNODB_METRICS) |
+-----------------------------------------------+
| INNODB_METRICS                                |
+-----------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT NAME, SUBSYSTEM, STATUS, TYPE
    -> FROM information_schema.INNODB_METRICS
    -> WHERE NAME IN ('log_lsn_current', 'log_lsn_checkpoint', 'trx_rseg_history_len')
    -> ORDER BY NAME;

+----------------------+-------------+----------+-------+
| NAME                 | SUBSYSTEM   | STATUS   | TYPE  |
+----------------------+-------------+----------+-------+
| log_lsn_current      | log         | disabled | value |
| trx_rseg_history_len | transaction | enabled  | value |
+----------------------+-------------+----------+-------+
2 rows in set (0.00 sec)
```

위 쿼리는 단일 테스트 인스턴스에서도 실행 가능하다. 다만 `INNODB_METRICS`의 개별 지표 활성화 상태와 노출 이름은 MySQL minor version에 따라 다를 수 있다. 운영에서는 이 결과만으로 복구 시간을 예측하기보다, checkpoint age, redo 생성량, dirty page flush 지연, 장애 직전 쓰기량을 함께 보아야 한다.

## 5. 2단계: undo rollback

redo 적용이 끝나면 InnoDB는 장애 시점에 active였던 트랜잭션을 확인한다. active 트랜잭션은 commit record가 없으므로 사용자 관점에서 완료된 작업이 아니다. 이 트랜잭션이 변경한 row version은 undo log를 따라 되돌려야 한다.

undo rollback의 중요한 특징은 다음과 같다.

- rollback 대상은 장애 시점에 커밋되지 않은 트랜잭션이다.
- 대량 UPDATE/DELETE가 미완료 상태로 장애를 맞으면 rollback 자체가 오래 걸릴 수 있다.
- rollback 중에도 일부 서비스가 올라온 것처럼 보일 수 있으나, 내부적으로는 트랜잭션 정리가 계속될 수 있다.
- 긴 트랜잭션은 정상 운영 중 purge 지연을 만들 뿐 아니라, 장애 후 rollback 비용도 키운다.

아래 예제는 crash recovery 자체를 재현하는 것은 아니지만, InnoDB가 미완료 변경을 undo 정보로 되돌리는 의미를 축소해서 보여준다.

```sql
DROP TABLE IF EXISTS crash_recovery_undo_demo;
CREATE TABLE crash_recovery_undo_demo (
  account_id BIGINT PRIMARY KEY,
  balance INT NOT NULL
) ENGINE=InnoDB;

INSERT INTO crash_recovery_undo_demo VALUES (1, 100), (2, 200);

START TRANSACTION;
UPDATE crash_recovery_undo_demo
SET balance = balance - 30
WHERE account_id = 1;

SELECT account_id, balance AS during_uncommitted_transaction
FROM crash_recovery_undo_demo
ORDER BY account_id;

ROLLBACK;

SELECT account_id, balance AS after_rollback
FROM crash_recovery_undo_demo
ORDER BY account_id;

DROP TABLE crash_recovery_undo_demo;
```

실행 결과(MySQL 8.0.46):

```text
mysql> DROP TABLE IF EXISTS crash_recovery_undo_demo;

Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE crash_recovery_undo_demo (
    ->   account_id BIGINT PRIMARY KEY,
    ->   balance INT NOT NULL
    -> ) ENGINE=InnoDB;

Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO crash_recovery_undo_demo VALUES (1, 100), (2, 200);

Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> START TRANSACTION;

Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE crash_recovery_undo_demo
    -> SET balance = balance - 30
    -> WHERE account_id = 1;

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

mysql> SELECT account_id, balance AS during_uncommitted_transaction
    -> FROM crash_recovery_undo_demo
    -> ORDER BY account_id;

+------------+--------------------------------+
| account_id | during_uncommitted_transaction |
+------------+--------------------------------+
|          1 |                             70 |
|          2 |                            200 |
+------------+--------------------------------+
2 rows in set (0.00 sec)

mysql> ROLLBACK;

Query OK, 0 rows affected (0.00 sec)

mysql> SELECT account_id, balance AS after_rollback
    -> FROM crash_recovery_undo_demo
    -> ORDER BY account_id;

+------------+----------------+
| account_id | after_rollback |
+------------+----------------+
|          1 |            100 |
|          2 |            200 |
+------------+----------------+
2 rows in set (0.00 sec)

mysql> DROP TABLE crash_recovery_undo_demo;

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

이 예제에서 첫 번째 `SELECT`는 같은 세션 내부에서 미커밋 변경을 볼 수 있음을 보여준다. `ROLLBACK` 이후 두 번째 `SELECT`는 변경이 취소되어 원래 값으로 돌아간 상태를 보여준다. crash recovery의 undo rollback도 원리는 같다. 차이는 사람이 명시적으로 `ROLLBACK`을 실행하는 것이 아니라, InnoDB가 복구 과정에서 commit되지 않은 transaction을 찾아 undo log를 따라 되돌린다는 점이다.

## 6. 3단계: purge 재개

rollback이 끝났다고 해서 undo 관련 모든 데이터가 즉시 사라지는 것은 아니다. InnoDB는 MVCC를 위해 과거 row version을 일정 기간 유지한다. 어떤 read view가 과거 버전을 참조할 수 있으면 purge는 해당 undo 이력을 제거할 수 없다. 장애 복구 후에는 purge thread가 다시 동작하면서 더 이상 필요 없는 undo record와 delete-marked record를 정리한다.

purge 지연은 다음과 같은 운영 증상으로 나타날 수 있다.

- history list length가 계속 증가한다.
- undo tablespace 사용량이 줄지 않는다.
- 오래된 read view를 가진 트랜잭션 때문에 DELETE/UPDATE 이후 공간 회수가 늦어진다.
- secondary index 정리와 page 재사용이 늦어져 장기적으로 I/O와 buffer pool 효율이 악화된다.

MySQL 8.0에서는 현재 트랜잭션과 InnoDB metric 객체를 다음처럼 확인할 수 있다.

```sql
SHOW TABLES FROM information_schema LIKE 'INNODB_TRX';
SELECT trx_id, trx_state, trx_started, trx_query
FROM information_schema.INNODB_TRX
ORDER BY trx_started
LIMIT 5;
SELECT NAME, COUNT, COMMENT
FROM information_schema.INNODB_METRICS
WHERE NAME = 'trx_rseg_history_len';
```

실행 결과(MySQL 8.0.46):

```text
mysql> SHOW TABLES FROM information_schema LIKE 'INNODB_TRX';

+-------------------------------------------+
| Tables_in_information_schema (INNODB_TRX) |
+-------------------------------------------+
| INNODB_TRX                                |
+-------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT trx_id, trx_state, trx_started, trx_query
    -> FROM information_schema.INNODB_TRX
    -> ORDER BY trx_started
    -> LIMIT 5;

Empty set (0.00 sec)

mysql> SELECT NAME, COUNT, COMMENT
    -> FROM information_schema.INNODB_METRICS
    -> WHERE NAME = 'trx_rseg_history_len';

+----------------------+-------+-------------------------------------+
| NAME                 | COUNT | COMMENT                             |
+----------------------+-------+-------------------------------------+
| trx_rseg_history_len |    13 | Length of the TRX_RSEG_HISTORY list |
+----------------------+-------+-------------------------------------+
1 row in set (0.00 sec)
```

테스트 컨테이너처럼 부하가 없는 환경에서는 active transaction이 없고 `INNODB_TRX` 결과가 비어 있을 수 있다. 이것은 쿼리 실패가 아니라 “현재 관측할 긴 트랜잭션이 없다”는 뜻이다. 운영 환경에서는 긴 SELECT, 열려 있는 트랜잭션, batch DML, replication SQL thread 지연 등과 함께 해석해야 한다.

## 7. 장애 복구 시간을 길게 만드는 운영 패턴

crash recovery 시간은 단일 변수로 결정되지 않는다. 다음 패턴이 겹치면 재시작은 더 오래 걸릴 수 있다.

### 7.1 checkpoint가 뒤처진 상태에서 장애가 발생한 경우

장애 직전 dirty page flush가 따라가지 못하면 checkpoint LSN이 오래된 위치에 머문다. 그러면 복구 시 읽고 적용해야 할 redo 범위가 길어진다. redo log 파일 크기를 크게 잡으면 peak workload를 흡수하는 데는 도움이 되지만, checkpoint가 계속 밀린 상태를 방치하면 복구 대상 범위도 커질 수 있다.

### 7.2 대량 미완료 트랜잭션이 있던 경우

큰 `UPDATE`, `DELETE`, `INSERT ... SELECT`, 대량 batch 작업이 commit 전에 장애를 맞으면 undo rollback이 길어질 수 있다. 특히 한 트랜잭션에 너무 많은 row 변경을 넣으면 정상 rollback도 오래 걸리고, crash recovery rollback도 오래 걸린다. 운영 batch는 가능한 단위로 나누고, commit 간격과 실패 재시작 전략을 명시해야 한다.

### 7.3 purge가 장기간 밀린 경우

긴 read transaction 때문에 purge가 밀리면 history list가 증가한다. 장애 복구가 끝난 뒤에도 purge가 재개되며 내부 정리 부하가 이어질 수 있다. 이 상태에서 바로 대량 쓰기 workload를 재개하면 buffer pool, undo tablespace, I/O에 추가 압력이 생긴다.

### 7.4 복구 직후 트래픽을 즉시 전면 개방한 경우

`mysqld`가 port를 열었다고 해서 모든 내부 정리 작업이 완전히 끝났다고 단정하면 안 된다. 복구 직후에는 buffer pool이 차갑고, purge 또는 rollback 후속 작업이 남아 있을 수 있으며, adaptive hash index나 통계 정보도 평상시와 다른 상태일 수 있다. 장애 후에는 application traffic을 단계적으로 열고, error log와 InnoDB 상태 지표를 확인하는 절차가 필요하다.

## 8. Aurora MySQL에서의 차이

Aurora MySQL은 storage 계층이 MySQL Community의 로컬 InnoDB 파일 배치와 다르게 구현되어 있다. Aurora는 분산 스토리지와 redo 중심의 저장 구조를 사용하므로, 인스턴스 장애 후 재시작 또는 failover에서 전통적인 로컬 데이터 파일 복구와 다른 성능 특성을 보일 수 있다. 일반적으로 Aurora는 crash recovery 시간을 줄이기 위한 스토리지 설계를 제공하지만, 다음 점은 여전히 중요하다.

- writer 장애 후 failover가 빨라도 application connection 재수립, DNS/cache, transaction retry는 별도 문제다.
- 긴 트랜잭션과 purge 지연은 Aurora에서도 undo/MVCC 관점의 운영 위험이다.
- reader lag, replica 재시작, cluster volume 상태, Performance Insights 지표를 함께 봐야 한다.
- Aurora parameter group의 InnoDB 관련 설정 일부는 Community MySQL과 의미나 적용 범위가 다를 수 있다.

따라서 Aurora에서는 “redo 적용 시간이 짧다”는 기대만으로 장애 복구 Runbook을 단순화하면 안 된다. failover 후 write endpoint 전환, transaction retry, idempotent application 설계, purge 지연 관찰을 함께 포함해야 한다.

## 9. 운영 진단과 Runbook 관점

장애 후 복구 상황을 볼 때는 다음 순서로 접근하는 것이 안전하다.

1. MySQL error log에서 InnoDB recovery 메시지를 확인한다.
2. redo scan/apply, rollback, purge 관련 메시지의 시간 간격을 본다.
3. 서버가 client connection을 받기 시작한 뒤에도 긴 rollback이나 purge가 남았는지 확인한다.
4. application traffic을 한 번에 전면 개방하지 말고 read/write 핵심 경로부터 점진적으로 확인한다.
5. 장애 직전 workload를 조사한다. 대량 DML, schema change, backup, flush pressure, disk latency가 있었는지 확인한다.

운영 중에는 다음 지표가 crash recovery 위험의 선행 신호가 될 수 있다.

| 관측 항목 | 의미 | 조치 방향 |
|---|---|---|
| checkpoint age 증가 | redo 생성 대비 flush 지연 | I/O capacity, dirty page 비율, redo log 설정 점검 |
| history list length 증가 | purge 지연 또는 긴 read view | 긴 트랜잭션 종료, batch 크기 조정 |
| active transaction 장기 지속 | rollback 비용 증가 가능성 | session 소유자와 SQL 확인 |
| 대량 DML 단일 transaction | 장애 시 undo rollback 비용 증가 | chunking, 중간 commit, 재시작 가능 설계 |
| 복구 직후 높은 I/O | redo 적용 후 page read/write 집중 | 트래픽 점진 개방, cache warm-up 고려 |

## 10. 흔한 오해와 주의점

### 10.1 “커밋됐으면 데이터 파일에 이미 있다”는 오해

InnoDB에서 커밋은 redo log 내구성과 밀접하게 연결되지만, 데이터 페이지가 즉시 데이터 파일에 flush됐다는 뜻은 아니다. 장애 후 redo 적용이 필요한 이유가 여기에 있다.

### 10.2 “redo 적용만 끝나면 모든 정리가 끝났다”는 오해

redo 적용은 커밋된 변경을 재생하는 단계다. 장애 시점에 active였던 트랜잭션은 undo rollback 대상이며, MVCC 이력 정리는 purge가 맡는다. 복구 메시지에서 어떤 단계가 진행 중인지 구분해야 한다.

### 10.3 `innodb_force_recovery`를 빠른 복구 도구로 보는 오해

`innodb_force_recovery`는 손상된 InnoDB를 읽기 가능한 상태로 올리기 위한 마지막 수단에 가깝다. 값을 높이면 purge, rollback, background 작업 일부가 제한될 수 있고, 쓰기 작업이 위험하거나 불가능해질 수 있다. 단순히 복구 시간이 길다는 이유로 먼저 적용할 옵션이 아니다. 데이터 손상 의심, 백업 추출, 전문적인 복구 절차가 필요한 상황에서 제한적으로 검토해야 한다.

### 10.4 “테이블 수가 적으면 복구도 항상 빠르다”는 오해

복구 시간은 테이블 수보다 checkpoint 이후 redo 범위, dirty page 상태, 미완료 트랜잭션 규모, 스토리지 성능, purge 부채와 더 직접적으로 연결된다. 작은 스키마라도 대량 DML이 미완료 상태로 장애를 맞으면 rollback이 길어질 수 있다.

## 11. 점검표

장애 전 예방 점검:

- [ ] 대량 DML은 chunk 단위와 commit 간격을 정해 실행한다.
- [ ] 장시간 열린 transaction을 주기적으로 탐지한다.
- [ ] purge 지연과 history list length 증가를 모니터링한다.
- [ ] checkpoint 압력, dirty page 비율, redo log 사용 패턴을 관찰한다.
- [ ] 장애 복구 Runbook에 error log 확인 지점과 traffic 재개 기준을 포함한다.

장애 후 재시작 점검:

- [ ] error log에서 redo scan/apply 시작과 종료 시각을 확인한다.
- [ ] rollback 대상 transaction 메시지가 있는지 확인한다.
- [ ] 서버가 열린 뒤 `INNODB_TRX`, purge 관련 지표, I/O 부하를 본다.
- [ ] application write traffic을 단계적으로 재개한다.
- [ ] Aurora에서는 failover 완료와 application retry 성공을 별도로 검증한다.

설정 변경 판단:

- [ ] redo log 크기 증설이 필요한 문제인지, flush 처리량 부족 문제인지 구분한다.
- [ ] `innodb_io_capacity`, storage latency, buffer pool dirty page 정책을 함께 본다.
- [ ] 긴 transaction 제거 없이 undo/purge 문제를 설정만으로 해결하려 하지 않는다.
- [ ] `innodb_force_recovery`는 일반 복구 단축 옵션이 아니라 비상 읽기/덤프 수단으로 취급한다.

## 12. 결론

InnoDB crash recovery는 하나의 동작이 아니라 redo 적용, undo rollback, purge 재개가 순서와 역할을 나누어 수행되는 복합 절차다. redo는 커밋된 변경을 잃지 않게 하고, undo는 미완료 변경을 되돌리며, purge는 MVCC 이력을 정리해 장기적인 저장소 부채를 줄인다.

운영자는 장애가 난 뒤에야 이 구조를 처음 살펴보면 늦다. 평상시에 checkpoint age, 긴 트랜잭션, purge 지연, 대량 DML 패턴을 관리해야 복구 시간을 예측 가능한 범위로 유지할 수 있다. 다음 글들에서는 InnoDB의 undo tablespace, MVCC read view, purge 지연이 실제 쿼리 성능과 공간 회수에 어떤 영향을 주는지 더 세밀하게 다룰 수 있다.
