(KO) PostgreSQL 클러스터 부트스트랩 — initdb, 부트스트랩 백엔드, genbki
목차
- 학술적 배경
- DBMS 공통 설계 패턴
- PostgreSQL의 구현
- 소스 코드 가이드
- 소스 검증 (2026-06-05 기준)
- PostgreSQL 너머 — 비교 설계와 연구 프론티어
- 출처
학술적 배경
섹션 제목: “학술적 배경”관계형 데이터베이스는 **자기 서술적(self-describing)**이다. 모든 테이블의 스키마가 테이블 자체에 저장된다. pg_class는 모든 릴레이션을 나열하고, pg_attribute는 모든 릴레이션의 컬럼을 담으며, pg_type은 모든 타입을 기록한다. 이 구조는 런타임에는 우아하다. 범용 튜플 접근 루틴이 카탈로그를 포함한 어떤 테이블이든 동일한 방식으로 읽을 수 있기 때문이다. 그런데 생성 시점에는 닭이 먼저냐 달걀이 먼저냐의 문제가 생긴다. pg_class에 행을 삽입하려면 pg_class의 튜플 디스크립터가 필요하다. 그 디스크립터는 pg_attribute 행으로 만들어진다. 그런데 그 pg_attribute 행 자체가 pg_attribute에 저장되어 있다. 아직 존재하지 않는 pg_attribute에 말이다. 최초의 카탈로그는 카탈로그를 읽지 않고도 동작하는 코드로 만들어야 한다.
이것이 부트스트랩 문제다. 컴파일러 부트스트랩 문제(컴파일러를 처음 빌드할 때는 그 컴파일러로 컴파일할 수 없다)나 OS 부트 로더 문제(로더는 자신을 로드하는 것에 의해 로드될 수 없다)와 구조적으로 동일하다. 고전적인 해법은 두 단계 전략이다. 가장 원시적인 구조에 대한 하드와이어드(hard-wired) 지식으로 작동하는 작고 특수한 로더가 시스템을 자립 가능한 상태로 끌어올린다. 그 뒤에는 일반 기계가 인계받는다.
Database System Concepts(Silberschatz 외)는 데이터 딕셔너리와 시스템 카탈로그를 “메타데이터 — 데이터에 관한 데이터”로 정의하고, 데이터 딕셔너리를 표현하는 릴레이션 자체가 재귀적 구조라는 점을 지적한다. 스토리지 매니저는 일반 릴레이션에 쓰는 것과 동일한 코드로 이 구조를 읽어야 한다. PostgreSQL이 계보를 이어받은 System R 설계(Astrahan 외, 1976)도 마찬가지였다. System R은 카탈로그를 RSS(Research Storage System) 인터페이스로 접근 가능한 일반 릴레이션으로 저장했고, 그 카탈로그의 카탈로그를 먼저 쌓는 특수 루틴이 필요했다.
PostgreSQL에는 순수 런타임 시스템에는 없는 두 번째 축의 문제도 있다. 카탈로그 내용 — 어떤 내장 타입, 함수, 연산자, 접근 방법이 존재하는지 — 은 방대한 수작업 데이터 집합이며 C 코드와 일관성을 유지해야 한다. 예를 들어 int4의 입력 함수 OID는 C 타입 기계가 컴파일해 넣는 상수와 동일해야 한다. PostgreSQL은 이를 빌드 타임 코드 생성으로 해결한다. Perl 프로그램 genbki.pl이 카탈로그 스키마와 초기 데이터를 정의하는 동일한 pg_*.h/pg_*.dat 소스 파일을 읽어, (a) 클러스터 생성 시 소비되는 postgres.bki 스크립트와 (b) 백엔드에 컴파일되는 C 헤더 팬아웃(pg_*_d.h, schemapg.h, syscache_info.h 등)을 함께 생성한다. 진실의 단일 출처는 .h/.dat 쌍이고, 하위 산출물은 모두 파생된 것이다.
전체 파이프라인을 특징짓는 설계 속성은 세 가지다.
- 하드와이어드 원시 타입.
bool,int4,oid,name등 최소 집합의 타입과 그 I/O 함수 OID가 부트스트랩 백엔드에 컴파일되어 있다.pg_type을 읽을 수 있게 되기 전에도 첫 번째 힙을 구성할 수 있는 이유다. - 선언적 초기화, 명령형 해석. 카탈로그 데이터는 선언적(
.dat파일)이다.genbki.pl이 이를 작은 명령형 명령 스트림(BKI)으로 펼치고, 6개 키워드 인터프리터가 순서대로 실행한다. - 단계적 상승(phase escalation). 클러스터 생성은 “카탈로그 없음” → “BKI를 말하는 원시 부트스트랩 백엔드” → “SQL을 말하는 일반 standalone 백엔드” → “세 개의 실제 데이터베이스(template1, template0, postgres)” 순서로 올라간다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”이 절은 대부분의 자기 서술적 DBMS가 공통으로 채택하는 엔지니어링 패턴을 정리한다. PostgreSQL의 구체적인 선택이 알려진 설계 공간 안에서 내린 결정으로 읽히도록 하기 위해서다.
카탈로그의 카탈로그에 대한 컴파일 내장 설명
섹션 제목: “카탈로그의 카탈로그에 대한 컴파일 내장 설명”카탈로그 스키마를 카탈로그에서 읽을 수 없는 첫 번째 삽입 시점에 대비해, 모든 자기 서술적 엔진은 가장 원시적인 릴레이션의 설명을 실행 파일에 직접 컴파일한다. PostgreSQL에서는 schemapg.h 메커니즘(Schema_pg_class, Schema_pg_attribute 매크로)과 부트스트랩 백엔드의 작은 하드와이어드 TypInfo[] 테이블이 그 역할을 한다. relcache의 formrdesc()가 이 컴파일된 디스크립터로 몇 개의 카탈로그를 디스크 읽기 없이 캐시에 “못 박는다(nail)”.
별도의 제한된 로드 언어
섹션 제목: “별도의 제한된 로드 언어”전체 SQL 파서/플래너/실행기로 시드 데이터를 로드하려면 그 서브시스템들이 아직 존재하지 않는 카탈로그에서 이미 동작 가능해야 한다. 공통 해법은 SQL을 우회하는 단순화된 로드 언어다. 표현식도 없고, 조인도 없고, “모두 하나의 트랜잭션 안에서” 이상의 트랜잭션도 없다. 명령 하나가 저수준 스토리지 연산 하나에 일대일로 대응한다. PostgreSQL의 BKI(“Backend Interface”) 언어가 바로 이것이다. create, open, insert, declare index, build indices, close — 각각이 heap_create/heap_insert/DefineIndex 호출 하나에 매핑된다.
단일 출처에서 시드 빌드 타임 생성
섹션 제목: “단일 출처에서 시드 빌드 타임 생성”“카탈로그를 기술하는 C 구조체”와 “그 카탈로그를 초기화하는 행” 사이의 불일치를 막기 위해, 성숙한 시스템은 구조체를 정의하는 동일한 소스에서 로드 스크립트를 생성한다. PostgreSQL의 genbki.pl은 pg_*.h의 CATALOG(...) 어노테이션과 pg_*.dat의 행을 읽어, 심볼 참조를 해소하고(boolin이라는 함수명 → 그 OID), BKI와 매칭되는 pg_*_d.h OID 매크로를 함께 생성한다.
복사-생성 프로토타입으로서의 템플릿 데이터베이스
섹션 제목: “복사-생성 프로토타입으로서의 템플릿 데이터베이스”비싼 시드 과정을 새 데이터베이스마다 반복하는 대신, 운영 엔진은 프로토타입 데이터베이스를 만들고 물리적으로 복사해 새 데이터베이스를 만든다. PostgreSQL은 정확히 하나의 데이터베이스(template1)를 부트스트랩하고, 이후 CREATE DATABASE template0 ... STRATEGY = file_copy와 CREATE DATABASE postgres ...를 실행한다. 나중에 사용자가 CREATE DATABASE foo TEMPLATE template1을 실행하면 같은 프로토타입을 복제한다. template0은 복구 기준선으로 유지되는 원본 연결 불가 복사본이다.
위치 자체가 카탈로그 데이터인 릴레이션을 위한 릴레이션 맵
섹션 제목: “위치 자체가 카탈로그 데이터인 릴레이션을 위한 릴레이션 맵”더 깊은 재귀가 있다. pg_class를 뒷받침하는 물리 파일은 보통 pg_class.relfilenode를 읽어 찾는다. 그런데 pg_class를 읽어서 pg_class를 찾을 수는 없다. 엔진은 이를 릴레이션 맵이라는 대역외(out-of-band) 방법으로 해결한다. global/pg_filenode.map과 base/<db>/pg_filenode.map이라는 작은 플랫 파일이 “매핑된(mapped)” 카탈로그의 OID를 파일 번호에 대응시킨다. 부트스트랩 백엔드가 RelationMapFinishBootstrap()으로 이 파일을 마지막에 쓴다.
flowchart TD A["pg_*.h catalog headers<br/>(CATALOG macro = schema)"] --> G["genbki.pl<br/>(build time)"] B["pg_*.dat data files<br/>(initial rows)"] --> G G --> BKI["postgres.bki<br/>(flat command stream)"] G --> H["pg_*_d.h / schemapg.h<br/>syscache_info.h / system_fk_info.h<br/>(compiled into backend)"] G --> SQL["system_constraints.sql<br/>(PK/UNIQUE constraints)"] BKI --> I["initdb (--boot backend)<br/>BKI interpreter"] H --> I I --> T1["template1<br/>(seeded catalogs on disk)"] T1 --> POST["standalone backend<br/>runs SQL post-bootstrap"] POST --> T0["template0 (file_copy)"] POST --> PG["postgres (file_copy)"]
PostgreSQL의 구현
섹션 제목: “PostgreSQL의 구현”PostgreSQL은 클러스터 탄생을 두 개의 바이너리와 세 개의 단계로 나눈다. initdb(src/bin/initdb/)는 오케스트레이터다. 카탈로그를 직접 건드리지 않는다. 디렉터리 트리를 만들고 설정 파일을 쓴 다음 백엔드를 두 번 구동한다. 첫 번째는 postgres.bki를 소비하는 부트스트랩 모드, 두 번째는 SQL을 실행하는 일반 standalone 모드다.
Phase 0 — 빌드 타임 genbki
섹션 제목: “Phase 0 — 빌드 타임 genbki”클러스터가 존재하기 훨씬 전에 빌드가 genbki.pl을 모든 카탈로그 헤더에 실행한다. 핵심 루프는 각 카탈로그를 순회하며 create 명령과 시드 행의 insert 명령을 생성하고 심볼 OID 참조를 해소한다. 핵심 트릭은 .dat 파일의 OID 동의어를 동일한 데이터 집합으로 만든 룩업 테이블로 해소한다는 점이다(proname => 'boolin'이라고 쓰면 그 함수의 OID로 바뀐다).
# genbki.pl — src/backend/catalog/genbki.pl# procedure OID lookupmy %procoids;foreach my $row (@{ $catalog_data{pg_proc} }){ my $prokey = $row->{proname}; if (defined $procoids{$prokey}) { $procoids{$prokey} = 'MULTIPLE'; } else { $procoids{$prokey} = $row->{oid}; } # ... also proname(argtypes) ...}컬럼에 BKI_LOOKUP(pg_proc) 어노테이션이 있으면 genbki.pl이 생성하는 insert에 조회된 OID를 대입한다.
# genbki.pl — the per-column substitution in the catalog emit loopif ($column->{lookup}){ my $lookup = $lookup_kind{ $column->{lookup} }; # ... oidvector / _oid / scalar cases ... $lookupnames[0] = $bki_values{$attname}; @lookupoids = lookup_oids($lookup, $catname, $attname, $lookup_opt, \%bki_values, @lookupnames); $bki_values{$attname} = $lookupoids[0];}pg_attribute는 특별하다. .dat 파일이 없다. gen_pg_attribute()가 다른 모든 카탈로그의 스키마를 순회해 행을 합성한다. 부트스트랩 카탈로그에는 시스템 컬럼 여섯 개(ctid, xmin, cmin, xmax, cmax, tableoid)도 추가해 인터프리터가 완전한 pg_attribute 힙을 구성할 수 있게 한다.
# gen_pg_attribute — src/backend/catalog/genbki.plmy @SYS_ATTRS = ( { name => 'ctid', type => 'tid' }, { name => 'xmin', type => 'xid' }, { name => 'cmin', type => 'cid' }, { name => 'xmax', type => 'xid' }, { name => 'cmax', type => 'cid' }, { name => 'tableoid', type => 'oid' });foreach my $attr (@SYS_ATTRS){ $attnum--; my %row; $row{attnum} = $attnum; $row{attrelid} = $table->{relation_oid}; morph_row_for_pgattr(\%row, $schema, $attr, 1); print_bki_insert(\%row, $schema);}자동 배정 OID(명시적 oid =>가 없는 행)는 카탈로그별로 예약 구간 [FirstGenbkiObjectId, FirstUnpinnedObjectId)에서 배정된다.
# assign_next_oid — src/backend/catalog/genbki.pl$GenbkiNextOids{$catname} = $FirstGenbkiObjectId if !defined($GenbkiNextOids{$catname});my $result = $GenbkiNextOids{$catname}++;die "genbki OID counter for $catname ... overrunning FirstUnpinnedObjectId\n" if $result >= $FirstUnpinnedObjectId;생성되는 BKI의 첫 줄은 버전 마커(# PostgreSQL 18)이고, 마지막 명령은 항상 build indices다. BKI로 표현할 수 없는 제약(기본 키, 유일성)은 나중 SQL 단계를 위해 별도의 system_constraints.sql에 기록된다.
Phase 1 — initdb가 디렉터리를 만들고 부트스트랩 백엔드를 구동
섹션 제목: “Phase 1 — initdb가 디렉터리를 만들고 부트스트랩 백엔드를 구동”initialize_data_directory()가 최상위 드라이버다. 디렉터리 뼈대를 만들고, 클러스터 전체 PG_VERSION을 쓰고, 설정 파일을 검사하고 쓴 뒤 bootstrap_template1()을 호출한다.
// initialize_data_directory — src/bin/initdb/initdb.ccreate_data_directory();create_xlog_or_symlink();/* ... mkdir each of subdirs[] ... */write_version_file(NULL); /* top-level PG_VERSION */set_null_conf();test_config_settings();setup_config();bootstrap_template1(); /* <-- runs the --boot backend */write_version_file("base/1"); /* template1's per-db PG_VERSION */bootstrap_template1()은 빌드 타임 postgres.bki를 읽고, 버전 헤더를 PG_MAJORVERSION과 대조하고, 플랫폼/로케일 토큰(NAMEDATALEN, SIZEOF_POINTER, FLOAT8PASSBYVAL, ENCODING, LC_COLLATE 등)을 대입한 뒤 결과 전체를 새로 구동한 postgres --boot 프로세스에 파이프로 넘긴다.
// bootstrap_template1 — src/bin/initdb/initdb.cbki_lines = readfile(bki_file);if (strcmp(headerline, *bki_lines) != 0) /* "# PostgreSQL <ver>\n" */ { pg_log_error("input file \"%s\" does not belong to PostgreSQL %s", ...); exit(1); }bki_lines = replace_token(bki_lines, "NAMEDATALEN", buf);bki_lines = replace_token(bki_lines, "FLOAT8PASSBYVAL", FLOAT8PASSBYVAL ? "true" : "false");bki_lines = replace_token(bki_lines, "ENCODING", encodingid_to_string(encodingid));/* ... */printfPQExpBuffer(&cmd, "\"%s\" --boot %s %s", backend_exec, boot_options, extra_options);PG_CMD_OPEN(cmd.data);for (line = bki_lines; *line != NULL; line++) PG_CMD_PUTS(*line); /* feed each BKI line to the backend's stdin */PG_CMD_CLOSE();토큰 대입이 genbki.pl이 아닌 initdb.c에서 이뤄지는 데는 의도된 경계가 있다. genbki는 릴리스 전반에 걸쳐 고정된 것(OID)만 구울 수 있다. 포인터 크기, 인코딩, 로케일처럼 플랫폼이나 설정에 따라 달라지는 것은 실제 대상 환경이 알려지는 클러스터 생성 시점에 채워야 한다.
Phase 2 — 부트스트랩 백엔드가 BKI를 해석
섹션 제목: “Phase 2 — 부트스트랩 백엔드가 BKI를 해석”BootstrapModeMain()은 argv[1]이 --boot일 때의 백엔드 진입점이다. 독립 단일 프로세스로 실행된다. postmaster도 없고, 프로토콜을 말하는 공유 메모리 자식도 없다. BootstrapProcessing 모드를 설정하고, 시스템 인덱스 사용을 끄고, BootStrapXLOG()로 WAL을 초기화한 뒤, stdin의 BKI를 flex/bison 파서로 처리한다.
// BootstrapModeMain — src/backend/bootstrap/bootstrap.cAssert(!IsUnderPostmaster);InitStandaloneProcess(argv[0]);/* ... getopt over "B:c:d:D:Fkr:X:-:" ... */SetProcessingMode(BootstrapProcessing);IgnoreSystemIndexes = true;/* ... shared mem, InitProcess, BaseInit ... */BootStrapXLOG(bootstrap_data_checksum_version);InitPostgres(NULL, InvalidOid, NULL, InvalidOid, 0, NULL);/* ... */StartTransactionCommand();boot_yyparse(scanner); /* the BKI interpreter loop */CommitTransactionCommand();RelationMapFinishBootstrap(); /* write pg_filenode.map */문법은 genbki가 생성하는 키워드와 정확히 일치한다. 못 박힌(nailed) 카탈로그에 대한 create ... bootstrap은 heap_create()를 직접 호출한다(파일을 만들지만 카탈로그에 등록하지 않는다. 그 항목은 이미 pg_class 시드 데이터에 있는 insert다). 비부트스트랩 create는 전체 heap_create_with_catalog()를 거친다.
// Boot_CreateStmt — src/backend/bootstrap/bootparse.ymapped_relation = ($4 || shared_relation); /* $4 = optbootstrap */if ($4) boot_reldesc = heap_create($2, PG_CATALOG_NAMESPACE, shared_relation ? GLOBALTABLESPACE_OID : 0, $3, InvalidOid, HEAP_TABLE_AM_OID, tupdesc, RELKIND_RELATION, RELPERSISTENCE_PERMANENT, shared_relation, mapped_relation, true, &relfrozenxid, &relminmxid, true);else id = heap_create_with_catalog($2, PG_CATALOG_NAMESPACE, ...);각 insert ( ... )는 현재 열려 있는 릴레이션에 heap_insert를 수행한다. 인터프리터는 한 번에 최대 하나의 릴레이션(boot_reldesc)을 열어 둔다. genbki 출력은 그래서 일반 카탈로그에 open/insert*/close를 교차시키고, 부트스트랩 카탈로그는 create bootstrap이 암묵적으로 연다. 인덱스 생성은 스캔 중 선언(큐에 추가)되고, 맨 마지막의 build indices에서 한꺼번에 구축된다. 인덱스 릴레이션 자체에도 pg_class/pg_attribute 행이 필요하기 때문이며, 그 행들은 모든 insert가 끝난 뒤에야 완성된다.
flowchart TD
S["boot_yyparse over postgres.bki"] --> C{command?}
C -->|create bootstrap| HC["heap_create<br/>nailed/mapped catalog"]
C -->|create| HCC["heap_create_with_catalog"]
C -->|open name| OR["boot_openrel<br/>set boot_reldesc<br/>(populate Typ from pg_type)"]
C -->|insert| IT["InsertOneValue x N<br/>then InsertOneTuple<br/>= heap_insert"]
C -->|declare index| DI["index_register<br/>(queued, skip_build)"]
C -->|close| CL["closerel"]
C -->|build indices| BI["build_indices<br/>(index_build each queued)"]
BI --> END["CommitTransactionCommand<br/>RelationMapFinishBootstrap"]
postgres.bki 해부
섹션 제목: “postgres.bki 해부”genbki가 실제로 무엇을 생성하는지 보면 이해가 빠르다. 파일은 줄 단위이고, #은 주석(스캐너가 이후 내용을 버린다), _null_은 유일한 예약어이며, 식별자는 [-A-Za-z0-9_]+ 패턴이고 그 외는 단따옴표로 감싼다. pg_type 같은 부트스트랩 카탈로그는 대략 다음과 같이 나온다.
// shape of postgres.bki emitted by genbki.pl (illustrative, condensed)# PostgreSQL 18create pg_type 1247 bootstrap rowtype_oid 71 ( oid = oid , typname = name , typlen = int2 , typbyval = bool , ... typinput = regproc , typoutput = regproc , ... )insert ( 16 bool 1 't' ... boolin boolout ... )insert ( 17 bytea -1 'f' ... byteain byteaout ... )...close pg_typeOID 뒤의 bootstrap 키워드는 이 카탈로그가 못 박힌/매핑된 카탈로그임을 표시한다(optbootstrap 프로덕션). 그래서 인터프리터는 heap_create를 쓰고, pg_class 행을 삽입하려 하지 않는다. 그 행은 pg_class에 대한 시드 insert 중 하나이기 때문이다. 스키마의 regproc 컬럼은 genbki의 룩업 기계가 왜 중요한지를 보여 준다. .dat 파일은 typinput => 'boolin'이라고 썼고, genbki가 이를 함수명 boolin으로 바꾸었다. 부트스트랩 백엔드의 regprocin(또는 Typ/TypInfo 상승)이 insert 시점에 이를 OID로 해소한다. FORCE NOT NULL/FORCE NULL 토큰(컬럼의 BKI_FORCE_NOT_NULL 어노테이션에서 옴)은 genbki가 자동 null 가능성 추론을 재정의할 수 있게 한다. 비부트스트랩 카탈로그는 추가로 open <catname>/close <catname> 쌍이 insert 앞뒤를 감싼다. create bootstrap이 자동으로 열어 주지 않기 때문이다.
키워드 집합은 진짜로 작다. 스캐너는 open, close, create, OID, bootstrap, shared_relation, rowtype_oid, insert, _null_, declare, build, indices, unique, index, on, using, toast, FORCE, NOT, NULL과 구두점만 인식한다. 산술도, 문자열 연결도, 조건도 없다. 이 간결함이 핵심이다. 인터프리터는 SQL 엔진이 사용 가능하기 전에 실행되므로, “힙을 만들고 튜플을 넣고 인덱스를 큐에 넣고 큐를 구축하라”는 것 외에는 아무것도 알 필요가 없다.
하드와이어드 타입 테이블과 Typ 상승
섹션 제목: “하드와이어드 타입 테이블과 Typ 상승”pg_type이 디스크에 존재하기 전에도 인터프리터는 부트스트랩 카탈로그에서 사용되는 컬럼 타입의 디스크 너비, 정렬, I/O 함수를 알아야 한다. 이들이 TypInfo[]로 컴파일된다.
// TypInfo[] — src/backend/bootstrap/bootstrap.cstatic const struct typinfo TypInfo[] = { {"bool", BOOLOID, 0, 1, true, TYPALIGN_CHAR, TYPSTORAGE_PLAIN, InvalidOid, F_BOOLIN, F_BOOLOUT}, {"int4", INT4OID, 0, 4, true, TYPALIGN_INT, TYPSTORAGE_PLAIN, InvalidOid, F_INT4IN, F_INT4OUT}, {"name", NAMEOID, CHAROID, NAMEDATALEN, false, TYPALIGN_CHAR, TYPSTORAGE_PLAIN, C_COLLATION_OID, F_NAMEIN, F_NAMEOUT}, /* ... oid, regproc, text, _oid, _text, ... */};DefineAttr()이 카탈로그 create 중에 이 테이블을 참조한다. 첫 번째 open 명령이 실행되는 순간, boot_openrel()은 pg_type에 이제 행이 있음을 감지하고 상승한다. pg_type을 인메모리 Typ 목록으로 읽어들이고, 이후 타입 조회는 컴파일된 테이블 대신 실제 카탈로그를 통한다.
// boot_openrel — src/backend/bootstrap/bootstrap.c/* pg_type must be filled before any OPEN command is executed, hence we * can now populate Typ if we haven't yet. */if (Typ == NIL) populate_typ_list();/* ... */boot_reldesc = table_openrv(makeRangeVar(NULL, relname, -1), NoLock);gettype()이 상승을 명시적으로 구현한다. Typ == NIL이면 TypInfo[] 인덱스를 반환하고, Typ가 로드되면 실제 OID를 반환하고 전역 Ap를 해당 행으로 설정한다. 호출자는 어떤 체제인지 확인해야 한다. 소스 주석이 “정말 추한(really ugly) 인터페이스”라고 표현한 고의적 설계다.
Phase 3 — standalone 백엔드가 SQL로 마무리
섹션 제목: “Phase 3 — standalone 백엔드가 SQL로 마무리”부트스트랩 백엔드는 template1의 시드 카탈로그만 만든다. 시스템의 대부분 — 뷰, pg_* SQL 함수, 설명, 콜레이션, 권한, information_schema, PL/pgSQL, 나머지 두 데이터베이스 — 은 두 번째 백엔드 호출로 만들어진다. 이번에는 일반 standalone 백엔드(postgres ... template1)가 stdin으로 SQL을 읽는다. initialize_data_directory()가 열고 SQL 파일과 생성된 구문을 순서대로 재생한다.
// initialize_data_directory (post-bootstrap) — src/bin/initdb/initdb.cprintfPQExpBuffer(&cmd, "\"%s\" %s %s template1 >%s", backend_exec, backend_options, extra_options, DEVNULL);PG_CMD_OPEN(cmd.data);setup_auth(cmdfd); /* REVOKE on pg_authid */setup_run_file(cmdfd, system_constraints_file); /* PK/UNIQUE from genbki */setup_run_file(cmdfd, system_functions_file);setup_depend(cmdfd); /* pg_stop_making_pinned_objects */setup_run_file(cmdfd, system_views_file);setup_description(cmdfd);setup_collation(cmdfd);setup_run_file(cmdfd, dictionary_file);setup_privileges(cmdfd);setup_schema(cmdfd); /* information_schema */load_plpgsql(cmdfd); /* CREATE EXTENSION plpgsql */vacuum_db(cmdfd); /* ANALYZE; VACUUM FREEZE */make_template0(cmdfd);make_postgres(cmdfd);setup_depend()는 pg_stop_making_pinned_objects()를 호출한다. 이 시점 이후에 생성되는 객체는 핀이 없다. 즉 DBA가 삭제할 수 있다. 이 시점 이전의 것은 핀이 박힌 삭제 불가 시스템 구성 요소다. make_template0()은 template1을 복제한다.
// make_template0 — src/bin/initdb/initdb.cPG_CMD_PUTS("CREATE DATABASE template0 IS_TEMPLATE = true ALLOW_CONNECTIONS = false" " OID = " CppAsString2(Template0DbOid) " STRATEGY = file_copy;\n\n");PG_CMD_PUTS("UPDATE pg_database SET datcollversion = NULL WHERE datname = 'template0';\n\n");PG_CMD_PUTS("REVOKE CREATE,TEMPORARY ON DATABASE template0 FROM public;\n\n");PG_CMD_PUTS("COMMENT ON DATABASE template0 IS 'unmodifiable empty database';\n\n");template0은 고정 OID(pg_upgrade가 데이터베이스 OID를 보존할 수 있도록)를 부여받고, IS_TEMPLATE과 ALLOW_CONNECTIONS = false로 표시되며, datcollversion이 지워진다. 이 덕분에 template0을 원본으로 복제하는 데이터베이스는 콜레이션 버전 검사를 건너뛴다. make_postgres()도 마찬가지로 고정 OID로 기본 postgres 데이터베이스를 복제한다. 둘 다 STRATEGY = file_copy를 쓴다. wal_log는 WAL을 부풀리고 새 클러스터에 불필요하기 때문이다.
카탈로그 내용과 genbki가 함께 생성하는 스키마 매크로/syscache 팬아웃은 postgres-system-catalogs.md에서 다루고, 실행 중인 서버가 연결별 백엔드를 fork하는 postmaster 쪽은 postgres-postmaster.md에서 다룬다. 이 문서는 클러스터 탄생 경로에 머문다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”파이프라인은 빌드 타임 Perl 스크립트 하나, 프론트엔드 C 프로그램 하나, flex/bison 프론트엔드를 가진 백엔드 C 프로그램 하나에 걸쳐 있다. 아래 심볼은 세 단계로 나눠 정리한다. 줄 번호는 끝의 위치 힌트 표에 있다. 줄 번호는 시간이 지나면 어긋나지만 심볼 이름은 지속적인 닻이다.
genbki.pl — 빌드 타임 코드 생성
섹션 제목: “genbki.pl — 빌드 타임 코드 생성”%lookup_kind—BKI_LOOKUP종류(pg_proc,pg_type,pg_operator,encoding등)를 해당 카탈로그의 OID 해시에 대응하는 마스터 디스패치 테이블.%procoids,%typeoids,%operoids,%classoids,%collationoids등의 해시가$catalog_data{pg_*}순회로 채워진다.- 주
foreach my $catname (@catnames)루프 — 카탈로그마다pg_<cat>_d.h를 열고 릴레이션/rowtype/인덱스/toast OID#define과Anum_*/Natts_*매크로를 생성하고, 컬럼 목록과 함께create <cat>BKI 명령을 쓰고, 각 데이터 행의 룩업 대입을 수행하고print_bki_insert를 호출한다. gen_pg_attribute— 다른 모든 카탈로그의 스키마에서pg_attribute행을 합성한다(사용자 컬럼 + 부트스트랩 카탈로그의 시스템 컬럼 6개).%schemapg_entries/@tables_needing_macros에schemapg.h항목을 축적한다.morph_row_for_pgattr—pg_type메타데이터(attlen,attbyval,attalign,attstorage)를 합성된pg_attribute행에 복사하고 고정 너비/이전 컬럼 규칙으로attnotnull을 계산한다. 런타임의DefineAttr()과 일치해야 한다.print_bki_insert—insert ( ... )줄 하나를 형식화한다. 단따옴표를 이중화하고, 부트스트랩 스캐너의[-A-Za-z0-9_]+패턴 외 문자를 포함하는 값을 따옴표로 감싼다.lookup_oids— OID 동의어 이름 목록을 룩업 해시로 해소한다. 미해소 또는 불법 zero 참조가 있으면 경고하고$num_errors를 증가시킨다..dat파일의 오타가 빌드를 실패시키는 방식이다.assign_next_oid— 카탈로그별로FirstGenbkiObjectId에서 자동 OID를 배정한다.FirstUnpinnedObjectId에 도달하면 die한다.form_pg_type_symbol—pg_type행의typname에서FOOOID/FOOARRAYOID매크로 이름을 파생한다.- 트레일러 생성 — 모든 카탈로그 처리 후: 큐에 쌓인
@toast_decls와@index_decls를 출력하고,build indices리터럴을 쓰고,schemapg.h,system_fk_info.h,system_constraints.sql,syscache_ids.h,syscache_info.h를 완성해Catalog::RenameTempFile로 원자적으로 이름을 바꾼다.
initdb.c — 프론트엔드 오케스트레이션
섹션 제목: “initdb.c — 프론트엔드 오케스트레이션”main→setup_data_file_paths/setup_bin_paths—postgres백엔드와postgres.bki의 위치를 찾는다. **initialize_data_directory**가 단계 드라이버다.subdirs[]— PGDATA 아래mkdir되는 디렉터리의 정적 목록(global,base,base/1,pg_wal/*,pg_xact,pg_multixact/*등).write_version_file—PG_VERSION을 쓴다. 클러스터 레벨(extrapath == NULL)과base/1(template1전용) 두 곳에 쓴다. 부트스트래퍼가 클러스터 레벨 파일을 확인하므로 먼저 만든다.set_null_conf/test_config_settings/setup_config— 임시 백엔드로 동작하는shared_buffers/max_connections쌍을 탐색하고,postgresql.conf,pg_hba.conf,pg_ident.conf를 쓴다.bootstrap_template1— Phase 1 드라이버.postgres.bki를 읽고, 헤더를PG_MAJORVERSION과 확인하고,replace_token대입(NAMEDATALEN,SIZEOF_POINTER,ALIGNOF_POINTER,FLOAT8PASSBYVAL,POSTGRES,ENCODING,LC_COLLATE,LC_CTYPE,DATLOCALE,ICU_RULES,LOCALE_PROVIDER)을 적용하고, 결과를postgres --boot에 파이프로 넘긴다.PG_CMD_OPEN/PG_CMD_PUTS/PG_CMD_CLOSE/PG_CMD_PRINTF— 백엔드를popen하고, 줄을 먹이고, 종료 상태를 확인하는 매크로 패밀리. 부트스트랩과 standalone 단계 모두에 쓰인다.initialize_data_directory의 부트스트랩 이후 블록 — standalone 백엔드를 열고 순서대로setup_auth,setup_run_file(system_constraints_file),setup_run_file(system_functions_file),setup_depend,setup_run_file(system_views_file),setup_description,setup_collation,setup_run_file(dictionary_file),setup_privileges,setup_schema,load_plpgsql,vacuum_db,make_template0,make_postgres를 호출한다.setup_depend—SELECT pg_stop_making_pinned_objects()를 생성한다. 핀/비핀 분수령이다.make_template0/make_postgres— 고정 OID(Template0DbOid,PostgresDbOid)와STRATEGY = file_copy로CREATE DATABASE를 실행한다.
bootstrap.c + bootparse.y — BKI 인터프리터
섹션 제목: “bootstrap.c + bootparse.y — BKI 인터프리터”BootstrapModeMain—--boot/--check의 백엔드 진입점. standalone 프로세스,BootstrapProcessing모드,IgnoreSystemIndexes, 공유 메모리,BootStrapXLOG,InitPostgres를 설정하고,boot_yyparse를 실행하고, 커밋하고,RelationMapFinishBootstrap을 호출한다.CheckerModeMain—--check조기 종료 경로. 주어진 GUC 아래에서 공유 메모리와 세마포어를 생성할 수 있음을 증명하고 종료한다.Boot_CreateStmt(bootparse.y) —create를heap_create(부트스트랩/매핑된 카탈로그) 또는heap_create_with_catalog(일반 카탈로그)로 디스패치한다. 못 박힌/공유 릴레이션에mapped_relation을 설정한다.boot_openrel—open구현.pg_type에서Typ를 지연 로드하고, 이전 열린 릴레이션을 닫고, 명명된 릴레이션을table_openrv로 열고, 속성 디스크립터를attrtypes[]에 스냅샷한다.DefineAttr—create의 컬럼 하나(<name = type>)를 구현한다.TypInfo(pre-pg_type) 또는 로드된Typ목록에서attlen/attbyval/attalign/attstorage를 채우고, 콜레이션 인식 시스템 컬럼에 C 콜레이션을 강제한다.InsertOneValue/InsertOneNull/InsertOneTuple—insert구현. 각 필드를 타입의 입력 함수(boot_get_type_io_data+OidInputFunctionCall)로 변환하고,heap_form_tuple+simple_heap_insert를 수행한다.gettype/populate_typ_list/boot_get_type_io_data—TypInfo[]→Typ상승 기계.Boot_DeclareIndexStmt/Boot_DeclareUniqueIndexStmt/index_register—declare index구현.IndexStmt를 만들고,DefineIndex(... skip_build = true)를 호출하고,ILHead에 인덱스를 큐잉한다.Boot_DeclareToastStmt—BootstrapToastTable로declare toast를 구현한다.Boot_BuildIndsStmt/build_indices—build indices구현. 큐잉된ILHead목록을 순회하며 각각index_build를 호출한다. 힙 데이터(인덱스 자체의 카탈로그 행 포함)가 모두 준비된 시점이다.
위치 힌트 (2026-06-05 기준, REL_18 273fe94)
섹션 제목: “위치 힌트 (2026-06-05 기준, REL_18 273fe94)”| 심볼 | 파일 | 줄 |
|---|---|---|
%lookup_kind 디스패치 테이블 | src/backend/catalog/genbki.pl | 415 |
주 foreach my $catname (@catnames) 생성 루프 | src/backend/catalog/genbki.pl | 466 |
print $bki "build indices\n" (트레일러) | src/backend/catalog/genbki.pl | 719 |
gen_pg_attribute | src/backend/catalog/genbki.pl | 858 |
morph_row_for_pgattr | src/backend/catalog/genbki.pl | 939 |
print_bki_insert | src/backend/catalog/genbki.pl | 994 |
lookup_oids | src/backend/catalog/genbki.pl | 1077 |
form_pg_type_symbol | src/backend/catalog/genbki.pl | 1116 |
assign_next_oid | src/backend/catalog/genbki.pl | 1138 |
subdirs[] | src/bin/initdb/initdb.c | 231 |
PG_CMD_OPEN/PG_CMD_PUTS 매크로 | src/bin/initdb/initdb.c | 319 |
write_version_file | src/bin/initdb/initdb.c | 1024 |
bootstrap_template1 | src/bin/initdb/initdb.c | 1545 |
setup_depend | src/bin/initdb/initdb.c | 1716 |
setup_run_file | src/bin/initdb/initdb.c | 1729 |
make_template0 | src/bin/initdb/initdb.c | 2011 |
make_postgres | src/bin/initdb/initdb.c | 2065 |
initialize_data_directory | src/bin/initdb/initdb.c | 3044 |
main | src/bin/initdb/initdb.c | 3158 |
TypInfo[] | src/backend/bootstrap/bootstrap.c | 87 |
CheckerModeMain | src/backend/bootstrap/bootstrap.c | 180 |
BootstrapModeMain | src/backend/bootstrap/bootstrap.c | 198 |
boot_openrel | src/backend/bootstrap/bootstrap.c | 440 |
DefineAttr | src/backend/bootstrap/bootstrap.c | 522 |
InsertOneTuple | src/backend/bootstrap/bootstrap.c | 629 |
InsertOneValue | src/backend/bootstrap/bootstrap.c | 657 |
populate_typ_list | src/backend/bootstrap/bootstrap.c | 726 |
gettype | src/backend/bootstrap/bootstrap.c | 766 |
boot_get_type_io_data | src/backend/bootstrap/bootstrap.c | 837 |
index_register | src/backend/bootstrap/bootstrap.c | 932 |
build_indices | src/backend/bootstrap/bootstrap.c | 982 |
Boot_CreateStmt | src/backend/bootstrap/bootparse.y | 157 |
Boot_InsertStmt | src/backend/bootstrap/bootparse.y | 254 |
Boot_DeclareIndexStmt | src/backend/bootstrap/bootparse.y | 273 |
Boot_DeclareToastStmt | src/backend/bootstrap/bootparse.y | 379 |
Boot_BuildIndsStmt | src/backend/bootstrap/bootparse.y | 391 |
소스 검증 (2026-06-05 기준)
섹션 제목: “소스 검증 (2026-06-05 기준)”이 문서의 주장은 커밋 273fe94의 REL_18_STABLE 트리를 기준으로 확인했다. 기록해 둘 핵심 검증 사항은 다음과 같다.
-
백엔드 호출은 두 번, 프론트엔드는 하나.
initialize_data_directory는bootstrap_template1(이 안에서postgres --boot를 구동)을 호출하고, 이후 별도로postgres ... template1standalone 백엔드를PG_CMD_OPEN으로 연다. 두printfPQExpBuffer(&cmd, ...)호출 위치가--boot플래그(bootstrap_template1) vs. 평이한backend_options(부트스트랩 이후 블록)로 구분된다. initdb.c 약 1612번과 약 3112번 줄에서 확인했다. -
BKI 키워드 집합은 정확히 여섯 가지 명령 형태.
bootparse.y의Boot_Query대안은Boot_OpenStmt,Boot_CloseStmt,Boot_CreateStmt,Boot_InsertStmt,Boot_DeclareIndexStmt/Boot_DeclareUniqueIndexStmt,Boot_DeclareToastStmt,Boot_BuildIndsStmt다.bootscanner.l의 매칭 스캐너 토큰(open,close,create,insert,declare,build,indices,index,unique,toast,on,using)이 BKI에 범용 연산이 없음을 확인한다. 검증 완료. -
pg_attribute에는.dat파일이 없고 행이 생성된다. genbki의 카탈로그 루프가if ($catname eq 'pg_attribute') { gen_pg_attribute($schema); }로 특별 처리하며$catalog_data{pg_attribute}를 반복하지 않는다. 시스템 컬럼 여섯 개는$table->{bootstrap}카탈로그에만 추가된다. genbki.pl 약 583번과 약 908번 줄에서 확인했다. -
토큰 대입은 정책상 genbki가 아닌 initdb에서. genbki.pl에 명시적 주의 주석이 있다(“플랫폼이나 설정에 따라 달라질 수 있는 심볼은 대입하지 말 것 … 올바른 위치는 initdb.c의
bootstrap_template1()”).NAMEDATALEN,FLOAT8PASSBYVAL,ENCODING, 로케일 토큰에 대한replace_token호출이 모두bootstrap_template1에 있다. 검증 완료. -
template0/postgres는 고정 OID와file_copy를 사용.make_template0과make_postgres모두CREATE DATABASE ... OID = CppAsString2(...) ... STRATEGY = file_copy를 생성하며,pg_upgradeOID 보존 동기를 설명하는 소스 주석이 명시적으로 있다. 검증 완료. -
인덱스 구축은 끝까지 지연.
Boot_DeclareIndexStmt가DefineIndex에skip_build = true를 전달하고,index_register가ILHead에 큐잉하며, 마지막build indices명령이 구동하는build_indices만 실제로index_build를 호출한다.index_register의 소스 주석이 근거를 밝힌다(“인덱스 자체에 카탈로그 항목이 있고 그 항목들이 해당 카탈로그의 인덱스에 포함되어야 한다”). 검증 완료. -
릴레이션 맵은 마지막에 쓰인다.
BootstrapModeMain이CommitTransactionCommand()이후에RelationMapFinishBootstrap()을 호출한다. 주석은 “이제 매핑된 모든 릴레이션을 알아야 한다”고 밝힌다. bootstrap.c 약 396번 줄에서 확인했다.
PostgreSQL 너머 — 비교 설계와 연구 프론티어
섹션 제목: “PostgreSQL 너머 — 비교 설계와 연구 프론티어”계보: System R과 버클리 POSTGRES 카탈로그
섹션 제목: “계보: System R과 버클리 POSTGRES 카탈로그”PostgreSQL의 부트스트랩 형태는 System R(Astrahan 외, 1976)에서 직접 내려온다. System R은 카탈로그를 사용자 데이터와 동일한 저수준 스토리지 인터페이스(RSS)로 접근 가능한 일반 릴레이션으로 저장하는 방식을 선구했다. 그 결정 — 카탈로그는 테이블이다 — 이 닭-달걀 문제를 만들고, System R도 범용 기계가 실행되기 전에 카탈로그의 카탈로그를 쌓는 특수 루틴이 필요했다. 버클리 POSTGRES 설계(Stonebraker & Rowe, 1986; “The Implementation of POSTGRES”, Stonebraker, Rowe & Hirohama, 1990)는 이를 공격적으로 확장 가능한 타입 및 접근 방법 시스템으로 확장했다. 새 타입, 연산자, 인덱스 AM이 컴파일된 특수 케이스가 아니라 카탈로그 행이다. 그 확장성이 PostgreSQL에 genbki/BKI_LOOKUP 기계가 필요한 이유다. 내장 타입과 함수는 열린 시스템의 시드 행일 뿐이고, 임시 C 초기화가 아닌 데이터(.dat 파일)와 심볼 교차 참조로 표현해야 한다. 부트스트랩 백엔드의 하드와이어드 TypInfo[]는 데이터 기계가 서 있는 바닥이기 때문에 데이터로 표현할 수 없는 최소 집합이다.
다른 엔진의 해법
섹션 제목: “다른 엔진의 해법”-
SQLite는 부트스트랩을 거의 완전히 우회한다. 유일한 카탈로그
sqlite_schema(이전sqlite_master)는 데이터베이스 파일의 고정 루트 페이지(1번 페이지)에 있는 일반 B-트리다. 별도의 시딩 프로그램이 없다. 데이터베이스 생성은 100바이트 헤더와 빈 루트 페이지를 쓰는 것이다. 대신 SQLite는 확장 가능한 타입 시스템이 없으므로 시딩해야 할 대형 내장 카탈로그 데이터 집합도 없다. PostgreSQL의 무거운 파이프라인은 풍부하고 정적으로 타입 지정된 확장 가능한 카탈로그의 비용이다. -
MySQL/InnoDB는 역사적으로 데이터 딕셔너리 정보를 InnoDB 내부 테이블과
.frm파일로 분리해 관리했다. 8.0부터는 InnoDB 테이블에 저장되는 트랜잭션 데이터 딕셔너리로 이동했다. 최소 컴파일 내장 설명이 딕셔너리 테이블을 일으키고, 그 딕셔너리가 이후 모든 것을 기술하는 동일한 두 단계 상승 구조다. -
Oracle은 데이터베이스 생성 시 최소 SYS 소유 부트스트랩 세그먼트를 대상으로
catalog.sql/catproc.sql스크립트를 실행한다.bootstrap$테이블이 PostgreSQL의pg_filenode.map+ 못 박힌 카탈로그의 유사물이다. 여기서도 작은 하드와이어드 코어, 그런 다음 나머지를 만드는 SQL 스크립트 — initdb가--boot와 standalone 패스에서 쓰는 것과 동일한 “명령형 로더 후 SQL” 두 단계 패턴이다.
빌드 타임 일관성 계약으로서의 코드 생성
섹션 제목: “빌드 타임 일관성 계약으로서의 코드 생성”genbki.pl의 더 깊은 아키텍처 아이디어는 단일 진실 출처 코드 생성이다. 하나의 선언적 설명(pg_*.h + pg_*.dat)이 런타임 C 쪽(구조체 레이아웃, OID #define, syscache 디스크립터, FK 메타데이터)과 시드 데이터 쪽(BKI) 모두를 구동한다. 빌드는 둘이 어긋나면 실패한다(중복 OID, 미해소 동의어, OID 카운터 초과). protobuf/Thrift IDL 컴파일러, ORM 마이그레이션 생성기, Linux의 syscall_64.tbl과 동일한 원칙이다. 데이터는 한 곳에서 손으로 관리하고, 산출물은 기계가 파생하며, 생성기가 일관성 검사기 역할을 한다. 이 패턴의 효과는 내장 함수를 추가하는 작업이 헤더, 초기화 구문, syscache 테이블을 협조해 수정하는 대신 .dat 파일 한 줄 편집으로 끝난다는 것이다.
연구 관심사를 하나 짚으면, 카탈로그를 완전히 데이터 주도적이고 핫 플러그 가능하게 만드는 방향(재컴파일 없이 로드 가능한 타입/AM 팩)에 대한 관심이 지속된다. PostgreSQL은 확장 기능으로 이를 근접하게 구현하지만 부트스트랩 코어에서는 아니다. genbki 경계 — 컴파일해야 할 것 vs. 시드 데이터로 표현할 수 있는 것 vs. 런타임 확장이 될 수 있는 것 — 는 확장 가능한 모든 DBMS가 협상하는 살아 있는 설계 긴장이다. PostgreSQL은 보수적으로 선을 긋는다. 바닥(TypInfo[], 못 박힌 relcache 디스크립터)은 컴파일되고, 내장 카탈로그는 생성된 시드 데이터이며, pg_stop_making_pinned_objects() 이후의 모든 것은 삭제 가능한 런타임 상태다.
운영상의 파생 사항
섹션 제목: “운영상의 파생 사항”- 청정 기준선으로서의
template0.template0은 연결되지 않고 수정되지 않으므로,template1과 다른 인코딩/로케일 조합을 포함해CREATE DATABASE ... TEMPLATE template0의 보장된 원본이다.datcollversion이 지워진 덕분에 콜레이션이 다른 복제가 합법적이다. --check모드.check_only가 설정된 동일한BootstrapModeMain경로를 postmaster의 리소스 자체 테스트(CheckerModeMain)가 재사용한다. 부트스트랩 백엔드가 공유 메모리 크기 검증을 위한 가장 가벼운 “최소 백엔드”로 쓰이는 좋은 사례다.pg_upgrade와 고정 OID.make_template0/make_postgres의 고정Template0DbOid/PostgresDbOid배정은 순전히 메이저 버전 업그레이드 시 데이터베이스 OID를 충돌 없이 보존하기 위한 것이다. 수년 후 실행되는 도구에 의해 구동되는 부트스트랩 타임 결정이다.
- 소스 트리: PostgreSQL REL_18_STABLE @ 273fe94 (PG 18.x).
src/bin/initdb/initdb.c— 프론트엔드 오케스트레이션: 디렉터리 레이아웃, 설정 생성,bootstrap_template1, 부트스트랩 이후 SQL 단계,make_template0/make_postgres.src/backend/bootstrap/bootstrap.c—BootstrapModeMain, BKI 명령 구현(boot_openrel,DefineAttr,InsertOne*,index_register,build_indices),TypInfo[]→Typ타입 상승.src/backend/bootstrap/bootparse.y,bootscanner.l— 여섯 가지 BKI 명령 형태를 정의하는 flex/bison 프론트엔드.src/backend/catalog/genbki.pl—postgres.bki,pg_*_d.h,schemapg.h,system_fk_info.h,system_constraints.sql,syscache_ids.h,syscache_info.h의 빌드 타임 생성기.src/include/catalog/pg_*.h,pg_*.dat— 카탈로그 스키마와 시드 데이터의 단일 진실 출처.
- 이론 앵커 (
.omc/plans/postgres-paper-bibliography.md참조):- Database System Concepts (Silberschatz, Korth, Sudarshan, 7e) — 자기 서술적 재귀 메타데이터로서의 데이터 딕셔너리/시스템 카탈로그.
knowledge/research/dbms-general/database-system-concepts.md. - System R (Astrahan 외, 1976) — 릴레이션으로서의 카탈로그 계보.
dbms-papers/systemr.md. - 버클리 POSTGRES 설계 시리즈 (Stonebraker & Rowe, 1986; Stonebraker, Rowe & Hirohama, 1990) —
BKI_LOOKUP데이터 주도 시드를 동기화하는 확장 가능한 타입/AM 카탈로그.
- Database System Concepts (Silberschatz, Korth, Sudarshan, 7e) — 자기 서술적 재귀 메타데이터로서의 데이터 딕셔너리/시스템 카탈로그.
- 교차 참조 (형제 문서):
postgres-system-catalogs.md— 시드된 카탈로그의 내용과 genbki가 함께 생성하는 schemapg/syscache 팬아웃.postgres-postmaster.md— 클러스터가 생성된 후 실행 중인 서버가 연결별 백엔드를 fork하는 방식.postgres-relcache.md— 컴파일된schemapg.h디스크립터를 사용하는 relcache “못 박기”/formrdesc.postgres-xlog-wal.md—BootStrapXLOG와 초기 WAL.