본문으로 건너뛰기

COMMIT 직후 서버가 꺼져도 값은 남을까

다음 트랜잭션이 성공했다고 해보겠습니다.

sql
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에서 다루는 단위는 행입니다.

text
id=1, status=BOOKED

하지만 InnoDB가 Buffer Pool과 Data Tablespace 사이에서 읽고 쓰는 단위는 Page입니다. 하나의 Page에는 여러 행이 들어갈 수 있습니다. 기본 Page 크기는 16KiB지만 innodb_page_size 설정에 따라 달라질 수 있습니다.

InnoDB는 Buffer Pool에 테이블과 인덱스 데이터를 Page 단위로 캐시합니다. 필요한 Page가 이미 있다면 바로 사용하고, 없다면 Data Tablespace에서 읽어 올립니다.

text
Data Tablespace
│ Page Read

Buffer Pool의 Data/Index Page

id=1은 기본 키를 기준으로 Clustered Index Record에 들어 있습니다. 상태를 바꾸는 작업은 Buffer Pool에 올라온 Clustered Index Page의 Record를 변경하는 작업이 됩니다.

text
Buffer Pool의 Clustered Index Page

id=1, status=BOOKED
↓ UPDATE
id=1, status=CANCELLED

변경은 우선 메모리에서 일어납니다. UPDATE를 실행했다고 Data Tablespace의 Page가 즉시 바뀌는 것은 아닙니다.

UPDATE는 세 가지 흔적을 남긴다

BOOKEDCANCELLED로 바꾸려면 두 상황에 대비해야 합니다.

  • 트랜잭션을 취소하면 다시 BOOKED로 돌아가야 합니다.
  • 변경된 Page가 Data File에 저장되기 전에 장애가 나면 CANCELLED를 다시 만들 수 있어야 합니다.

첫 번째 상황에는 Undo가 필요하고, 두 번째 상황에는 Redo가 필요합니다.

개념적으로 보면 이 UPDATE는 이전 값을 되돌릴 Undo 정보와 Data/Index Page의 변경을 함께 만듭니다.

UPDATE가 Undo Log Record, Dirty Data Page, Redo Log Record를 남기는 과정

세 정보는 같은 내용을 복사해 둔 것이 아닙니다.

구조이번 UPDATE에서 맡는 역할
Undo Log RecordCANCELLEDBOOKED로 되돌릴 정보를 남깁니다.
Dirty Data/Index PageBuffer Pool에서 보이는 현재 값 CANCELLED를 담습니다.
Redo Log RecordsData/Index Page와 Undo Page의 변경을 다시 적용할 정보를 남깁니다.

Undo는 이전 상태를 복원한다

트랜잭션이 Rollback되면 CANCELLEDBOOKED로 되돌려야 합니다. 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됩니다.

text
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는 트랜잭션이 커밋되지 않았다는 뜻이 아닙니다.

text
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를 거칩니다.

text
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 범위를 차지합니다.

text
SQL Transaction
├─ mtr A: 하나 이상의 Page 변경
├─ mtr B: 하나 이상의 Page 변경
└─ ...

실제 mtr의 개수와 경계는 작업에 따라 달라집니다.

mtr가 끝났다고 해서 SQL Transaction이 COMMIT되거나 Redo가 저장 장치까지 영속화된 것은 아닙니다.

text
mtr 완료
≠ SQL Transaction COMMIT
≠ Redo Log 영속화 완료

COMMIT 성공과 Data Page 저장 완료는 같은 사건이 아닙니다.