콘텐츠로 이동

(KO) PostgreSQL 스토리지 엔진 — 섹션 개요

목차:

스토리지 엔진 섹션은 PostgreSQL이 디스크 위의 바이트 스트림을 익스큐터가 읽는 행과 인덱스 항목으로 바꾸는 방법, 그리고 그 반대 방향을 문서화한다. 이 섹션은 질의 파이프라인 아래에 위치한다. 익스큐터는 접근 방법 루틴으로 이 계층을 호출하며, 운영 체제 에 경계가 있다. 세그먼트 파일에 대한 read()/write() 호출이 그 경계다. 구체적으로는 src/backend/storage/{smgr,buffer,page,freespace,large_object,aio}src/backend/access/{table,heap,common,index,nbtree,gin,gist,spgist,brin,hash,tablesample}를 아우른다.

아래에서 위로 읽으면 스택은 다섯 계층으로 나뉜다.

  1. smgr / md — 릴레이션의 논리적 블록을 물리적 파일 세그먼트로 매핑하는 스토리지 매니저 간접 계층이다. 릴레이션을 1 GB 파일로 분할하는 매직 넘버 계층이다.
  2. 버퍼 풀 — 모든 읽기와 쓰기가 반드시 통과하는 공유 고정 크기 페이지 캐시다. 메모리와 디스크 사이의 유일한 경유지이며, WAL-before-flush 규칙이 적용되는 지점이기도 하다. 그 밑에는 PG18 비동기 I/O 서브시스템 (storage/aio/)이 있어, 블록 읽기를 발행-대기(issued-and-awaited) 방식의 작업으로 전환하는 read-stream API를 제공한다.
  3. 페이지 레이아웃 — 모든 접근 방법이 공유하는 슬롯 단위 페이지 형식이다. PageHeader, ItemId 라인 포인터, ItemPointer/TID 주소 체계가 여기에 속하며, 각 페이지를 보호하는 데이터 체크섬도 이 계층이 관리한다.
  4. 플러그인 접근 방법 — 이 섹션의 구조적 핵심이다. 테이블 접근은 TableAmRoutine으로, 인덱스 접근은 IndexAmRoutine으로 간접화된다. 익스큐터는 “heap”을 직접 코드에 하드코딩하지 않는다. heap은 기본 테이블 AM일 뿐이며, nbtree/gin/gist/spgist/brin/hash는 내장 인덱스 AM이다.
  5. 온-페이지 레코드와 그 위성들 — 힙 AM의 덮어쓰지 않는 튜플 형식과 HOT 가지치기, 크기가 큰 속성을 위한 TOAST, 스캔과 삽입을 가속하는 가시성 맵빈 공간 맵, 그리고 힙 위에 올라탄 시퀀스대형 객체 저장소가 이 계층을 이룬다.

명확한 경계. 이 섹션은 의도적으로 두 경계에서 멈추고 책임을 넘긴다.

  • MVCC 의미론, WAL, vacuum, 복구는 이 섹션의 소유가 아니다. 힙은 덮어쓰지 않는다UPDATE는 새 튜플 버전을 쓰고 이전 버전을 남긴다. 그런데 어느 버전이 가시적인지, 커밋 상태가 어떻게 기록되는지, 변경 사항을 기술하는 WAL 레코드는 무엇인지, 죽은 버전을 누가 회수하는지는 모두 txn-recovery 서브카테고리(postgres-overview-txn-recovery.md)에 속한다. 이 섹션은 온-페이지 실현xmin/xmax/infomask 비트가 어디에 있는지, HOT 체인이 어떻게 가지치기 되는지, 가시성 맵의 페이지당 두 비트가 어떻게 설정되는지 — 을 설명하고, 프로토콜은 txn-recovery로 미룬다. 가시성 맵과 빈 공간 맵 문서가 이 섹션에 있는 이유는 이것들이 힙의 물리적 위성이기 때문이다. 이것들을 구동하는 vacuum은 txn-recovery에 있다.
  • 동시성 프리미티브는 이 섹션의 소유가 아니다. 페이지는 버퍼 콘텐츠 잠금과 핀으로 보호되지만, LWLock/핀 메커니즘 자체, 그리고 AM이 획득하는 헤비웨이트 릴레이션 잠금은 server-architecture (postgres-overview-server-architecture.md)에 속한다. 이 섹션은 래치 기율을 언급할 뿐, 잠금 관리자를 다시 설명하지 않는다.

그림 1 — 익스큐터부터 데이터 파일까지, 스토리지 엔진 스택

그림 1 — 스토리지 엔진 계층형 스택. 익스큐터는 테이블/인덱스 AM 루틴으로 진입하며(heap을 직접 코드에 고정하지 않는다), 테이블 샘플링은 테이블 스캔 위의 얇은 AM 인식 래퍼다. 힙 AM은 온-페이지 튜플 형식과 위성들(TOAST, 가시성 맵, 빈 공간 맵, 시퀀스, 대형 객체)을 소유한다. 힙을 포함한 여섯 개 인덱스 AM 모두, 선택적 데이터 체크섬으로 보호되는 하나의 공유 슬롯 단위 페이지 형식으로 페이지를 주소 지정한다. 모든 페이지 읽기·쓰기는 공유 버퍼 풀을 통과하며, 버퍼 풀은 PG18 비동기 I/O 계층과 smgr/md 릴레이션-세그먼트 매핑 위에 앉아 OS 파일에 도달한다.

이 다이어그램이 중심 구조로 만드는 세 가지 사실이 있다.

  • AM 간접 계층이 이 섹션의 척추다. tableam.h는 콜백 구조체 TableAmRoutine (scan_begin, tuple_insert, tuple_update, index_fetch_tuple, …)을 정의하고, amapi.hIndexAmRoutine을 정의한다. 익스큐터와 genam/indexam은 이 포인터들로 디스패치한다. heapTableAmRoutine을 채우고 기본값으로 연결되어 있지만, 호출 경로에서 특권적 지위를 갖지 않는다. 이것이 플러그인 가능한 스토리지와 내장 인덱스 AM 모두를 하나의 패턴의 사례로 만드는 이유다. postgres-table-am.mdpostgres-index-am.md가 두 라우터를 소유하며, 각 구체적 AM 문서는 “이 AM이 콜백 뒤에 무엇을 두는가”를 다룬다.

  • 모든 AM 아래에는 하나의 페이지 형식이 있다. 바이트가 힙 튜플을 담든 B-트리 내부 노드를 담든, 블록은 슬롯 단위 페이지다. 고정 크기 PageHeader, 앞으로 자라는 ItemId(라인 포인터) 배열, 뒤로 자라는 튜플 영역으로 구성되며, ItemPointer(블록 번호 + 오프셋 = TID)로 주소를 지정한다. postgres-page-layout.md가 이를 한 번만 정의하고, 각 AM 문서는 슬롯 안에 무엇을 저장하는지만 설명한다. 데이터 체크섬(postgres-data-checksums.md)은 버퍼 플러시 경계에서 해당 페이지 이미지를 계산해 기록한다.

  • 버퍼 풀은 범용 경유지다. 어떤 AM도 파일에 직접 접근하지 않는다. 버퍼를 핀하고, 콘텐츠 잠금을 획득하고, 페이지를 변경하고, 더티(dirty)로 표시하고, 핀을 해제하는 방식으로만 파일에 접근할 수 있다. 버퍼 매니저(postgres-buffer-manager.md)가 WAL-before-flush 규칙을 강제한다. 더티 페이지는 변경 사항을 기술하는 WAL 레코드가 내구성을 확보하기 전까지 기록될 수 없다는 규칙이며, 이것이 txn-recovery 서브카테고리와의 경계다. 그 아래에서 PG18 비동기 I/O(postgres-aio.md)와 read-stream API는 동기적 블록 읽기를 일괄 발행-대기 작업으로 변환한다. smgr/md(postgres-smgr-md.md)는 논리 블록 번호를 1 GB 파일 세그먼트로 매핑한다.

이 섹션의 문서 20개는 중요도가 동일하지 않으며, 알파벳 순서로 읽으면 척추가 묻혀 버린다. 상호 참조를 먼저 읽는 방식으로, 공유 바닥과 AM 간접 계층을 개별 AM보다 먼저 파악하는 것이 핵심이다.

  1. postgres-page-layout.md — 가장 먼저 읽는다. 섹션 내 모든 다른 문서가 재사용하는 어휘(PageHeader, ItemId, ItemPointer/TID, 슬롯 단위 레이아웃)를 정의하며, 상위 의존성이 없다. 라인 포인터와 페이지 체크섬 개념은 모든 AM에서 반복 등장한다.
  2. postgres-buffer-manager.md — 두 번째로 읽는다. 스토리지 엔진 엔지니어링 작업의 대부분이 여기에 있다. BufferDesc 배열, 클록-스윕 피해자 선택, 핀과 콘텐츠 잠금, txn-recovery와 연결되는 WAL-before-flush 강제가 포함된다. 위의 모든 계층이 버퍼 풀에 접근한다.
  3. postgres-smgr-md.md — 세 번째로 읽는다. 버퍼 매니저의 디스크 방향 보완재다. 릴레이션의 블록이 base/<db>/<relfilenode> 파일로 1 GB 단위로 세그먼트화되는 방식, 그리고 smgr 간접 계층이 비-md 스토리지 매니저를 허용하는 방식을 다루는 짧고 자족적인 문서다.
  4. postgres-table-am.md — 네 번째로 읽는다. TableAmRoutine 라우터다. 이 구조체를 이해해야 이후 “heap이 X를 한다”는 주장을 “기본 AM이 X를 한다”로 읽을 수 있다.
  5. postgres-heap-am.md — 다섯 번째로 읽는다. 기본 테이블 AM이다. 덮어쓰지 않는 튜플 형식(HeapTupleHeader, xmin/xmax/infomask), 삽입/수정/삭제 경로, HOT 가지치기(README.HOT)를 다룬다. 가장 내용이 빽빽한 문서이므로 충분한 시간을 할애해야 한다.
  6. postgres-index-am.md — 여섯 번째로 읽는다. IndexAmRoutine 라우터와 genam/indexam 범용 스캔 루프를 다룬다. 이것을 읽어야 아래의 여섯 구체적 인덱스 AM 문서를 각각 “이 AM이 콜백 뒤에 무엇을 두는가”로 읽을 수 있다.
  7. postgres-nbtree.md — 일곱 번째로 읽는다. B-트리는 기본이자 가장 많이 사용되는 인덱스 AM이고, 인덱스 AM 계약(Lehman-Yao 하이 키, 페이지 분할, README가 1급 자료)을 가장 명확하게 보여 준다. 특화된 AM들보다 먼저 읽어야 한다.
  8. postgres-visibility-map.md + postgres-free-space-map.md — 힙 AM을 읽은 뒤 함께 읽는다. 힙의 두 물리적 위성이다. VM은 인덱스-온리 스캔과 동결(freeze)을 제어하고, FSM은 삽입 대상 페이지를 안내한다.
  9. postgres-toast.md — 크기가 큰 속성이 문제가 될 때 읽는다. 넓은 값이 어떻게 압축되거나 아웃-오브-라인 TOAST 릴레이션으로 밀려나고, 읽기 시 어떻게 복원되는지를 다룬다.
  10. postgres-aio.md — I/O 경로가 중요할 때 읽는다. 버퍼 매니저 아래의 PG18 비동기 I/O 서브시스템과 read-stream API를 다룬다. AM 계층과는 대체로 독립적인 새 코드이며, 자체 README가 있다.
  11. postgres-gin.md, postgres-gist.md, postgres-spgist.md, postgres-brin.md, postgres-hash-index.md — 필요에 따라 훑어본다. 각각 동일한 IndexAmRoutine 뒤에 있는 특화된 인덱스 AM이므로, 자신의 워크로드에 맞는 것을 읽으면 된다.
  12. postgres-table-sampling.md, postgres-sequences.md, postgres-large-objects.md, postgres-data-checksums.md — 필요에 따라 훑어본다. 각각 바닥을 변경하지 않고 그 위에 구성되는 집중적인 사이드 채널이다.

1번부터 7번까지 순서대로 읽은 독자는 “행 또는 인덱스 항목이 어떻게 읽히고 쓰이는가”에 대한 완전한 멘탈 모델을 갖추게 된다. 나머지 문서들은 핵심 경로가 올라타거나 디스패치하는 위성이나 특화된 AM을 채워 준다.

아래는 앞으로 참조할 문서들이다. 일부 모듈 문서는 아직 작성되지 않았다. 요약은 각 문서가 다룰 내용을 예측한 것이다.

문서한 줄 요약
postgres-page-layout.md모든 AM이 공유하는 슬롯 단위 페이지 형식. PageHeader, 앞으로 자라는 ItemId 라인 포인터 배열, 뒤로 자라는 튜플 영역, ItemPointer/TID 주소 체계, 빈 공간·특수 공간 영역.
postgres-buffer-manager.md공유 고정 크기 버퍼 풀. BufferDesc 배열과 버퍼 해시 테이블, 클록-스윕 피해자 선택, 핀/콘텐츠-잠금 기율, WAL-before-flush 규칙, 대량 스캔을 위한 링 버퍼.
postgres-smgr-md.md릴레이션의 논리 블록을 물리 파일로 매핑하는 스토리지 매니저 간접 계층. md 매니저는 릴레이션을 1 GB 파일로 세그먼트화하고 포크 파일(main/fsm/vm/init)을 추적한다.
postgres-aio.mdPG18 비동기 I/O 서브시스템. 플러그인 가능한 방법(io_uring, worker, sync), read_stream 미리보기 API, 버퍼 매니저 아래에서 블록 읽기가 발행-대기 작업이 되는 방식.
postgres-table-am.md익스큐터를 힙에서 분리하는 TableAmRoutine 콜백 구조체. scan/insert/update/delete/index-fetch 슬롯, 슬롯 기반 튜플 접근, heap이 기본값으로 등록되는 방식.
postgres-heap-am.md기본 테이블 AM. 덮어쓰지 않는 HeapTupleHeader(xmin/xmax/infomask), hio를 통한 삽입/수정/삭제 경로, HOT 체인과 온-페이지 가지치기(pruneheap.c, README.HOT).
postgres-toast.md대형 속성 저장 기법(TOAST). 속성별 압축(pglz / LZ4), 컴패니언 TOAST 릴레이션에 행 단위로 청크된 아웃-오브-라인 저장, 읽기 경로의 복원 과정.
postgres-visibility-map.md힙 페이지당 두 비트(all-visible, all-frozen)를 _vm 포크에 저장. 인덱스-온리 스캔을 가능하게 하고 vacuum이 페이지를 건너뛰게 한다. 비트가 설정되고 지워지는 방식.
postgres-free-space-map.md릴레이션 단위 FSM 포크. 페이지별 빈 공간 비율의 이진 트리로 삽입 시 여유 공간이 있는 페이지를 찾는다. vacuum과 삽입 실패 시 유지 관리된다.
postgres-nbtree.md기본 인덱스 AM. 하이 키와 오른쪽 링크가 있는 Lehman-Yao B+-트리, 페이지 분할과 삭제 프로토콜, 다중 컬럼 및 INCLUDE 인덱스, 중복 제거, 1급 자료인 README.
postgres-gin.md일반화 역 인덱스(GIN). 힙 튜플당 다수의 키에 대한 포스팅 리스트/트리(배열, jsonb, 전문 검색), 대기 목록 빠른 삽입 경로, 대량 빌드.
postgres-gist.md일반화 검색 트리(GiST). 옵클래스(consistent/union/penalty/picksplit)로 파라미터화된 균형 트리. 기하, 범위, 최근접 이웃 검색에 사용된다.
postgres-spgist.md공간 분할 GiST(SP-GiST). 불균형, 옵클래스 정의 분할(쿼드트리, 기수 트리). 비직사각형 데이터와 텍스트 접두사 데이터에 적합하며, 내부 튜플 노드 하강 방식을 사용한다.
postgres-brin.md블록 범위 인덱스(BRIN). 튜플 단위 항목 대신 블록 범위별 요약(min/max, bloom). 자연 클러스터링된 대형 테이블에 적합한 초소형 인덱스이며, 요약·비요약 수명 주기가 있다.
postgres-hash-index.md해시 인덱스 AM. 키의 해시로 주소를 지정하는 버킷 페이지, 오버플로 페이지 체인, 분할·확장 프로토콜, WAL 로깅(PG10부터 충돌 안전).
postgres-index-am.mdIndexAmRoutine 라우터와 genam/indexam 범용 스캔 기계(index_beginscan, index_getnext_tid, amcheck). AM이 기능을 알리는 방식과 인덱스-온리 스캔 경로.
postgres-table-sampling.mdTsmRoutine 테이블 샘플 인터페이스와 두 가지 내장 방법(SYSTEM 블록 수준, BERNOULLI 행 수준). 테이블 스캔 위의 AM 인식 래퍼로 구현된다.
postgres-data-checksums.md페이지별 체크섬(checksum.c / checksum_helper.c). 읽기 시 검증되고 플러시 시 기록된다. 클러스터 수준 활성화 플래그와 pg_checksums 오프라인 도구.
postgres-large-objects.mdpg_largeobject 저장소. OID로 주소를 지정하는 시스템 테이블의 2 KB 행들로 값을 청크화한다. 서버 측 inv_apilo_* libpq/SQL 인터페이스.
postgres-sequences.md백엔드당 캐시가 있는 단일 행 힙 릴레이션으로서의 시퀀스. nextval/setval 의미론, 증분의 WAL 로깅, 비트랜잭션적인 이유.

스토리지 엔진은 데이터 경로의 바닥이지만, 시스템 전체의 바닥은 아니다. 세 인접 섹션이 스토리지 엔진이 접하는 경계를 소유한다.

  • 위 (질의 처리). 익스큐터는 스토리지 엔진의 유일한 상위 호출자다. 시퀀셜 스캔, 인덱스 스캔, 인덱스-온리 스캔, 비트맵 힙 스캔, 샘플 스캔 노드가 모두 여기서 설명하는 테이블/인덱스 AM 루틴을 구동한다. AM 간접 계층이 정확히 그 경계다. 익스큐터는 TableScanDesc/IndexScanDesc를 사용하며, 힙 내부를 직접 다루지 않는다. postgres-overview-query-processing.md를 참조하라.
  • 아래 / 옆 (트랜잭션 및 복구). 이 섹션의 가장 중요한 경계이며, 이 섹션이 가장 의도적으로 책임을 미루는 곳이다. 힙은 덮어쓰지 않으므로 죽은 튜플과 가시성 비트를 생산하지만, 모든 페이지를 보호하는 WAL(버퍼 매니저에서 강제되는 WAL-before-flush 규칙), 어느 버전이 가시적인지 결정하는 MVCC 스냅샷 로직, 커밋 상태 레코드, 죽은 버전을 회수하고 가시성·빈 공간 맵을 관리하는 vacuum은 모두 txn-recovery에 있다. 스토리지 엔진은 WAL이 보호하고 vacuum이 회수하는 기판이며, txn-recovery는 프로토콜이다. postgres-overview-txn-recovery.md를 참조하라.
  • 옆 (서버 아키텍처). 버퍼 핀과 콘텐츠 잠금, 버퍼 헤더와 AM 내부 페이지를 보호하는 LWLock, AM이 스캔 전에 획득하는 헤비웨이트 릴레이션 잠금은 모두 server-architecture가 소유하는 동시성 기계에 속한다. 이 섹션은 해당 프리미티브를 전제하고 래치 기율을 언급할 뿐, 잠금 관리자를 다시 설명하지 않는다. postgres-overview-server-architecture.md를 참조하라.

시스템 전체 그림 — PostgreSQL이 왜 하나의 공유 메모리 세그먼트 위의 단일 바이너리인지, 그리고 이 섹션의 버퍼 풀과 AM이 일곱 가지 아키텍처 축 가운데 어디에 위치하는지 — 는 postgres-architecture-overview.md(축 4가 이 섹션이다)를 참조하라.