profile image

L o a d i n g . . .

728x90

이전 포스팅에서 DBMS가 데이터를 어떻게 디스크에 저장하는지 살펴봤습니다. 이번 포스팅에서는 디스크에 저장된 데이터를 메모리에 어떻게 적재해서 사용하는지 살펴보겠습니다. 

Buffer Pool 

데이터베이스 버퍼 풀은 데이터베이스 시스템에서 사용하는 메모리 공간을 의미합니다. 이 공간은 데이터베이스 시스템이 일반적으로 사용하는 데이터를 임시로 저장하는 데 사용됩니다. 데이터베이스 버퍼 풀은 데이터를 읽거나 쓸 때 자주 사용되는 데이터를 미리 읽거나 쓰는 것을 돕습니다. 이렇게 하면 데이터베이스 시스템이 자주 사용하는 데이터를 빠르게 읽고 쓸 수 있으며, 이를 통해 전체적인 성능을 향상할 수 있습니다. 버퍼 풀은 프레임(frame)의 배열 형태로 구성됩니다. 디스크의 페이지는 버퍼 풀의 프레임에 적재돼서 활용됩니다. DBMS는 디스크의 페이지 디렉터리(page directory)를 메모리로 페이지 테이블(page table)의 형태로 적재 후 버퍼 풀에 로드된 페이지들을 관리합니다. 

페이지 테이블은 버퍼 풀에 로드된 페이지에 대한 메타데이터 정보를 관리합니다. 
- Dirty Flag : 페이지에 수정이 있었지만 아직 디스크에 반영되지 않았음을 표시하는 플래그 
- Pin / Reference Counter : 특정 페이지를 참조하는 요청의 수

Buffer Pool의 프레임에는 디스크의 페이지가 저장

 

버퍼 풀을 살펴보기 전에 헷갈릴 수 있는 용어부터 짚고 넘어가겠습니다. 

용어

Locks vs Latches 

Lock은 DBMS가 데이터를 트랜잭션으로부터 논리적으로 보호하기 위한 수단입니다. 트랜잭션이 수행될 때 해당 트랜잭션은 자신이 참조하는 데이터에 Lock을 걸고 다른 트랜잭션으로부터 데이터 접근을 방지합니다(DBMS의 isolation level에 따라 달라질 수 있습니다). 

Latch는 DBMS가 데이터를 물리적으로 보호하기 위한 수단입니다. DBMS 내부적으로 사용하는 자료구조에 다른 스레드가 접근할 수 없도록 하는 것이고 운영체제의 Lock과 같습니다. 

Lock = DBMS가 데이터를 논리적으로 보호하기 위해 사용 
Latch = DBMS가 데이터를 물리적으로 보호하기 위해 사용. 운영체제의 Lock과 동일 

Page directory vs Page table 

데이터베이스 page directory는 데이터베이스의 page를 관리하는 자료구조를 의미합니다. 데이터베이스 page는 데이터베이스의 데이터를 저장하는 단위로, 이들은 데이터베이스 버퍼 풀에 저장되어 있습니다. Page directory는 데이터베이스의 page들의 위치와 상태를 기록하고 관리하는 데 사용됩니다. 이를 통해 데이터베이스 시스템은 필요한 데이터를 빠르게 찾을 수 있고, 데이터베이스 버퍼 풀을 효율적으로 관리할 수 있습니다.

Page table은 버퍼 풀 프레임에 로드된 페이지를 매핑한 자료구조입니다. 메모리에 저장되기 때문에 Page directory와 달리 영구적으로 저장되지 않습니다. 

 

Buffer Pool 최적화 

다음으로는 DBMS가 버퍼 풀을 어떻게 최적화하는지 살펴보겠습니다. 

Multiple Buffer Pools 

버퍼 풀을 여러 개 사용하는 방식입니다. 버퍼 풀을 여러 개 사용하면 트랜잭션 간 페이지 경합의 발생 빈도가 낮아지고 지역성(locality)을 높일 수 있습니다. 다음과 같은 방식으로 multiple buffer pool을 사용할 수 있습니다. 

  • Object Id : 데이터에 object id를 함께 저장하고 object id를 통해 특정 버퍼 풀과 매핑시키는 방식 
  • Hashing : 페이지 id에 해시함수를 적용한 결괏값을 사용해서 버퍼 풀과 매핑시키는 방식  

Pre-Fetching 

요청된 페이지만 버퍼 풀에 로드하는 것이 아니라 나중에 필요할 것으로 예상되는 페이지를 미리 버퍼 풀에 로드하는 방식입니다. 예를 들어 순차 탐색, 인덱스 스캔 등의 요청은 다음 페이지를 요청할 확률이 크므로 미리 페이지를 로드해 성능을 높일 수 있습니다.

Scan Sharing 

이 기법은 동일한 쿼리를 수행하는 여러 트랜잭션이 있을 때, 처음 쿼리를 수행하는 트랜잭션이 수행한 데이터 스캔의 결과를 다른 트랜잭션들이 공유하여 사용하는 것을 말합니다. 이렇게 함으로써 여러 트랜잭션이 수행하는 쿼리의 중복을 줄이고, 데이터베이스 시스템의 성능을 향상할 수 있습니다.

Buffer Pool Bypass 

오버헤드를 줄이기 위해 디스크에서 가져온 페이지를 버퍼 풀에 저장하지 않는 방식입니다. 연속되지 않은 대량의 데이터를 읽어야 할 때 효율적입니다. 또한 sorting, join 등 작업에 필요한 임시 데이터를 가져올 때 유용한 방식입니다. 

OS Page Cache 

대부분의 디스크 작업은 운영체제의 API를 사용합니다. 별다른 처리 없이 디스크 작업을 한다면 운영체제는 filesystem cache를 사용해 페이지를 캐시 합니다. 하지만 대부분의 DBMS는 direct I/O(O_DIRECT)를 활용해 운영체제의 페이지 캐싱을 활용하지 않습니다. DBMS가 direct I/O를 사용하는 이유는 버퍼 풀을 통해 직접 페이지를 캐싱하고 관리하는 게 효율적이라 판단하기 때문입니다. 버퍼 풀을 통해 페이지를 직접 관리함으로써 DBMS에 최적화된 페이지 eviction policy, file I/O 조작이 가능하기 때문입니다. 

 

Buffer Replacement 정책 

메모리의 크기는 보통 디스크보다 작기 때문에 디스크의 모든 데이터를 메모리에 올릴 수 없습니다. 따라서 메모리에 가용 공간이 부족하면 메모리에 로드된 페이지를 제거해야 합니다. 대표적인 방식에 대해 살펴보겠습니다. 

LRU(Least Recently Used) 

메모리에 적재된 페이지에는 가장 최근에 페이지가 요청된 timestamp를 함께 저장합니다. 버퍼 풀에서 페이지를 제거할 때 timestamp를 참고해서 가장 오래된 timestamp를 가진 페이지를 제거합니다. 

Clock 

버퍼 풀에 로드된 각 페이지는 reference bit을 가지고 있습니다. 각 reference bit은 페이지 접근 요청이 있을 때 1로 설정됩니다. Clock의 "clock hand"는 주기적으로 각 페이지를 방문해서  reference bit이 1인 페이지를 reference bit을 0으로 설정합니다. 만약 reference bit이 0이라면 해당 페이지는 버퍼 풀에서 제거됩니다. 

Clock의 동작방식

LRU와 Clock은 구현이 단순하다는 장점이 있습니다. 하지만 sequential flooding 현상이 발생할 수 있습니다(Sequential flooding에 대한 상세한 설명은 링크를 참고하세요). Sequential flooding을 방지하기 위해서 사용할 수 있는 정책에 대해 살펴보겠습니다. 

LRU-K

LRU는 가장 최근에 페이지가 요청된 timestamp를 기준으로 제거할 페이지를 선정합니다. LRU-K의 경우 최근 K번째로 페이지가 요청된 timestamp를 기준으로 제거할 페이지를 선정합니다. 아래의 그림에서는 page 0은 t1이 접근된 3번째(LRU-3) 최근 시간이고, page 1은 t2가 페이지에 접근된 3번째(LRU-3) 최근 시간입니다. 두 시간을 비교했을 때 page 1이 더 최근에 접근됐기 때문에 만약 페이지를 제거해야 한다면 page 0이 제거됩니다. 

LRU-K

Localization

트랜잭션 또는 쿼리 단위로 페이지 제거를 수행합니다. 이는 서로 다른 트랜잭션 또는 쿼리에 의해서 로드된 페이지에 의해 버퍼 풀이 오염되지 않도록 방지합니다. 

 

마무리 

이번 포스팅을 통해 디스크의 데이터를 어떻게 메모리에 로드하고, 로드된 데이터를 어떻게 관리하는지에 대해 살펴봤습니다. 다음 포스팅에서는 DBMS 해시 테이블 자료구조에 대해 살펴보겠습니다. 

728x90
복사했습니다!