MySQL의 Storage Engine은 Innodb이다.
Innodb은 Transaction-Safe한 Storage Engine이며 자체적으로 메인메모리 안에 데이터 캐싱과 인덱싱을 위한 버퍼 풀을 가지고 있습니다.
이번 글에서는 데이터 캐싱과 관련해서 Cache Miss가 발생했을 때 MySQL이 이를 어떻게 처리하는지 공부한 것을 정리해보려고 합니다.
먼저 Innodb의 버퍼 풀을 살펴 보겠습니다.
InnoDB의 Buffer pool의 구조는 위와 같습니다.
이러한 buffer pool을 다르게 말하면 LRU list라고 이야기합니다. 이는 page 교체 알고리즘을 LRU 방식을 사용하기 때문에 붙여진 이름이라고 생각합니다.
Page Hit
page들이 어느 영역에 속하는지에 따라 Page hit 발생시 처리하는 정책이 다릅니다.
1. New Sublist : head로 부터 특정 거리 안에 있다면 page 이동이 발생하지 않습니다. 특정 거리 밖에 있다면 head 다음으로 이동하게 됩니다.
2. Old Sublist : page hit이 발생하면 New Sublist의 head부분으로 page를 이동 시킵니다.
Page Miss 발생
page miss가 발생한다면 Victim page를 찾아야합니다.
Innodb가 page miss가 발생하면 탐색하게 되는 장소는 총 3가지 입니다.
- Free List : Free page로 채워져있는 List
- LRU List : buffer pool
- Disk
먼저 Free List는 데이터가 채워져 있지 않은 page들이 들어있는 List입니다.
이제 부터 이 글의 핵심인 Page Miss 발생 시에 MySQL은 어떤 과정을 거쳐서 Victim page를 찾는지 보겠습니다.
Victim page를 찾기 위해 Innodb가 실행 시키는 함수는 MySQL-server에서 buf0lru.cc에 있는 buf_LRU_get_free_block 함수 입니다.
해당 함수가 Page Miss가 발생했을 때 MySQL의 Free Space Management를 관리하는 코드 입니다.
해당 코드의 작동 과정을 살펴보겠습니다.
buf_LRU_get_free_block
해당 코드는 n_iteration이라는 인자를 중심으로 돌아갑니다.
여기서 n_iteration이란 서버가 free page를 찾기위해 시도를 몇번 했는지에 관한 변수입니다.
처음 free_block을 찾으려고 시도했을 때는 n_iteration이 0입니다.
👉 n_iteration이 0일 때
1. buf_LRU_get_free_block(buf_pool) 실행
DBMS는 먼저 free list를 통해 free page를 탐색하게 됩니다.
해당 과정에서 free page를 찾게 되면 실행이 종료되지만
찾지 못하게 되면 인수로 받은 buffer 구조체에 try_LRU_Scan을 True로 설정하게 됩니다.
2. LRU Scan : buf_LRU_scan_and_free_block(buf_pool)실행
위에서 보았던 LRU list에서 clean page를 찾게 됩니다.
clean page란 데이터가 들어있지만 새로 데이터가 write되어있지 않은 page를 의미합니다.
여기서 clean page를 찾으면 이를 free list에 free page로 옮기고 1번을 재실행하여 free page를 찾게 됩니다.
이때 scan을 하는 방향은 old list의 tail부터 new list의 헤드까지이고 n_iteration이 0일 때는 lru_scan_depth(default=1024)만큼 탐색을 진행하게 된다.
해당 과정 clean page를 찾지 못하게 되면 3번째 단계로 넘어가게 됩니다.
3. Flush Page : buf_flush_single_page_from_LRU(buf_pool) 실행
clean page를 찾지 못했기 때문에 I/O 작업을 통해 free page를 찾으려고 하는 작업이다.
해당 과정을 통해 LRU tail부터 scan depth까지 있는 dirty page 중 flush가 가능한 page들에 대해서 flush를 진행한다.
이후 해당 page들은 free page가 되어 free list에 들어간다.
이후 n_iteration이 증가하게 된다.
👉 n_iteration이 1일 때
n_iteration이 1일 때는 n_iteration이 0과 같을 때 같은 방식으로 작동한다. 단, LRU scan depth를 기존에 사용하고 있고 파라미터가 아닌 LRU list전체를 대상으로 탐색을 진행한다.
👉 n_iteration이 2이상 20 이하일 때
해당 과정은 n_iteration이 1일 때와 동일하게 작동하지만 매 iteration마다 10ms sleep을 하게 된다.
👉 n_iteration이 20 이상일 때
이정도까지 돌았는데 free page를 찾지 못했다면 서버가 os에 event를 발생 시킨다. 이런 경우 주로 메모리 공간 부족, 커널 문제등으로 발생할 것이라는 오류 메세지를 로그상에 남기게 된다.
이상으로 MySQL 5.7에서 Buffer Miss가 발생했을 때 page를 찾는 과정을 정리해보았습니다. 공식 문서와 소스코드를 보면서 공부를 했기 때문에 틀린 부분이 있을 수 있습니다.
틀린 부분이 있으면 댓글 남겨주시면 감사하겠습니다.
출처
https://dev.mysql.com/doc/refman/5.7/en/
https://github.com/mysql/mysql-server/blob/5.7/storage/innobase/buf
긴 글 읽어주셔서 감사합니다.
📧 : may3210@g.skku.edu