콘텐츠로 이동

(KO) PostgreSQL 시스템 카탈로그와 캐시 — 섹션 개요

목차

PostgreSQL은 카탈로그 주도 엔진이다. 타입, 연산자, 함수, 접근 방법(access method), 인덱스 전략, 네임스페이스, 그리고 릴레이션 자체가 하드코딩된 값이 아니라 pg_* 시스템 테이블의 행이다(pg_class, pg_attribute, pg_type, pg_proc, pg_am, pg_index 등). 이 한 가지 사실이 엔진의 런타임 확장성을 가능하게 하는 근거다(postgres-architecture-overview.md의 축 7). 새로운 타입이나 연산자를 PostgreSQL에 가르치는 일은 INSERT이지 재컴파일이 아니다.

이 서브카테고리가 존재하는 이유도 거기 있다. 모든 플랜, 모든 표현식, 모든 튜플 디폼이 카탈로그를 힙에서 직접 읽어야 한다면 카탈로그가 병목이 된다. 그래서 PostgreSQL은 카탈로그 앞에 세 단계의 캐시를 두고, 그 캐시들이 백엔드 간에 일관성을 유지하도록 공유 무효화 루프를 운영한다. 이 섹션이 다루는 범위는 정확히 그 스택이다. 거기에 캐시 계층 위에 놓인 두 가지 서비스도 포함된다. 카탈로그를 캐시하는 것이 아니라 읽는 서비스들이다.

  • 카탈로그 레이아웃 자체pg_* 테이블이 무엇인지, 행이 어떻게 생기는지, 그리고 서버가 SQL을 읽기 전에 카탈로그를 채우는 부트스트랩 경로(pg_*.dat.bkiinitdb)가 어떻게 동작하는지. → postgres-system-catalogs.md
  • relcache — 백엔드별 Relation 디스크립터. 각 항목은 여러 카탈로그(pg_class + pg_attribute + pg_index + AM 정보 + …)를 조합해 만들고, 테이블 접근 때마다 읽히기 때문에 캐시된다. → postgres-relcache.md
  • catcache와 syscache — catcache는 개별 카탈로그 튜플을 조회 키로 캐시한다. syscache는 각 캐시에 이름을 부여하는 타입별 디스패치 테이블(TYPEOID, PROCOID 등)이고, lsyscache는 그 위에서 자주 쓰는 조회를 감싸는 편의 접근자다. → postgres-catcache-syscache.md
  • 캐시 무효화(sinval) — 캐시를 공유 메모리 기반과 연결하는 루프. 카탈로그 변경이 메시지를 큐에 넣고, 모든 백엔드가 그것을 소비해 무효화한다. → postgres-cache-invalidation.md
  • 의존성 추적pg_depend / pg_shdepend, 객체 참조 그래프. DROP ... CASCADE, RESTRICT, pg_dump 정렬 순서를 가능하게 한다. → postgres-dependency-tracking.md
  • 네임스페이스 / search_path — 스키마 해석. 비정규화된 이름 foo를 활성 search_path를 사용해 특정 카탈로그 OID로 바꾸는 과정. → postgres-namespace-search-path.md

이 섹션이 다루지 않는 범위:

  • 공유 메모리 기반이 아니다. sinval 큐는 고정 공유 메모리 세그먼트 안에 살고 그 구조체 중 하나지만, 세그먼트 자체, PGPROC, procsignal, 무효화 신호를 전달하는 IPC 프리미티브는 server-architecture(postgres-shared-memory-ipc.md)에 속한다. 이 섹션은 캐시 정합성 프로토콜을 소유하고, 전송 계층은 아래로 위임한다.
  • DDL 실행이 아니다. CREATE / ALTER / DROP 문이 카탈로그를 변경하고 무효화 메시지를 발행하지만, 명령 처리 기계(tcop/utility.c, commands/tablecmds.c, commands/indexcmds.c)는 ddl-schema(postgres-ddl-execution.md, postgres-alter-table.md)에 속한다. 이 섹션은 카탈로그를 자료 구조이자 캐시로 소유하고, ddl-schema는 그 기록자를 소유한다. 의존성 추적이 접합부다. 이 섹션은 pg_depend 그래프를 설명하고, ddl-schema가 그 위에서 performDeletion을 호출한다.
  • 트랜잭션 힙으로서의 카탈로그가 아니다. 카탈로그 테이블은 평범한 MVCC 힙 릴레이션이다. 가시성, WAL, vacuum 규칙이 사용자 테이블과 동일하게 적용된다. 그 메커니즘은 txn-recoverystorage-engine(postgres-mvcc-snapshots.md, postgres-heap-am.md)에 속하고, 이 섹션은 그 바닥 위에 캐시 계층을 쌓는다.
  • 부트스트랩 도구가 아니다. 이 섹션은 카탈로그 행이 pg_*.dat에서 genbki.plinitdb를 거쳐 온다는 사실을 설명하지만, 코드 생성 파이프라인과 initdb 바이너리 자체는 utilities(postgres-initdb-bootstrap-genbki.md)에 속한다.

카탈로그가 바닥이고, 캐시가 그 위에 쌓이고, sinval이 기록자의 커밋에서 모든 독자의 캐시로 루프를 닫는다. 의존성 추적과 네임스페이스 해석은 캐시 옆에 자리 잡은 서비스로, 같은 카탈로그 행을 읽는다.

flowchart TB
  subgraph BOOT["부트스트랩 (initdb, 최초 1회) — utilities로 위임"]
    DAT["pg_*.dat / .bki<br/>genbki.pl 코드 생성"]
  end

  subgraph CAT["카탈로그 (MVCC 힙 위의 pg_* 테이블)"]
    direction LR
    SYSCAT["postgres-system-catalogs.md<br/>pg_class, pg_attribute, pg_type,<br/>pg_proc, pg_am, pg_index, ..."]
  end
  DAT -. "initdb 시점에 채움" .-> SYSCAT

  subgraph CACHES["백엔드별 캐시 (사적 사본)"]
    direction LR
    RELC["postgres-relcache.md<br/>릴레이션 디스크립터<br/>(여러 카탈로그를 조합)"]
    CATC["postgres-catcache-syscache.md<br/>catcache 튜플 + syscache 디스패치<br/>+ lsyscache 접근자"]
  end
  SYSCAT --> RELC
  SYSCAT --> CATC

  subgraph SVC["카탈로그 위의 서비스"]
    direction LR
    DEP["postgres-dependency-tracking.md<br/>pg_depend / pg_shdepend 그래프<br/>(DROP CASCADE, pg_dump 순서)"]
    NS["postgres-namespace-search-path.md<br/>search_path → OID 해석"]
  end
  SYSCAT --> DEP
  SYSCAT --> NS

  EXEC["실행기 / 플래너 / 튜플 디폼<br/>(query-processing)"]
  RELC --> EXEC
  CATC --> EXEC
  NS --> EXEC

  subgraph LOOP["정합성 루프"]
    DDL["DDL / 카탈로그 변경<br/>(ddl-schema 기록자)"]
    INVAL["postgres-cache-invalidation.md<br/>CacheInvalidate* → 메시지 등록"]
    SINV["sinval 큐<br/>(공유 메모리 — server-architecture)"]
  end
  DDL --> SYSCAT
  DDL --> INVAL
  INVAL --> SINV
  SINV -. "모든 백엔드가 AcceptInvalidationMessages에서 소비" .-> RELC
  SINV -.-> CATC

다이어그램에서 세 가지를 읽어 두면 된다.

  1. 캐시는 사적이고, 카탈로그는 공유다. 각 백엔드의 relcache와 catcache는 그 백엔드만의 메모리 컨텍스트 안에 있다. 이 서브카테고리에서 공유 상태는 sinval 메시지 큐 하나뿐이다. 바로 그래서 무효화는 공유 캐시 퇴거 문제가 아니라 메시징 문제다.
  2. 루프가 이 섹션의 척추다. 기록자(DDL 명령)가 카탈로그 행을 변경하는 동시에 무효화 메시지를 등록한다. 커밋 시점에 그 메시지들이 공유 큐로 들어가고, 다른 모든 백엔드는 AcceptInvalidationMessages에서 큐를 소비해 낡은 relcache / catcache 항목을 버린다. postgres-cache-invalidation.md가 이 루프를 처음부터 끝까지 담당한다.
  3. 의존성 추적과 네임스페이스 해석은 읽기 위주 서비스다. 두 서비스 모두 카탈로그(pg_depend, pg_namespace)를 참조하지만 캐시 스택의 일부가 아니다. 카탈로그 로직이기 때문에 여기 묶인 것이지, 질의나 스토리지 로직이어서가 아니다.

다른 문서들이 의존하는 문서를 먼저 읽는 교차 참조 우선 순서다.

  1. postgres-system-catalogs.md — 여기서 시작한다. 이 섹션의 나머지는 모두 카탈로그 위의 캐시이거나 카탈로그를 읽는 서비스다. pg_class / pg_attribute / pg_type이 무엇을 담는지, OID가 어떻게 작동하는지, 행이 어떻게 부트스트랩되는지를 먼저 파악하고 들어온다.
  2. postgres-catcache-syscache.md — 더 단순한 캐시(개별 튜플)이고, relcache 자체가 이것에 의존한다. relcache보다 먼저 읽는다.
  3. postgres-relcache.md — 더 무거운 캐시(릴레이션 전체 디스크립터, 일부는 syscache 조회로 조합). 버퍼 매니저 다음으로 엔진 안에서 가장 많이 참조되는 문서다. 모든 테이블 접근이 이것을 건드리기 때문이다.
  4. postgres-cache-invalidation.md — 무엇이 캐시되는지(2~3번)를 알아야 의미 있다. 두 캐시를 정직하게 유지하는 정합성 루프가 여기 있으며, 전송 계층은 postgres-shared-memory-ipc.md로 전달된다.
  5. postgres-dependency-tracking.md — 캐시 스택과 독립적이다. DDL 의미론(DROP CASCADE, pg_dump 정렬 순서)을 볼 때 읽는다.
  6. postgres-namespace-search-path.md — 가장 얇은 문서. 이름 해석 프런트엔드다. 마지막에 읽거나, 질문이 순전히 “search_path가 어떤 foo를 고르는가”라면 첫 번째로 읽는다.

아래는 전방 참조다. 모듈 문서들이 아직 존재하지 않을 수 있으며, 요약은 각 문서의 계획된 범위를 예측한 것이다.

모듈 문서한 줄 범위
postgres-system-catalogs.md자료 구조로서의 pg_* 시스템 테이블 — pg_class, pg_attribute, pg_type, pg_proc, pg_am, pg_index 등; OID 할당, 공유 카탈로그 대 데이터베이스 로컬 카탈로그, 고정/매핑 릴레이션을 위한 relmapper, 행이 생성(heap_create_with_catalog, InsertPgClassTuple)되고 부트스트랩(pg_*.dat.bki)되는 방식.
postgres-relcache.md릴레이션 캐시 — 여러 카탈로그로 Relation 디스크립터를 구성하는 RelationBuildDesc, RelationIdGetRelation 핫 패스, “nailed” 시스템 카탈로그 항목, 빠른 백엔드 기동을 위한 init 파일, RelationClearRelation / RelationCacheInvalidate가 sinval에 응답하는 방식.
postgres-catcache-syscache.md시스템 카탈로그 튜플 캐시 — catcache 해시 버킷과 네거티브 캐싱(SearchCatCache), syscache 타입별 디스패치 테이블(SearchSysCache1..4, TYPEOID / PROCOID 캐시 ID), 자주 쓰는 조회를 감싸는 lsyscache 편의 접근자.
postgres-cache-invalidation.md공유 무효화 루프 — 메시지를 등록하는 CacheInvalidateHeapTuple / CacheInvalidateRelcache, CommandEndInvalidationMessages를 통한 트랜잭션별 버퍼링, sinvaladt.c 링(SIInsertDataEntries / SIGetDataEntries)을 통한 브로드캐스트, AcceptInvalidationMessages에서의 소비.
postgres-dependency-tracking.md객체 의존성 그래프 — pg_depend(로컬) · pg_shdepend(공유/전역) 엣지, ObjectAddress 식별, recordDependencyOn, DROP ... CASCADERESTRICT와 pg_dump 순서를 구현하는 performDeletion / findDependentObjects 재귀.
postgres-namespace-search-path.md스키마 해석 — 활성 search_path, recomputeNamespacePath, 정규화된 이름과 비정규화된 이름 조회(RangeVarGetRelid), 임시 스키마와 pg_catalog 우선순위, 해석된 목록이 필요한 호출자를 위한 fetch_search_path.
  • server-architecture (postgres-overview-server-architecture.md) — sinval 큐가 사는 공유 메모리 세그먼트, 그리고 백엔드를 깨워 큐를 소비하게 하는 procsignal / latch 기계를 소유한다. 이 섹션의 무효화 루프는 그 기반의 클라이언트다. 접합부는 postgres-shared-memory-ipc.md다.
  • ddl-schema (postgres-overview-ddl-schema.md) — 카탈로그의 기록자. 모든 CREATE / ALTER / DROPpg_* 테이블을 변경하고 이 섹션의 루프가 운반하는 무효화 메시지를 발행한다. DROP ... CASCADE는 이 섹션이 기술하는 의존성 그래프를 따라 걷는다. ddl-schema는 명령을 소유하고, 이 섹션은 명령이 건드리는 자료 구조와 캐시를 소유한다.
  • txn-recovery (postgres-overview-txn-recovery.md) — 카탈로그 테이블은 MVCC 힙 릴레이션이다. 읽기는 스냅샷 가시성을 따르고, 쓰기는 WAL에 기록되고, vacuum을 받는다. 카탈로그 접근은 질의 스냅샷과 구별되는 카탈로그 스냅샷(postgres-mvcc-snapshots.md)을 사용한다. 무효화 타이밍을 다룰 때 알아 두어야 할 접합부다.
  • query-processing (postgres-overview-query-processing.md) — 이 섹션 캐시들의 주된 독자. 플래너는 relcache에서 통계와 릴레이션 형태를 가져오고, 표현식 평가는 syscache로 함수와 연산자를 해석하고, 분석 단계의 이름 해석은 여기 있는 네임스페이스 해석기를 사용한다.
  • utilities (postgres-overview-utilities.md) — 이 섹션이 읽는 카탈로그를 채우는 부트스트랩 도구(genbki.pl, initdb)와 이 섹션의 의존성 그래프에 객체 정렬 순서를 의존하는 pg_dump를 소유한다.