COMMIT 직후 서버가 꺼져도 값은 남을까
다음 트랜잭션이 성공했다고 해보겠습니다.
START TRANSACTION;
UPDATE reservations
SET status = 'CANCELLED'
WHERE id = 1;
COMMIT;
원래 값은 BOOKED였습니다. 애플리케이션은 COMMIT 성공 응답을 받았고, 그 직후 MySQL 서버가 비정상 종료됐습니다. 다시 시작했을 때 id=1의 상태는 무엇이어야 할까요?
Redo Log가 활성화된 일반 영구 InnoDB 테이블에서 innodb_flush_log_at_trx_commit=1을 사용했다면, 저장 계층이 Flush 요청을 정상적으로 처리한다는 전제에서 재시작 뒤에도 CANCELLED가 보여야 합니다. Data File의 Page가 아직 BOOKED여도 마찬가지입니다.
이를 이해하려면 UPDATE가 행 하나만 바꾸는 작업이라고 생각해서는 부족합니다. InnoDB는 메모리에 있는 Page를 바꾸고, 이전 상태를 되돌릴 정보와 장애 뒤 변경을 다시 적용할 정보를 함께 관리합니다.
InnoDB는 행이 아니라 Page를 읽고 쓴다
SQL에서 다루는 단위는 행입니다.
id=1, status=BOOKED
하지만 InnoDB가 Buffer Pool과 Data Tablespace 사이에서 읽고 쓰는 단위는 Page입니다. 하나의 Page에는 여러 행이 들어갈 수 있습니다. 기본 Page 크기는 16KiB지만 innodb_page_size 설정에 따라 달라질 수 있습니다.
InnoDB는 Buffer Pool에 테이블과 인덱스 데이터를 Page 단위로 캐시합니다. 필요한 Page가 이미 있다면 바로 사용하고, 없다면 Data Tablespace에서 읽어 올립니다.
Data Tablespace
│ Page Read
▼
Buffer Pool의 Data/Index Page
id=1은 기본 키를 기준으로 Clustered Index Record에 들어 있습니다. 상태를 바꾸는 작업은 Buffer Pool에 올라온 Clustered Index Page의 Record를 변경하는 작업이 됩니다.
Buffer Pool의 Clustered Index Page
id=1, status=BOOKED
↓ UPDATE
id=1, status=CANCELLED
변경은 우선 메모리에서 일어납니다. UPDATE를 실행했다고 Data Tablespace의 Page가 즉시 바뀌는 것은 아닙니다.
UPDATE는 세 가지 흔적을 남긴다
BOOKED를 CANCELLED로 바꾸려면 두 상황에 대비해야 합니다.
- 트랜잭션을 취소하면 다시
BOOKED로 돌아가야 합니다. - 변경된 Page가 Data File에 저장되기 전에 장애가 나면
CANCELLED를 다시 만들 수 있어야 합니다.
첫 번째 상황에는 Undo가 필요하고, 두 번째 상황에는 Redo가 필요합니다.
개념적으로 보면 이 UPDATE는 이전 값을 되돌릴 Undo 정보와 Data/Index Page의 변경을 함께 만듭니다.
세 정보는 같은 내용을 복사해 둔 것이 아닙니다.
| 구조 | 이번 UPDATE에서 맡는 역할 |
|---|---|
| Undo Log Record | CANCELLED를 BOOKED로 되돌릴 정보를 남깁니다. |
| Dirty Data/Index Page | Buffer Pool에서 보이는 현재 값 CANCELLED를 담습니다. |
| Redo Log Records | Data/Index Page와 Undo Page의 변경을 다시 적용할 정보를 남깁니다. |
Undo는 이전 상태를 복원한다
트랜잭션이 Rollback되면 CANCELLED를 BOOKED로 되돌려야 합니다. Undo Log Record에는 최근 변경을 취소할 수 있는 정보가 들어갑니다.
Undo Log는 Rollback뿐 아니라 Consistent Read에서 이전 버전을 재구성할 때도 사용됩니다. UPDATE와 DELETE가 만든 Undo는 다른 트랜잭션의 Read View가 필요로 할 수 있어 COMMIT 직후 곧바로 사라지지 않습니다.
Undo Log는 별도의 메모리 버퍼가 아닙니다. 일반 영구 테이블의 Undo Record는 Undo Page에 기록되고, 이 Page도 Buffer Pool에서 바뀐 뒤 Undo Tablespace로 Flush됩니다.
RAM Storage
Buffer Pool의 Undo Page ── Page Flush ──> Undo Tablespace
이전 값을 복원할 정보 Undo Pages
Undo Page의 변경도 장애 뒤 복원할 수 있도록 Redo가 보호합니다. Undo는 이전 행 상태를 되돌리고, Redo는 Data Page와 Undo Page의 물리 변경을 다시 적용합니다.
Dirty Page는 미커밋 Page라는 뜻이 아니다
Buffer Pool의 Page가 CANCELLED로 바뀌면 이 Page는 Dirty Page가 됩니다. Dirty는 트랜잭션이 커밋되지 않았다는 뜻이 아닙니다.
Buffer Pool의 Page status=CANCELLED
Data Tablespace의 Page status=BOOKED
메모리의 Page가 대상 Data File보다 최신이면 Dirty입니다. 트랜잭션이 아직 ACTIVE여도 Dirty Page가 생길 수 있고, COMMIT된 뒤에도 Page가 Flush되지 않았다면 계속 Dirty일 수 있습니다.
반대로 미커밋 변경이 들어간 Page도 WAL 조건을 지킨 뒤 Data File에 먼저 저장될 수 있습니다. 따라서 Page의 Dirty 여부와 트랜잭션 상태는 따로 봐야 합니다.
Redo는 Page 변경을 다시 적용한다
Redo Log Record는 UPDATE reservations ... 같은 SQL 문장을 그대로 저장한 것이 아닙니다. InnoDB가 Data/Index Page와 Undo Page에 가한 변경을 장애 뒤 재현할 수 있는 형태로 기록합니다.
Redo Log는 Crash Recovery에 사용됩니다. 정상 실행 중 발생한 변경은 Redo에 추가되고, Data File에 반영되지 못한 변경은 재시작 과정에서 필요한 만큼 다시 적용됩니다.
Redo는 처음부터 파일에 바로 기록되지 않습니다. 먼저 메모리의 Log Buffer를 거칩니다.
RAM Storage
Log Buffer ── write ──> OS I/O 경로와 Redo Log File
Page 변경 Redo Records Flush 전에는 휘발 가능
── Flush ──> 비휘발성 저장 영역
Log Buffer는 Redo Log File에 쓸 데이터를 보관하는 메모리 영역입니다. 미커밋 트랜잭션의 Redo도 내부 작업이나 주기적인 Log 처리에 따라 먼저 파일에 기록될 수 있습니다. Redo가 파일에 있다는 사실만으로 COMMIT 여부를 판단할 수는 없습니다.
SQL Transaction과 Mini-Transaction은 다르다
한 번의 SQL UPDATE도 InnoDB 안에서는 여러 Page를 바꿀 수 있습니다. 이번 예시에서도 Undo Page와 Clustered Index Page를 다룹니다. 대상 컬럼이 Secondary Index에 포함돼 있다면 추가 Index Page도 바뀔 수 있습니다.
InnoDB는 내부 Page 변경을 Mini-Transaction(mtr) 단위로 묶습니다. Redo를 남기는 하나의 mtr는 여러 Page를 바꿀 수 있습니다. mtr에서 만들어진 Redo Log Records는 하나의 연속된 묶음으로 Log Buffer에 기록되고 연속된 LSN 범위를 차지합니다.
SQL Transaction
├─ mtr A: 하나 이상의 Page 변경
├─ mtr B: 하나 이상의 Page 변경
└─ ...
실제 mtr의 개수와 경계는 작업에 따라 달라집니다.
mtr가 끝났다고 해서 SQL Transaction이 COMMIT되거나 Redo가 저장 장치까지 영속화된 것은 아닙니다.
mtr 완료
≠ SQL Transaction COMMIT
≠ Redo Log 영속화 완료
COMMIT 성공과 Data Page 저장 완료는 같은 사건이 아닙니다.