
안녕하세요 머랭입니다.
데이터베이스를 사용하는 애플리케이션을 개발하다 보면 수많은 트랜잭션을 커밋하게 됩니다.
그런데, 여러분이 커밋한 트랜잭션이 실제로 디스크에 반영되지 않았을 수 있다는 사실을 알고 계신가요?
디스크에 반영한다는 것은, 랜덤 I/O가 발생한다는 의미입니다.
만약 모든 트랜잭션마다 랜덤 I/O가 발생한다면, DBMS의 처리량은 매우 낮을 것입니다.
MySQL의 InnoDB는 이런 문제를 어떻게 해결했을까요?
몇 개의 포스팅을 통해 InnoDB가 높은 처리량을 보장하면서도 ACID를 지킬 수 있도록 하는 핵심 기술인 WAL(Write-Ahead Logging)에 대해 이야기해보고자 합니다.
이번 포스팅에서는 WAL(Write-Ahead Logging)을 이해하기 위해 필요한 핵심 개념인 InnoDB의 버퍼 풀 관리 정책에 대해 설명드리겠습니다.
데이터베이스의 가장 큰 병목은 언제나 디스크 I/O, 특히 랜덤 I/O 입니다.
버퍼는 디스크 I/O를 최소화하기 위해 페이지를 메모리(RAM)에 캐싱해두는 공간입니다.
InnoDB는 변형된 LRU 알고리즘을 통해 버퍼를 관리합니다.
변형된 LRU 알고리즘이 궁금하다면? MySQL InnoDB 공식 문서
- 데이터 캐시: 자주 쓰이는 데이터를 메모리에 올려두어 디스크 I/O 없이 바로 응답합니다.
- 쓰기 지연: 데이터를 변경할 때 즉시 디스크에 기록하지 않고, 버퍼에서 먼저 변경한 뒤 Page Ceaner가 백그라운드에서 디스크에 기록합니다.
Page Cleaner는 InnoDB의 백그라운드 스레드로, 버퍼에 있는 더티 페이지들을 주기적으로 디스크로 플러시(Flush)하여 데이터 손실을 방지하고 버퍼 풀 공간을 확보하는 역할을 합니다.
STEAL / No-STEAL 정책은 트랜잭션이 진행 중(커밋 전)일 때, 버퍼에 존재하는 수정된 페이지를 디스크에 미리 쓸 수 있는지 여부를 결정합니다.
트랜잭션의 진행 여부와 관계없이, 수정된 페이지를 언제든지 디스크에 쓸 수 있는 정책입니다.
버퍼가 부족하면, 버퍼 관리자는 아직 완료되지 않은 트랜잭션이 수정한 페이지라도 디스크에 기록하고 버퍼를 비울 수 있습니다.
아직 커밋되지 않은 데이터가 디스크에 존재하게 됩니다.
InnoDB를 포함한 대부분의 스토리지 엔진이 이 정책을 사용합니다.
한정된 버퍼를 효율적으로 사용할 수 있습니다.
트랜잭션이 종료(커밋)될 때까지는 수정된 페이지를 절대 디스크에 쓰지 않는 정책입니다.
변경된 페이지를 계속해서 버퍼에 유지합니다.
디스크에는 항상 커밋된 데이터만 존재하게 됩니다.
트랜잭션이 다루는 데이터가 매우 크면 그만큼 엄청난 양의 메모리 버퍼가 필요해집니다.
FORCE / No-FORCE 정책은 트랜잭션이 커밋되는 시점에 수정된 모든 페이지를 반드시 디스크에 반영해야 하는지 여부를 결정합니다.
매 트랜잭션이 커밋되는 시점에 수정했던 모든 페이지를 디스크에 즉시 반영하는 정책입니다.
커밋 직후 장애가 발생해도, 이미 디스크 쓰기가 완료된 상태이므로 복구가 필요 없습니다.
매 트랜잭션 커밋 시마다 랜덤 I/O가 발생하므로, 성능이 매우 떨어집니다.
트랜잭션이 커밋되어도 수정된 페이지를 디스크에 즉시 반영하지 않는 정책입니다.
InnoDB를 포함한 대부분의 스토리지 엔진이 이 정책을 사용합니다.
랜덤 I/O 횟수가 크게 감소합니다.
InnoDB를 포함한 대부분의 스토리지 엔진은 STEAL & No-FORCE 정책을 사용합니다.
한정된 버퍼를 효율적으로 사용하기 위해 STEAL 정책을 사용합니다.
랜덤 I/O를 최소화하기 위해 No-FORCE 정책을 사용합니다.
그러나, 두 정책을 사용하게 되면 더티 페이지가 실제 DB에 반영되는 시점과 단위가 모호해지기 때문에 Atomicity와 Durability를 보장할 수 없게 됩니다.
STEAL 정책은 버퍼를 효율적으로 사용하기 위해, 아직 커밋되지 않은 페이지를 디스크에 반영합니다.
이로 인해 장애 발생 시 Atomicity를 보장할 수 없습니다.
1. 트랜잭션 시작 및 페이지 수정
디스크에서 페이지를 읽어와 버퍼 풀에서 데이터를 수정합니다.
수정할 페이지가 많아 수정 중간에 버퍼는 더티 페이지로 가득 찼습니다.
2. STEAL 발생
아직 데이터 수정이 더 필요합니다.
이때, STEAL 정책에 의해 Page Cleaner가 동작해 아직 커밋되지 않은 페이지를 디스크에 작성합니다.
3. 갑작스러운 장애 발생
나머지 데이터를 수정하던 중, 갑자기 서버가 다운되었습니다.
트랜잭션은 커밋되지 못하고 비정상 종료되었습니다.
4. 재부팅 후 Atomicity 위반
트랜잭션은 실패했으므로 데이터는 수정 전으로 롤백되어야 합니다.
그러나, Page Cleaner가 디스크에 작성한 페이지까지만 디스크에 반영되어있습니다.
No-FORCE 정책은 랜덤 I/O를 최소화하기 위해, 트랜잭션이 커밋되어도 수정된 페이지를 디스크에 즉시 반영하지 않고 한 번에 Flush합니다.
1. 트랜잭션 시작 및 커밋
트랜잭션 커밋 시, 변경된 페이지는 더티 페이지 상태로 버퍼에만 존재합니다.
더티 페이지가 실제로 디스크에 반영되는 것을 기다리지 않습니다.
버퍼에 반영되었으니 트랜잭션 커밋은 성공적으로 이루어집니다.
2. 갑작스러운 장애 발생
Page Cleaner가 더티 페이지들을 디스크에 작성하기 전, 갑자기 서버가 다운되었습니다.
버퍼는 RAM에 존재하므로, 버퍼에 존재하던 더티 페이지는 유실됩니다.
3. 재부팅 후 Durability 위반
트랜잭션은 성공했었으므로, 재부팅 후에도 성공 상태로 남아있어야 합니다.
그러나, 버퍼 유실로 인해 디스크에는 커밋 전 데이터만 존재합니다.
이번 포스팅에서는 랜덤 I/O로 인한 병목을 해결하기 위해 InnoDB가 도입한 버퍼, 이를 관리하는 Page Cleaner의 역할을 살펴보았습니다.
또한, 버퍼를 관리하기 위한 정책인 STEAL / No-STEAL 그리고 FORCE / No-FORCE에 대해 알아보았습니다.
InnoDB를 포함한 대부분의 스토리지 엔진은 STEAL & No-FORCE 정책을 사용합니다.
STEAL & No-FORCE 정책은 버퍼를 효율적으로 사용하고 랜덤 I/O를 최소화한다는 장점이 있지만, Atomocity와 Durability를 보장하지 못한다는 치명적인 단점이 존재합니다.
다음 포스팅에서는, InnoDB가 이 단점들을 어떻게 해결했는지 알아보도록 하겠습니다.
끝까지 읽어주셔서 감사합니다.
참고 문서
https://dev.mysql.com/doc/refman/8.4/en/innodb-buffer-pool.html
MySQL 8.4 Glossaryhttps://d2.naver.com/helloworld/407507
DBMS는 어떻게 트랜잭션을 관리할까? - 오이석|NBP 서비스플랫폼개발센터https://tech.kakao.com/posts/721
MySQL InnoDB Log에 대한 이해 - (1) - christy.seo, sun.j