PostgreSQL 9.6 성능이야기 - 2장. Shared Buffer 동작 원리

2021. 3. 20. 09:59카테고리 없음

Shared Buffer의 구조

해시 테이블 해시 엘리먼트 버퍼 디스크립터 버퍼 풀

 

Lock

LW (oracle latch) Spin (oracle mutex)

Shared Buffer 내에 블록을 읽는 경우 (Memory Read)

1. BufferTag 생성 2. 해당 버퍼 파티션#에 대한 LW획득 (SHARE 모드) 3. hashvalue를 이용해서 해시 테이블 버킷 번호 계산 4. 버퍼 디스크립터에 핀 설정 5. 버퍼 파티션#h에 대한 LW 락 해제 6. 버퍼 풀 배열 인덱스[n]번 내용을 읽는다. 7. PIN 해제

Disk Read가 발생한 경우

Shared Buffer에 존재하지 않는 경우 Disk Read를 통해 해당 블록을 Shared Buffer로 로딩한 후에 해당 버퍼를 읽는다 상당히 복잡, 조회 과정에서 LW 락과 함께 추가적으로 Spin 락이 사용 됨.

 

Victim 버퍼

Shared Buffer 내에 empty 버퍼가 없다면 Shared Buffer 내의 버퍼를 디스크에 기록해야 한다.

이 때 디스크에 기록되는 버퍼

(Disk Read시 Shared Buffer에 기록하게 되는데, 이때 Shared Buffer에 기록할 빈 공간이 없다면 공간을 확보하기 위해 기존 Shared Buffer 내의 정보를 Disk에 써서 emtpy 버퍼를 확보해야 함 으로 이해)

Clock Sweep

Victim 버퍼를 선정하기 위해 PostgreSQL에서 사용하는 Buffer Replacement 알고리즘

(Shared Buffer 내의 어떤 버퍼를 비울지)

NFU(Not Frequently Used) 알고리즘의 일종, 덜 사용된 버퍼를 Victim 버퍼로 선정.

PostgreSQL에서는 버퍼마다 액세스 된 횟수를 관리하고 있음. (액세스 횟수 최대치 = BM_MAX_USAGE_COUNT 기본값 5)

버퍼 디스크립터의 처음과 끝을 원처럼 연결하여 시계 방향으로 탐색하며(Clock)

1씩 감소, 0이 되면 Shared Buffer 에서 정리(Sweep)

만일 Victim 버퍼가 dirty 버퍼(BM_DIRTY 비트가 1)이면 해당 버퍼 내용을 디스크에 기록

(dirty 버퍼가 아닌 경우에는 변화가 없다는 소리니 굳이 디스크에 쓰기 작업없이 날려버려도 된다 라고 이해)

Bulk IO 처리를 위한 IO 전략과 Ring Buffer

Seq Scan으로 인해 큰 테이블의 모든 블록이 Shared Buffer로 로딩되는 문제를 해결하기 위한 방법

PostgreSQL에서 구분하는 IO 유형

1. Normal Random Access 2. Large read-only scan (BULK READ) 3. Large multi-block write (BULK WRITE) 4. VACUUM

Normal 요청(1)을 제외한 모든 요청은 Ring Buffer를 사용한다.

예를 들어 BULK READ(2)의 경우 Shared Buffer 크기의 1/4 이상인 테이블에 대한 Seq Scan은 Ring Buffer를 이용하게 됨.

이 때 Ring Buffer 사이즈는 32블록 (PG버전, IO 유형별 달라질 수 있을 듯)

그렇다면 해당 BULK READ 의 경우 Shared Buffer 영역에서 최대 32블록만 차지하게 됨 이라고 이해

(다만 n번 호출시 32블록*n번..)

Seq Scan 으로 인해 Shared Buffer 가 특정 요청에 대한 블록으로 모두 채워지는 것을 막기 위해

특정 조건에 해당하는 요청의 경우 (예: Shared Buffer 크기 1/4 이상인 테이블에 대한 Seq Scan)

무한정 Shared Buffer에 기록되는 것이 아니라 Ring Buffer를 사용하여 Ring Buffer 사이즈만큼만 담기게 되므로 문제 해결 이라고 이해

참고로 Ring Buffer 역시 Shared Buffer 내에 존재 함.