캐시 조직: SQLite가 실제로 메모리에서 페이지를 보관하는 방법
I’m happy to translate the article for you, but I’ll need the full text of the post (the portions you’d like translated) pasted here. Please provide the content you want translated, and I’ll keep the source line, formatting, and code blocks exactly as they are while translating the rest into Korean.
페이지어 및 페이지 캐시 개요
지금까지 페이지어를 트랜잭션 관리자이자 게이트키퍼로서 이야기했습니다.
이제 페이지 캐시 자체의 물리적 조직—SQLite가 작동하는 동안 데이터베이스 페이지가 실제로 존재하는 장소—에 대해 살펴보겠습니다. 여기서는 “페이지”가 추상적인 개념을 넘어 구조, 메타데이터, 규칙을 가진 실제 메모리 조각이 됩니다.
- 모든 페이지어는 정확히 하나의 페이지 캐시를 소유하며, 이는 PCache 라는 핸들러 객체를 통해 관리됩니다.
- 페이지어는 캐시 내부를 직접 관리하지 않습니다; PCache 객체에 대한 참조를 보유하고 정의된 인터페이스를 통해 통신합니다. 이러한 간접화는 SQLite가 플러그 가능한 캐시 구현을 지원하도록 합니다—사용자가 자체 캐시 모듈을 제공하면 SQLite가 이를 사용합니다.
내장 구현은 pcache1.c에 있으며, 우리가 공부할 내용은 바로 이 파일입니다.
PCache 객체
PCache 객체 내부에서 포인터(pCache)는 실제로 사용되는 캐시 엔진을 가리킵니다. 페이지 관리자의 관점에서 캐시는 검은 상자와 같으며, 빠르고 예측 가능하고 교체 가능합니다.
해시‑테이블 슬롯 구성
SQLite는 페이지를 평면 리스트에 저장하지 않습니다. 캐시된 페이지는 슬롯에 저장되며, 해시 테이블을 통해 인덱싱됩니다:
- 해시 테이블은 처음에 비어 있습니다. 페이지가 요청될 때마다 슬롯이 생성되어 삽입됩니다.
- 슬롯의 총 개수는
PCache.nMax로 제한됩니다. - 기본 제한값:
- 메인 및 첨부된 데이터베이스에 2000 페이지
- 임시 데이터베이스에 500 페이지
- 메모리‑내 데이터베이스는 제한 없음(사용 가능한 주소 공간에만 제한)
이 설계는 조회 속도를 빠르게 유지하면서 메모리 무한 증식을 방지합니다.
PgHdr 구조
각 캐시된 페이지는 PgHdr 객체로 표현되며, 이는 페이지 관리자가 필요로 하는 모든 정보를 담고 있습니다(트리 모듈은 원시 페이지 메모리만을 봅니다). PgHdr는 페이지 관리자가 정확성을 보장하기 위해 필요한 모든 데이터를 포함합니다:
| 필드 | 의미 |
|---|---|
pgno | 해당 데이터베이스 페이지 번호 |
dirty | 수정되었는지 여부 |
needSync | 다시 쓰기 전에 저널을 플러시해야 하는지 여부 |
nRef | 참조 횟수 (고정) |
pDirtyNext / pDirtyPrev | 더러운 페이지 리스트의 링크 |
nRef > 0이면 페이지가 고정된 상태이며, 현재 사용 중이어서 해제할 수 없습니다.nRef == 0이면 페이지가 고정 해제된 상태이며, 재사용이 가능합니다.
PCache1 – SQLite의 내장 캐시
SQLite의 내장 캐시(PCache1)는 또 다른 계층을 추가합니다. 각 해시‑테이블 슬롯은 PgHdr1 객체로 표현됩니다. 메모리상의 슬롯 레이아웃은 대략 다음과 같습니다:
[ PgHdr | page image | private space | (optional recovery pointers) ]
- Page image – 원시 데이터베이스 페이지.
- Private space – 트리 모듈이 페이지당 메모리 상태를 저장하는 데 사용합니다.
- Recovery metadata – 인‑메모리 데이터베이스용으로 여기 저장됩니다.
페이지가 캐시로 들어올 때 전체 블록이 0으로 초기화되어, 재사용 시 오래된 상태가 새 페이지에 섞이는 것을 방지합니다.
모든 슬롯은 PCache1.apHash 배열을 통해 접근할 수 있으며, 각 항목은 무순서 단일 연결 리스트 형태의 버킷을 가리킵니다—단순하고 빠르며 특별한 트릭이 없습니다.
캐시 그룹 모드 (옵션)
캐시 그룹 모드가 활성화되면:
- 여러
PCache1인스턴스가 하나의 그룹에 배치됩니다. - 한 캐시의 고정되지 않은 페이지가 다른 캐시에서 재활용될 수 있습니다.
- 메모리 압박을 개별 캐시가 아니라 전역적으로 처리합니다.
이는 동일 프로세스 내에 많은 연결이 존재하고 메모리가 제한된 경우에 유용합니다. 고정된(pinned) 페이지는 여전히 개인적으로 유지되며, 고정되지 않은 슬롯만이 공유됩니다.
Content‑Addressed Access
A cache is not an array you index into. Clients never know:
- Where a page lives in memory.
- Which slot it occupies.
- Whether it was recently evicted.
Pages are requested by page number, not by memory address. When the tree module wants a page, it calls sqlite3PagerGet(P)—the pager handles the rest.
Fetch‑On‑Demand 정책
SQLite는 엄격한 fetch‑on‑demand 정책을 따릅니다:
- 페이지를 핀하고 반환합니다.
- 트리 모듈이 페이지를 일시적으로 소유합니다.
- 클라이언트는 결국
sqlite3PagerUnref를 호출합니다.
Unref 후에 페이지는 다시 재활용 가능해집니다. 핀된 페이지는 제거될 수 없습니다. SQLite는 또한 최소 캐시 크기(SQLite 3.7.8 기준 10 페이지)를 강제하여 항상 어느 정도의 여유 공간을 확보합니다.
페이지가 변경될 때 무슨 일이 일어나나요?
다음 논리적인 질문은: 페이지가 수정될 때 무슨 일이 일어나는가? 다음 글에서 다룰 내용은:
- 캐시 업데이트 규칙
- 더티 페이지 추적 방법
- 교체 정책
- SQLite가 성능과 안전성을 균형 있게 유지하는 방법
여기서 캐싱은 수동적인 상태를 벗어나 실행을 형성하기 시작합니다.
참고 문헌 및 추가 읽을거리
- SQLite Database System: Design and Implementation – Sibsankar Haldar (n.d.)
- SQLite와 관련된 나의 실험 및 직접 실행
FreeDevTools
👉 FreeDevTools를 확인해 보세요 – 개발 도구, 치트 코드, TL;DR을 위한 오픈‑소스 허브입니다.
피드백이나 기여자를 언제든 환영합니다! 온라인에 있으며 오픈‑소스이고 누구나 사용할 수 있습니다.
⭐ GitHub에서 별표를 눌러 주세요: