Buffer Pool

버퍼 풀 (buffer pool)은 메인 메모리 내에서 데이터과 인덱스 데이터가 접근될 때 해당 데이터를 캐시하는 영역이다. 버퍼 풀을 통해서 자주 접근되는 데이터를 메모리에서 바로 획득할 수 있으며 전체 작업의 수행 속도를 증가 시킬 수 있다. MySQL을 위한 서버에서는 물리 메모리의 최대 80%까지를 InnoDB의 버퍼 풀로 할당하여 사용하는 경우가 많다.

대량의 읽기 요청을 효율적으로 처리하기 위해, 버퍼 풀은 데이터를 page 단위로 나누어 관리하며, 한 페이지에는 여러 로우 (row)가 속할 수 있다. 버퍼 풀 캐시의 효율적인 관리를 위해서, 버퍼풀 내의 페이지는 링크드 리스트 (linked list) 로 관리한다. 일종의 LRU 알고리즘에 따라 잘 접근되지 않는 데이터 페이지는 캐시에서 제거하는 방식으로 버퍼풀을 관리한다.

실제 사용하고자 하는 애플리케이션에서 자주 사용되는 데이터를 버퍼풀에 어떤 방식으로 캐싱할지 알아 두는 것은 MySQL 튜닝의 중요한 부분이다.

버퍼풀의 LRU 알고리즘

위에서 언급했듯이 InnoDB의 버퍼풀은 링크드 리스트를 이용하여 least recentrly used (LRU) 알고리즘을 통해 관리한다. 새로운 페이지를 버퍼풀에 추가하기 위한 페이지 공간이 필요한 경우, 접근이 가장 오래된 페이지를 선정하여 버퍼풀에서 제거하고, 새로운 페이지를 리스트의 중간지점에 삽입하는데 이를 midpoint insertion이라고 한다. 중간지점 삽입 전략을 통해 버퍼풀 전체 리스트를 아래에서 설명할 두가지 리스트로 나누어 관리한다.

  • 전체 리스트의 head는, 최근에 접근된 (young) 페이지들의 리스트를 가리킨다.
  • 전체 리스트의 tail은, 접근 시기가 가장 오래된 (old) 페이지를 가리킨다.

Figure 1. 버퍼풀을 리스트 형태로 관리하는 구조

innodb buffer pool list

이러한 알고리즘을 통해 사용자 쿼리에서 가장 자주 접근 되는 데이터 페이지들을 new sublist에 보관한다. old sublist에는 잘 접근 되지 않은 데이터를 저장하며, 이후에 eviction 대상으로 선정된다.

기본적으로 알고리즘 전체 순서는 아래와 같다.

  • 버퍼 풀의 3/8 은 old sublist로 사용된다.
  • 버퍼 풀 전체의 midpointnew sublist의 tail과 old sublist의 head가 만나는 지점이다.
  • InnoDB가 새로운 데이터 페이지를 버퍼 풀로 읽어오는 경우에는, midpoint (old sublist의 head)에 삽입한다. 여기서 읽어오는 데이터에는 실제 사용자 쿼리에 의해 요청된 데이터도 있을 수 있지만, read-ahead에 의해 자동적으로 읽어오는 데이터도 포함된다.
  • old sublist에 존재하는 페이지를 접근할 경우 해당 페이지는 “young”이라고 판단 되며, 버퍼풀 전체의 head (new sublist의 head)로 이동된다. 사용자 쿼리나 중간 작업에 의해 실제로 필요해서 접근된 페이지의 겨웅 곧바로 young으로 변경되지만, read-ahead 작업에 의해 접근된 페이지의 경우에는 young으로 취급하지 않는다.
  • 데이터베이스의 작업이 계속 진행됨에 따라서, 버퍼풀에 상주하는 데이터 페이지들 중 자주 접근 되지 않는 페이지는 자연스럽게 리스트이 tail로 이동하게 된다. 이는 직접적으로 데이터를 tail로 옮기는 것이 아닌, young 이 되는 페이지가 앞으로 이동하면서 발생한다. 추가로 old sublist의 페이지들은 새로운 페이지가 midpoint에 삽입 되면서 tail에 가까워지게 된다. 페이지 eviction시점까지 접근되지 않고 tail에 남아있는 페이지는 결국 버퍼 풀에서 삭제되게 된다.

기본적으로 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 ResistantConfiguring InnoDB Buffer Pool Prefetching (Read-Ahead)를 읽어보길 바란다.

InnoDB의 기본 모니터링 아웃풋의 BUFFER POOL AND MEMORY 섹션에 LRU 알고리즘에 관한 정보가 있으므로, 자세히 보고싶은 경우 Monitoring the Buffer Pool Using the InnoDB Standard Monitor를 찹조하길 바란다.

버퍼 풀 관련 설정하기

InnoDB 전체 성능 향상을 위해서 여러가지 측면의 설정을 변경할 수 있다. 아래는 설정에 관한 설명이다.

  • 이상적으로는, 같은 서버에서 동작하는 다른 프로세스들이 쓸만큼 충분한 공간을 남겨둔 채, 대부분의 메모리 공간을 InnoDB의 버퍼 풀로 할당하는 것이 가장 좋다. 버퍼풀의 크기가 클 수록 InnoDB는 in-memory 데이터베이스 처럼 동작하게 되고, 이는 데이터를 디스크에서 한번만 읽으면 되고 이후에는 계속 버퍼풀에서 읽을 수 있게 됨을 말한다. 이러한 설정을 위해서는 Configuring InnoDB Buffer Pool Size을 참조하길 바란다.
  • 충분한 메모리가 있는 64비트 환경에서는 버퍼 풀을 여러 파트로 쪼개서 관리할 수 있다. 이를 통해서 다중 사용자 환경에서 하나의 버퍼풀에 발생하는 여러가지 경합 현상을 완화할 수 있다. 이와 관련된 설정은 Configuring Multiple Buffer Pool Instances를 참조하길 바란다.
  • 테이블 스캔이나 read-ahead 같은 특정한 상황속에서 많은 데이터가 읽어져 버퍼 풀의 기존 데이터를 삭제하는 경우에도, 사용자가 원하는 데이터를 버퍼풀에 계속해서 상주하도록 만들 수 있다. 이와 관련된 설정은 Making the BUffer Pool Scan Resistant를 참조하길 바란다.
  • 곧 요청될 데이터를 미리 예측하여 사용자의 실제요청과 관계없이 비동기적으로 미리 데이터 페이지를 읽어오는 read-ahead 작업을, 언제/어떤방식으로 수행할 지 직접 지정할 수 있다. 이와 관련된 설정은 Configuring InnoDB Buffer Pool Prefetching (Read-Ahear)를 참조하길 바란다.
  • 버퍼풀의 데이터에 대한 백그라운드 플러시 작업을 얼마나 자주 수행할지, 혹은 플러시 주기를 자동적으로 조절할지 말지에 대한 설정을 할 수 있다. 이와 관련된 설정은 Configuring InnoDB Buffer Pool Flushing를 참조하기 바란다.
  • 버퍼 풀 플러시와 관련하여 더 큰 성능 향상으로 위해 세밀하게 조절할 수 있다. 이와 관련된 설정은 Fine-tuning InnoDB Buffer Pool Flushing을 참조하기 바란다.
  • 서버를 재시작 한 이후에 기존의 버퍼 풀 상태를 그대로 유지하여, 길어질 수 있는 warm-up 시간을 줄일 수 있다. 이와 관련된 설정은 [Saving and Restoring the Buffer Pool State]를 참조하기 바란다.

InnoDB Standard Monitor와 버퍼 풀 모니터링

SHOW ENGINE INNODB STATUS 쿼리를 통해 확인할 수 있는 InnoDB의 기본 모니터링 툴에는 버퍼 풀과 관련된 작업에 대한 통계도 제공한다. 버퍼 풀과 관련된 통계자료는 모니터링 아웃풋의 BUFFER POOL AND MEMORY 섹션에서 확인할 수 있으며, 아래의 내용과 유사하게 출력된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 2198863872
Dictionary memory allocated 776332
Buffer pool size 131072
Free buffers 124908
Database pages 5720
Old database pages 2071
Modified db pages 910
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 4, not young 0
0.10 youngs/s, 0.00 non-youngs/s
Pages read 197, created 5523, written 5060
0.00 reads/s, 190.89 creates/s, 244.94 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not
0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read
ahead 0.00/s
LRU len: 5720, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]

아래의 표는 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 variablesINNODB_BUFFER_POOL_STATS 테이블도 InnoDB의 기본 모니터링 아수풋에서 볼 수 있는 결과값들을 제공한다. 자세한 내용은 Querying the INNODB_BUFFER_POOL_STATS Table를 참조하기 바란다.