본문으로 건너뛰기

Checkpoint는 복구 시작점을 어떻게 옮기는가

Redo Log의 저장 용량은 한정되어 있습니다. Checkpoint가 전진하면 장애 복구에 더는 필요하지 않은 오래된 Redo부터 정리해 공간을 다시 사용합니다.

Checkpoint는 Redo를 재사용할 수 있는 경계를 만든다

Redo의 진행 상태를 단순하게 그리면 다음과 같습니다.

text
last_checkpoint_lsn current_lsn
|-----------------------------------------|
checkpoint age

checkpoint age = current_lsn - last_checkpoint_lsn

current_lsn은 새로운 Redo가 만들어지는 끝을 나타냅니다. last_checkpoint_lsn은 장애 복구가 시작할 수 있는 최근 경계입니다. 둘 사이의 범위는 아직 복구에 필요할 수 있으므로 보존해야 합니다.

오래된 변경을 담은 Dirty Page가 Data File에 안전하게 반영되지 않았다면 Checkpoint는 그 변경을 지나 전진할 수 없습니다. Checkpoint가 뒤처지면 재사용할 수 있는 Redo 공간이 줄고, InnoDB는 Page Flush를 더 적극적으로 진행할 수 있습니다. 공간이 부족하면 새로운 쓰기가 기다릴 수도 있습니다.

Fuzzy Checkpoint는 Buffer Pool 전체를 비우지 않는다

Checkpoint 때마다 Buffer Pool의 모든 Dirty Page를 한 번에 저장한다면 쓰기가 오래 멈출 수 있습니다. InnoDB는 Fuzzy Checkpoint 방식으로 Dirty Page를 작은 묶음으로 계속 Flush합니다.

Fuzzy Checkpoint가 오래된 Redo를 재사용 가능한 범위로 만들고 Recovery 시작점을 옮기는 과정

Checkpoint는 작은 Dirty Page 묶음을 Flush하면서 전진합니다. Crash Recovery는 최신 Checkpoint부터 Redo를 순방향으로 읽습니다.

Checkpoint가 LSN 100까지 전진했다면 그 이전 변경을 복구하려고 앞쪽 Redo를 다시 읽을 필요가 없다는 뜻입니다. 해당 구간은 순환 로그에서 재사용할 수 있습니다.

Checkpoint는 Buffer Pool 전체를 비우거나 특정 트랜잭션의 COMMIT을 표시하는 작업이 아닙니다. LSN 100 이후에 다시 변경된 Page는 여전히 Dirty일 수 있습니다.

Page LSN은 Page가 어디까지 반영됐는지 나타낸다

Redo를 읽더라도 모든 기록을 Data Page에 다시 적용할 필요는 없습니다. 장애 전에 Data File에 이미 저장된 변경도 있기 때문입니다.

InnoDB Page Header에는 FIL_PAGE_LSN이 있습니다. 해당 Page에 반영된 가장 최근 Redo Record가 끝나는 LSN을 담으므로 보통 Page LSN이라고 부릅니다.

text
Data Page A
FIL_PAGE_LSN = 160

Redo Group [120, 140) → 이미 반영된 범위
Redo Group [140, 160) → 이미 반영된 범위
Redo Group [160, 180) → 적용 후보

실제로는 Redo Record의 범위와 Page 초기화 여부, Tablespace 상태도 함께 확인합니다(FIL_PAGE_LSN 정의, Recovery 구현).

Page LSN을 COMMIT 여부로 해석해서도 안 됩니다. Page LSN은 Page 단위의 물리 변경 위치입니다. 미커밋 변경을 담은 Page도 Data File에 저장될 수 있으므로 LSN이 높다고 그 안의 모든 변경이 COMMIT됐다는 뜻은 아닙니다.

WAL만으로 Torn Page를 복구할 수는 없다

WAL은 Data Page를 쓰기 전에 관련 Redo가 먼저 영속화되도록 순서를 지킵니다. Page 자체가 온전하게 기록됐는지까지 보장하지는 않습니다.

기본 16KiB InnoDB Page를 Data File에 쓰는 동안 장애가 발생하면 저장 계층에 따라 일부만 새 값으로 바뀐 Page가 남을 수 있습니다. 이를 Torn Page 또는 Partial Page Write라고 합니다.

text
정상 Page
[ 새 Header ][ 새 Record ][ 새 나머지 영역 ]

쓰는 도중 장애가 난 Page
[ 새 Header ][ 이전 Record ][ 이전 나머지 영역 ]

Redo는 기준 Page에 어떤 변경을 적용할지 기록합니다. 기준이 될 Page 자체가 중간 상태로 깨져 있다면 Redo만으로 항상 정상 Page를 만들 수 있다고 볼 수 없습니다.

Doublewrite에 Page 사본을 먼저 기록하고 Data File의 최종 위치를 쓰는 과정

일반적인 innodb_doublewrite=ON 또는 DETECT_AND_RECOVER 경로에서는 Page를 최종 위치에 쓰기 전에 Doublewrite 영역에 사본을 쓰고 Flush합니다. 최종 위치 기록이 중간에 끊기면 Recovery에서 온전한 사본을 이용할 수 있습니다.

Doublewrite와 Redo의 역할은 다음처럼 나뉩니다.

  • Doublewrite는 불완전한 최종 Page Write를 복구할 수 있는 사본을 마련합니다.
  • Redo는 복구한 Page 이후의 변경을 필요한 지점까지 다시 적용합니다.

CANCELLED Page를 다시 따라가 보기

id=1BOOKED에서 CANCELLED로 바꾸고 COMMIT했다고 가정하겠습니다.

text
Redo Log
BOOKED → CANCELLED 변경에 필요한 Redo가 영속화됨

Buffer Pool
Dirty Page: CANCELLED

Data File
Page: BOOKED

Data File의 Page가 아직 BOOKED라면 Page LSN도 관련 Redo보다 뒤처져 있을 수 있습니다. 재시작한 InnoDB는 최신 Checkpoint부터 Redo를 읽고, Page가 오래된 경우 CANCELLED 변경을 적용합니다.

반대로 Data File의 Page에 이미 CANCELLED가 반영됐다면 오래된 Redo를 다시 적용할 필요가 없습니다. Page Write가 중간에 끊겼다면 Doublewrite 사본으로 기준 Page를 복구한 뒤 필요한 변경을 Redo로 다시 적용합니다.