728x90
※ PostgreSQL: SLRU (Simple Least Recently Used) Buffer Cache.
안녕하세요. 듀스트림입니다.
오늘은 PostgreSQL 내부 구조에서 단순하지만 매우 중요한 버퍼 캐시 관리 시스템인 SLRU에 대해 알아보겠습니다.
1. 배경 지식
LRU (Least Recently Used) 알고리즘이란?
- 가장 오랫동안 사용되지 않은 데이터를 가장 먼저 제거하는 전략입니다.
- LRU는 메모리 캐시, DB 버퍼 관리, 운영체제 페이지 교체 등에서 가장 널리 쓰이는 알고리즘 중 하나입니다.
LRU의 핵심 개념
- 캐시에는 제한된 크기만큼의 데이터를 저장할 수 있습니다.
- 새로운 데이터가 들어왔는데 공간이 부족하면?
- 가장 오래 전에 접근된 데이터를 찾아서 제거
- 가장 최근에 접근한 항목은 가장 늦게 제거
동작 예시 (Step-by-step)
- 예시: 캐시 크기 = 3
- 접근 순서: A → B → C → A → D
| 시점 | 접근한 값 | 캐시 상태 | 설명 |
| 1 | A | A | A 삽입 |
| 2 | B | A B | B 삽입 |
| 3 | C | A B C | C 삽입 |
| 4 | A | B C A | A 재접근 → 가장 최근으로 이동 |
| 5 | D | C A D | B 제거 (가장 오래 사용 X) |
- 결과적으로 LRU는 사용 빈도가 아니라, 얼마나 오래 전 사용되었는지에 따라 제거 여부가 결정됩니다.
LRU 구현 방식
- 연결 리스트 (Linked List) 기반
- 이중 연결 리스트(Doubly Linked List)로 최근 접근한 순서를 유지
- 가장 앞(head): 최근에 접근
- 가장 뒤(tail): 가장 오래된 항목 → 제거 대상
- HashMap + LinkedList 조합 (LRU Cache 구현의 정석)
- HashMap: O(1) 시간으로 데이터 위치 찾기
- LinkedList: 접근 순서 유지 및 LRU 제거
// Key-Value 구조로 저장 + 연결 리스트로 순서 유지
hashmap[key] = pointer to node
PostgreSQL의 LRU와의 차이점 (SLRU)
| 항목 | 일반 LRU 알고리즘 | PostgreSQL SLRU |
| 목적 | 범용 캐시 교체 | 트랜잭션 메타 캐시 |
| 단위 | 항목(Item) | 페이지(page, 8KB) |
| 구현 | LinkedList or Tree | 고정 슬롯 + LRU 카운터 |
| 크기 | 유동적 (설정 가능) | 고정 (8~32 슬롯) |
요약
| 항목 | 내용 |
| 목적 | 캐시 공간이 부족할 때 오래된 항목 제거 |
| 전략 | 가장 최근에 접근된 항목은 보존, 오래된 것부터 제거 |
| 구현 | HashMap + LinkedList, LRU 카운터 등 |
| PostgreSQL | pg_xact 등 SLRU 구조에 LRU 방식 적용 |
2. PostgreSQL의 SLUR (Simple Least Recently Used)
SLRU (Simple LRU)란?
- SLRU(Simple LRU)는 이름 그대로 단순한 LRU 알고리즘 기반의 메모리 캐시 구조입니다.
- PostgreSQL 내부에서 소형의 페이지 기반 저장소(persistent metadata)를 캐시하기 위해 사용됩니다.
주요 역할
- 디스크에 저장되는 트랜잭션 상태 파일(예: pg_xact)을 메모리로 로딩해서 접근 성능이 향상됩니다.
- 핫스탠바이, hint bit 업데이트, MVCC 판단 시 트랜잭션 상태 조회/갱신에 사용됩니다.
- 버퍼 수가 작기 때문에 LRU(Least Recently Used) 알고리즘으로 교체됩니다.
SLRU로 관리되는 목록
| 디렉토리 이름 | 설명 | SLRU 캐시 이름 |
| pg_xact | 트랜잭션 커밋 상태 (예전의 clog) | Xact |
| pg_subtrans | 서브트랜잭션 → 부모 매핑 | Subtrans |
| pg_multixact/members, offsets | 멀티락(MultiXact) 상태 관리 | MultiXactOffset, MultiXactMember |
| pg_notify | LISTEN/NOTIFY용 큐 | Notify |
| pg_commit_ts | 트랜잭션 커밋 타임스탬프 기록 | CommitTs |
- 각 항목은 SLRU 페이지(보통 8KB 단위)로 구성되며, 메모리 내에 일정 수만 캐싱됩니다.
구성 요소 별 SLRU 버퍼 슬롯 수
| 구성 요소 (디렉토리) | 상수 이름 | 기본값 | 설명 |
| pg_xact (구 pg_clog) | NUM_CLOG_BUFFERS | 32 | 트랜잭션 커밋 상태 |
| pg_subtrans | NUM_SUBTRANS_BUFFERS | 8 | 서브트랜잭션 → 부모 매핑 |
| pg_commit_ts | NUM_COMMITTS_BUFFERS | 8 | 커밋 타임스탬프 저장 |
| pg_notify | NUM_NOTIFY_BUFFERS | 8 | LISTEN/NOTIFY용 큐 |
| pg_multixact/offsets | NUM_MXACTOFFSET_BUFFERS | 8 | MultiXact ID → offset |
| pg_multixact/members | NUM_MXACTMEMBER_BUFFERS | 32 | MultiXact Member 저장 |
- 기본값으로 구성되었을 경우 총 96슬롯, 슬롯당 8KB의 크기를 갖고 있으며 총 캐시 메모리 사용량은 약 768KB입니다.
동작 방식
구조
- 내부적으로는 페이지 단위로 데이터를 로딩함 (page_number → 8KB)
- 8~32개의 페이지 버퍼 슬롯만 유지하며, 오래된 페이지는 LRU에 따라 제거
- 슬롯 수는 PostgreSQL 빌드 설정 또는 사용 목적에 따라 다를 수 있습니다.
// src/include/access/clog.h
#define NUM_CLOG_BUFFERS 32
// src/include/access/subtrans.h
#define NUM_SUBTRANS_BUFFERS 8
로딩 & 저장
- SLRU는 필요할 때 디스크에서 페이지를 읽어와 메모리에 로딩함 → SimpleLruReadPage()
- 변경된 페이지는 flush 되며 → SimpleLruWritePage()
모니터링 방법
SELECT * FROM pg_stat_slru;

요약
| 항목 | 설명 |
| 구조 | 단순한 페이지 캐시 (8KB 단위, 8~32개 유지) |
| 사용 대상 | 트랜잭션 메타데이터 (pg_xact 등) |
| 교체 방식 | LRU (Least Recently Used) |
| 사용 목적 | 디스크 접근 최소화, 성능 향상 |
| 관리 방법 | 내부 함수 (SimpleLruReadPage, SimpleLruWritePage) |
| 모니터링 | pg_stat_slru 뷰 사용 |
오늘은 여기까지~
728x90
'PostgreSQL' 카테고리의 다른 글
| PostgreSQL: VACUUM (0) | 2025.05.17 |
|---|---|
| PostgreSQL: MVCC (0) | 2025.05.16 |
| PostgreSQL: Latency Spike 관련 (0) | 2025.05.14 |
| PostgreSQL vs Oracle: 실행 계획 캐싱 전략과 통계 수집의 민감도 비교 분석 (0) | 2025.05.12 |
| PostgreSQL: EXPLAIN을 활용한 선택도 추정 정확도 분석 (0) | 2025.05.11 |