버퍼 풀 (buffer pool)은 메인 메모리 내에서 데이터과 인덱스 데이터가 접근될 때 해당 데이터를 캐시하는 영역이다. 버퍼 풀을 통해서 자주 접근되는 데이터를 메모리에서 바로 획득할 수 있으며 전체 작업의 수행 속도를 증가 시킬 수 있다. MySQL을 위한 서버에서는 물리 메모리의 최대 80%까지를 InnoDB의 버퍼 풀로 할당하여 사용하는 경우가 많다.
대량의 읽기 요청을 효율적으로 처리하기 위해, 버퍼 풀은 데이터를 page 단위로 나누어 관리하며, 한 페이지에는 여러 로우 (row)가 속할 수 있다. 버퍼 풀 캐시의 효율적인 관리를 위해서, 버퍼풀 내의 페이지는 링크드 리스트 (linked list) 로 관리한다. 일종의 LRU 알고리즘에 따라 잘 접근되지 않는 데이터 페이지는 캐시에서 제거하는 방식으로 버퍼풀을 관리한다.
실제 사용하고자 하는 애플리케이션에서 자주 사용되는 데이터를 버퍼풀에 어떤 방식으로 캐싱할지 알아 두는 것은 MySQL 튜닝의 중요한 부분이다.
위에서 언급했듯이 InnoDB의 버퍼풀은 링크드 리스트를 이용하여 least recentrly used (LRU) 알고리즘을 통해 관리한다. 새로운 페이지를 버퍼풀에 추가하기 위한 페이지 공간이 필요한 경우, 접근이 가장 오래된 페이지를 선정하여 버퍼풀에서 제거하고, 새로운 페이지를 리스트의 중간지점에 삽입하는데 이를 midpoint insertion이라고 한다. 중간지점 삽입 전략을 통해 버퍼풀 전체 리스트를 아래에서 설명할 두가지 리스트로 나누어 관리한다.
Figure 1. 버퍼풀을 리스트 형태로 관리하는 구조
이러한 알고리즘을 통해 사용자 쿼리에서 가장 자주 접근 되는 데이터 페이지들을 new sublist에 보관한다. old sublist에는 잘 접근 되지 않은 데이터를 저장하며, 이후에 eviction 대상으로 선정된다.
기본적으로 알고리즘 전체 순서는 아래와 같다.
기본적으로 read 쿼리에 의해 직접 접근되는 페이지들은 곧바로 new sublist로 이동하여 더 오랜기간 버퍼 풀에 상주할 수 있게 된다. table scan 쿼리 (mysqldump, 나 WHERE
가 없는 SELECT
쿼리) 에 대량의 데이터가 한번에 버퍼 풀에 삽입되는 경우, midpoint 삽입에 의해 old sublist의 대량 데이터들이 모두 버퍼풀에서 삭제되게 된다. 이와 유사하게, read-ahead 작업에 의해 midpoint에 삽입 되었던 데이터가, 단 1번만 접근되더라도 new sublist의 head로 이동하게 되어, 실제 자주 접근되는 데이터가 old tail에 가까워지는 일이 발생할 수도 있다. 이러한 현상을 최적화 하기 위해서는 Making the Buffer Pool Scan Resistant와 Configuring InnoDB Buffer Pool Prefetching (Read-Ahead)를 읽어보길 바란다.
InnoDB의 기본 모니터링 아웃풋의 BUFFER POOL AND MEMORY
섹션에 LRU 알고리즘에 관한 정보가 있으므로, 자세히 보고싶은 경우 Monitoring the Buffer Pool Using the InnoDB Standard Monitor를 찹조하길 바란다.
InnoDB 전체 성능 향상을 위해서 여러가지 측면의 설정을 변경할 수 있다. 아래는 설정에 관한 설명이다.
SHOW ENGINE INNODB STATUS
쿼리를 통해 확인할 수 있는 InnoDB의 기본 모니터링 툴에는 버퍼 풀과 관련된 작업에 대한 통계도 제공한다. 버퍼 풀과 관련된 통계자료는 모니터링 아웃풋의 BUFFER POOL AND MEMORY 섹션에서 확인할 수 있으며, 아래의 내용과 유사하게 출력된다.
1 | ---------------------- |
아래의 표는 InnoDB 모니터링 쿼리를 통해 볼 수 있는 버퍼 풀 통계에 대한 설명이다.
출력 되는 정보중 초단위 정보들은 서버 시작부터 현재까지의 누적 자료가 아닌, 가장 마지막 모니터링 쿼리부터 현재까지의 측정 자료이다.
표. InnoDB 버퍼 풀 통계 자료 내용
항목 이름 | 출력되는 내용 |
---|---|
Total memory allocated | 버퍼 풀에 할당 된 전체 메모리의 바이트 단위 크기 |
Dictionary memory allocated | InnoDB의 데이터 딕셔너리에 할당된 메모리의 바이트 단위 크기 |
Buffer pool size | 버퍼 풀에 할당된 페이지의 갯수 |
Free buffers | 버퍼 풀의 free list에 존재하는 페이지의 갯수 |
Database pages | 버퍼 풀의 LRU 전체 리스트에 존재하는 페이지의 갯수 |
Old database pages | 버퍼 풀의 old LRU sublist에 존재하는 페이지의 갯수 |
Modified db pages | 현재 버퍼 풀에서 dirty (변경)된 페이지의 갯수 |
Pending reads | 버퍼 풀에 삽입되길 기다리는 앞으로 read될 page 갯수 |
Pending writes LRU | old sublist에 존재하는 페이지 들 중 disk로 플러시 되어야 하는 old page의 갯수 |
Pending writes flush list | 버퍼풀 전체 리스트 중 체크 포인트 시에 플러시 되어야 하는 페이지의 갯수 |
Pending writes single page | 버퍼풀의 전체 리스트 중에서 single page write를 기다리는 페이지 갯수 |
Pages made young | 버퍼풀 전체 LRU 리스트에서 “young”으로 변경된 (old sublist에서 new sublist로 이동 된) 페이지 갯수 |
Pages made not young | 버퍼풀 전체 LRU 리스트에서 “Young”으로 변경되지 못한 (old sublist에만 계속 상주하는) 페이지 갯수 |
youngs/s | old sublist에 존재하는 페이지를 “young”으로 변경하는 페이지 접근 횟수를 초단위로 측정한 값. 자세한 내용은 표 아래의 note 참조 |
non-youngs/s | old sublist에 접근은 하지만 “young”으로 만들지 않는 접근 횟수. 자세한 내용은 표 아래의 note 참조 |
Pages read | 버퍼 풀에서 read된 페이지 갯수 (hit 인듯) |
Pages created | 버퍼 풀 내에 생성된 페이지 갯수 |
Pages written | 버퍼 풀에서 disk로 쓰여진 페이지의 갯수 |
reads/s | 초당 버퍼풀 페이지 읽기 횟수 |
creates/s | 초당 버퍼풀에 생성되는 페이지 갯수 |
writes/s | 초당 버퍼풀에서 disk로 write되는 페이지 갯수 |
Buffer pool hit rate | 버퍼 풀 히트율. 버퍼 풀 메모리 네에서 바로 읽히는 페이지 vs 디스크에서 읽어오는 페이지 |
Young-making rate | 페이지 접근이 young 페이지를 생성하는 작업을 유발하는 경우에 대한 비율 |
not (young-making rate) | 페이지를 접근 함에도 young 페이지 생성을 하지 않는 경우에 대한 비율. 위와 반대이다. |
Pages read ahead | 초당 read ahead 작업 횟수 |
Pages evicted without access | 버퍼풀에서 접근되지 않은 데이터 페이지들의 초당 eviction 페이지 갯수 |
Random read ahead | 초당 random read ahead 작업 횟수 |
LRU len | 버퍼풀 전체 LRU 리스트의 페이지 갯수 |
unzip_LRU len | 버퍼풀 전체 unzip_LRU 리스트의 페이지 갯수 |
I/O sum | 최근 50초간 버퍼풀 전체 LRU 리스트에서 접근된 페이지 수 |
I/O cur | 전체 기간동안 버퍼풀 전체 LRU 리스트에서 접근된 페이지 수 |
I/O unzip sum | 최근 50초간 버퍼풀 전체 unzip_LRU 리스트에서 접근된 페이지 수 |
I/O unzip cur | 전체 기간동안 버퍼풀 전체 unzip_LRU 리스트에서 접근된 페이지 수 |
Notes:
youngs/s
는 old sublist 에 있는 페이지들에 대해서만 측정된다. 평균값은 전체 페이지 갯수에 대한 평균이 아닌, 페이지 접근 횟수에 대한 평균값이다. 동일한 페이지에 여러번의 접근이 발생할 경우 해당 접근 횟수를 모두 계산한다. 대량 읽기 작업이 없음에도 불구하고 매우 적은 수의 youngs/s
가 기록된 경우에는 young 판별을 위한 delay time을 줄이거나, old sublist가 버퍼 풀에서 차지 하는 비중을 늘릴 필요가 있다. old sublist의 비중을 늘릴 경우 페이지 들이 old tail까지 도달하는데 더 많은 시간이 걸리고, 그 추가된 기간동안 다시 접근 되어 young으로 변할 가능성을 늘릴 수 있다.non-youngs/s
는 old sublist에 있는 페이지들에 대해서만 측정된다. 평균값은 전체 페이지 갯수에 대한 평균이 아닌, 페이지 접근 횟수에 대한 평균값이다. 동일한 페이지에 여러번의 접근이 발생할 경우 해당 접근 횟수를 모두 게산한다. 대량의 테이블 스캔 (1번만 읽고 버리는 페이지를 대량 생성)을 수행함에도 불구하고 non-youngs/s
가 작다면, young으로 판별을 위한 delay를 늘려서 불필요한 youngs를 방지할 수 있다.young-making
비율 통계는 old sublist만이 아닌 버퍼 풀 전체 리스트를 대상으로 측정한 값이다. young-making
비율이나 not
비율은 버퍼 풀의 전체 히트 레이트보다는 거의 낮은 값을 갖는다. old sublist에 존재하는 페이지에 대한 히트는 해당 페이지를 new sublist로 옮기는 작업을 유발하고, new sublist에 존재하는 페이지에 대한 히트는 해당 페이지가 new head에서 일정 거리만큼 멀어진 경우에만 다시 new head로 옮기는 작업을 유발한다. not (young-making rate)
는 innodb_old_blocks_time 설정에 의해 정의된 delay 시간에 의해 페이지 접근이 young-making으로 판별되지 않는 접근이나 new sublist에 대한 접근 중 new head로 이동하는 작업을 유발하지 않는 접근의 비율을 나타낸다. 이 값은 위의 설명처럼 old sublist뿐만 아닌 버퍼풀의 전체 LRU 리스트에 대한 값을 측정한다.버퍼 풀의 server status variables나 INNODB_BUFFER_POOL_STATS 테이블도 InnoDB의 기본 모니터링 아수풋에서 볼 수 있는 결과값들을 제공한다. 자세한 내용은 Querying the INNODB_BUFFER_POOL_STATS Table를 참조하기 바란다.