(KO) PostgreSQL pg_dump / pg_restore — 카탈로그 기반 논리 백업
목차
- 이론적 배경
- DBMS 공통 설계
- PostgreSQL의 접근 방식
- 소스 워크스루
- 소스 검증 (2026-06-06 기준)
- PostgreSQL 너머 — 비교 설계와 연구 방향
- 참고 자료
이론적 배경
섹션 제목: “이론적 배경”데이터베이스 백업은 단 하나의 질문에 답한다. 장애 이후 이 데이터를 어떻게 복구할 것인가? 그런데 답은 구조적으로 두 갈래다. 선택의 갈림목에서 어느 쪽을 택하느냐에 따라 이후의 모든 설계 결정이 달라진다.
**물리 백업(physical backup)**은 스토리지 계층의 바이트를 그대로 복사한다. 데이터 파일·WAL·컨트롤 파일을 순차 읽기로 빠르게 가져온다. 정확하다. 모든 튜플, 모든 죽은 행, 모든 인덱스 리프 노드가 포함된다. 단, *불투명(opaque)*하다. 복원하려면 동일한 메이저 버전·블록 크기·아키텍처의 서버가 필요하다. PostgreSQL의 물리 백업 경로는 pg_basebackup과 WAL 아카이브다(postgres-backup-basebackup.md 참조).
**논리 백업(logical backup)**은 데이터베이스의 의미를 복사한다. CREATE TABLE, COPY, CREATE INDEX, ALTER TABLE … ADD CONSTRAINT 같은 SQL 문의 흐름을 빈 클러스터에 재생해 동등한 데이터베이스를 재구성한다. 느리다. 서버가 모든 것을 다시 파싱·계획·실행·인덱싱해야 한다. 바이트 단위로 정확하지도 않다. 복원된 테이블은 새로 패킹되고, 죽은 튜플은 없으며, OID도 새로 받는다. 그 대신 **이식성(portability)**이 있다. 메이저 버전 간·아키텍처 간·포크 간에 이동할 수 있고, 스키마 하나·테이블 하나만 선택적으로 복원할 수 있다. pg_dump가 PostgreSQL의 논리 백업 엔진이다.
논리 백업 설계 공간을 정의하는 이론적 문제는 세 가지다.
-
스냅샷 일관성. 덤프는 계속 변하는 라이브 데이터베이스를 대상으로 몇 분에서 몇 시간 동안 실행된다. 격리 없이는 카탈로그 스캔 이후에 생성된 테이블을 보지만 그 데이터를 놓치거나, 덤프 도중 참조 테이블이 삭제된 외래 키를 캡처하는 사태가 생긴다. 덤프는 카탈로그와 데이터 전체를 단일 시점으로 읽어야 한다. 이것이 바로 MVCC가 이미 제공하는 스냅샷 격리 보장이다. 덤프 도구의 역할은 스냅샷 하나를 획득하고 전체 실행 동안 그것을 유지하는 것이다(
postgres-mvcc-snapshots.md). -
재구성 순서 — 위상 정렬 문제. SQL은 정의 순서가 가환적(commutative)이지 않다.
CREATE TABLE orders (cust_id REFERENCES customers)는customers가 먼저 존재해야 실행할 수 있다. 뷰는 참조하는 테이블보다 나중에 생성해야 한다. 인덱스의 연산자 클래스도 먼저 있어야 한다. “X가 Y보다 먼저 존재해야 한다”는 사실들이 방향 그래프를 이룬다. 유효한 복원 순서는 그 그래프의 어떤 **위상 정렬(topological sort)**이든 된다. Knuth의 The Art of Computer Programming, Vol. 1 §2.2.3이 정준 알고리즘을 제시한다. 미충족 선행자가 없는 노드를 반복해서 내보내고, 그 후계 노드의 선행자 카운트를 감소시키는 방식이다. 문제는 실제 스키마에 사이클이 있다는 점이다. 자기 자신을 규칙으로 참조하는 뷰, 상호 외래 키를 가진 두 테이블 같은 경우다. 사이클이 있는 그래프에는 위상 정렬이 존재하지 않는다. 덤프 도구는 사이클을 감지하고, 객체를 두 개의 TOC 항목으로 분리해 루프를 끊어야 한다. 테이블 정의를 먼저 내보내고, 루프를 닫는 제약 조건을 나중 패스에 내보내는 방식이다. -
형식과 엔진의 분리. 무엇을 덤프할지와 어떤 순서로 할지는 바이트를 디스크에 어떻게 배치할지와 독립적이다. 깔끔한 설계는 서버에 SQL을 날리는 객체 추출 로직을, 파일을 쓰는 아카이브 직렬화 로직과 분리한다. 두 로직은 추상적인 “아카이브 항목” 인터페이스로만 연결된다. 이것이 고전적인 *전략 패턴(strategy pattern)*이다. 하나의 추출 프론트엔드, 여러 교체 가능한 형식 백엔드 구조다.
Database System Concepts (Silberschatz et al., “Recovery System”)는 백업/복원의 이중성을 덤프(상태의 스냅샷)와 로그(변경의 기록)의 차이로 정리하며, 논리 덤프는 근본적으로 바이트 복사가 아닌 재도출 레시피라고 설명한다. Database Internals (Petrov, Part II)는 동일한 분리를 “논리 복제 대 물리 복제”로 표현한다. 논리 형식은 행·문의 의도를 전달하고, 물리 형식은 페이지 이미지를 전달한다. pg_dump와 pg_basebackup을 가르는 이식성 대 정확성의 교환관계와 같은 구도다.
DBMS 공통 설계
섹션 제목: “DBMS 공통 설계”성숙한 데이터베이스 엔진에는 모두 논리 내보내기 도구가 있다. 이 도구들은 공통된 패턴으로 수렴한다. 패턴에 이름을 붙여두면 PostgreSQL의 선택이 공유된 설계 공간 안의 결정으로 읽힌다.
카탈로그가 진원(source of truth)
섹션 제목: “카탈로그가 진원(source of truth)”논리 덤프 도구는 사용자가 원래 입력한 DDL 텍스트를 파싱하지 않는다. 그 텍스트는 보존되지 않기 때문이다. 대신 시스템 카탈로그를 질의해 DDL을 재구성한다. 테이블을 설명하는 테이블, 컬럼·타입·제약 조건·인덱스를 담은 행들이 그 원천이다. 덤프 도구는 사실상 대형 카탈로그-to-DDL 디컴파일러다. 어느 엔진이든 하나씩 가지고 있다. MySQL의 mysqldump는 INFORMATION_SCHEMA와 SHOW CREATE TABLE을 읽고, Oracle의 Data Pump는 딕셔너리 뷰를 읽으며, SQL Server의 SMO는 sys.*를 탐색한다. 덤프의 정확성은 카탈로그 읽기의 완전성에 비례한다. pg_attrdef에 저장된 컬럼 기본값을 덤프 도구가 조회하지 않으면 그 값은 백업에서 사라진다.
일관된 읽기를 위한 스냅샷 고정
섹션 제목: “일관된 읽기를 위한 스냅샷 고정”내보내기는 카탈로그와 데이터를 하나의 스냅샷 아래에서 읽어야 한다. 범용적인 방법은 엔진이 제공하는 가장 엄격한 격리 수준으로 트랜잭션을 열고 전체 덤프가 끝날 때까지 유지하는 것이다. PostgreSQL에서는 REPEATABLE READ / SERIALIZABLE, MySQL/InnoDB에서는 START TRANSACTION WITH CONSISTENT SNAPSHOT, Oracle에서는 Flashback SCN이 그 역할을 한다. 비용은 오래 살아남는 트랜잭션이다. 이 트랜잭션은 오래된 행 버전을 고정해(vacuum을 막고) 덤프 대상 테이블 전체에 AccessShareLock을 유지한다.
스트리밍 파이프가 아닌 중간 객체 모델
섹션 제목: “스트리밍 파이프가 아닌 중간 객체 모델”단순한 덤프 도구는 카탈로그를 걷는 동안 CREATE … ; COPY … ;를 파일에 스트리밍할 수 있다. 하지만 순서 문제가 이를 막는다. 모든 객체와 모든 의존 관계를 수집하기 전에는 안전한 순서를 알 수 없다. 성숙한 도구는 먼저 인메모리 객체 그래프를 구축하고, 정렬한 뒤, 직렬화한다. 이 그래프가 선택적 복원(하나의 노드와 그것의 전이 선행자만 복원)과 병렬성(독립적인 서브 그래프를 동시에 디스패치)을 가능하게 한다.
목차(TOC)로 목록과 콘텐츠 분리
섹션 제목: “목차(TOC)로 목록과 콘텐츠 분리”컨테이너 형식은 알려진 위치에 **목차(table of contents)**를 둔다. (id, 종류, 이름, 소유자, 의존-목록, 데이터-오프셋) 레코드의 목록이다. TOC 덕분에 복원 도구는 (a) 데이터를 읽지 않고 아카이브 내용을 나열하고, (b) 항목을 재정렬하거나 필터링하며, (c) 선택적 또는 병렬 복원을 위해 특정 테이블의 데이터로 직접 탐색할 수 있다. pg_restore -l / -L, Oracle Data Pump의 마스터 테이블, SQL Server의 백업 헤더가 모두 같은 역할을 한다.
섹션 단계 분리: 스키마 → 데이터 → 제약 조건
섹션 제목: “섹션 단계 분리: 스키마 → 데이터 → 제약 조건”인덱스와 제약 조건이 아직 없는 상태에서 데이터를 대량 적재하는 것이 훨씬 빠르다. 행마다 인덱스 관리와 제약 조건 검사를 치르지 않아도 되기 때문이다. 빠른 로더는 모두 복원을 단계로 나눈다. 빈 테이블을 만들고, 데이터를 대량 적재하고, 그 다음에 인덱스를 빌드하고 제약 조건을 추가한다. PostgreSQL은 이를 pre-data, data, post-data 세 섹션으로 공식화한다. 섹션 경계 자체가 의존 그래프의 노드가 된다.
의존 그래프가 병렬성의 한계
섹션 제목: “의존 그래프가 병렬성의 한계”복원은 의존 관계가 직렬화하는 지점을 제외하면 부끄러울 정도로 병렬적이다. 두 테이블의 데이터는 동시에 적재할 수 있다. 그러나 인덱스는 테이블의 데이터가 존재하기 전에 빌드할 수 없고, 외래 키는 양쪽 엔드포인트가 존재하기 전에 추가할 수 없다. 일반적인 패턴은 준비 큐(ready queue)를 소비하는 워커 풀이다. 모든 의존이 완료된 항목이 “준비” 상태가 된다. 워커가 준비 항목을 가져가고, 완료되면 다른 항목들이 준비 상태가 될 수 있다.
PostgreSQL의 접근 방식
섹션 제목: “PostgreSQL의 접근 방식”PostgreSQL은 문제를 src/bin/pg_dump/ 안의 몇 개 파일에 나누어 구현한다. 코드베이스를 읽는 가장 깔끔한 방법은 네 개의 협력 계층으로 보는 것이다.
flowchart TB
subgraph extract["추출 프론트엔드 — SQL로 서버에 질의"]
A["pg_dump.c main()<br/>옵션 파싱, 접속,<br/>스냅샷 설정"]
B["common.c getSchemaData()<br/>getTables / getTypes / getFuncs / …<br/>DumpableObject 그래프 구축"]
end
subgraph order["순서 결정"]
C["pg_dump_sort.c<br/>sortDumpableObjectsByTypeName<br/>→ sortDumpableObjects (TopoSort)"]
end
subgraph emit["아카이브 백엔드 — 바이트 기록"]
D["pg_backup_archiver.c<br/>ArchiveEntry()로 TOC 구성<br/>CreateArchive()로 형식 선택"]
E["pg_backup_custom.c<br/>pg_backup_directory.c<br/>pg_backup_tar.c<br/>pg_backup_null.c (plain)"]
end
subgraph restore["pg_restore"]
F["OpenArchive + ReadToc<br/>RestoreArchive()<br/>직렬 또는 병렬"]
end
A --> B --> C --> D --> E
E -.아카이브 파일.-> F
그림 1 — 네 개의 계층. 실선 화살표는 덤프 경로, 점선 화살표는 pg_restore에 전달되는 파일. 일반 텍스트 덤프는 archNull 백엔드를 사용하는 동일한 경로이며, 이 경우 컨테이너 파일을 쓰는 대신 pg_dump 자체가 RestoreArchive를 실행한다.
고정된 스냅샷 아래에서 카탈로그 기반 추출
섹션 제목: “고정된 스냅샷 아래에서 카탈로그 기반 추출”pg_dump.c의 main()은 일반 libpq 접속을 열고 트랜잭션을 시작한다. 일관된 덤프를 위해 적절한 격리 수준을 설정하고 스냅샷을 내보낸다. 이는 병렬 덤프가 여러 워커 접속에 하나의 스냅샷을 공유할 때 쓰는 것과 같은 메커니즘이다. 그 트랜잭션 안에서 getSchemaData(common.c)는 길고 의도적으로 순서가 정해진 카탈로그 읽기 시퀀스를 실행한다. 순서는 장식이 아니다. 함수의 주석이 데이터 의존 관계를 직접 설명한다.
// getSchemaData — common.c (condensed)/* extensions first: membership gates whether other objects dump */extinfo = getExtensions(fout, &numExtensions);getExtensionMembership(fout, extinfo, numExtensions);getNamespaces(fout);/* getTables ASAP to minimize the lock-acquisition window */tblinfo = getTables(fout, &numTables);getOwnedSeqs(fout, tblinfo, numTables);getFuncs(fout);getTypes(fout); /* must be after getTables and getFuncs *//* ... aggregates, operators, opclasses, collations, casts, ... */flagInhTables(fout, tblinfo, numTables, inhinfo, numInherits);getTableAttrs(fout, tblinfo, numTables); /* columns of interesting tables */getIndexes(fout, tblinfo, numTables);getConstraints(fout, tblinfo, numTables);getTriggers(fout, tblinfo, numTables);/* ... rules, policies, publications, subscriptions ... */각 getXxx는 관련 카탈로그(pg_class, pg_type, pg_proc, pg_constraint, …)에 SELECT를 날려 각 행을 힙 할당된 *Info 구조체(TableInfo, TypeInfo, FuncInfo, …)로 변환한다. 첫 번째 멤버가 DumpableObject다. getTables의 주석은 잠금 관련 우려를 명시한다. 덤프 대상이 될 수 있는 테이블 전체에 LOCK TABLE … IN ACCESS SHARE MODE를 걸어 덤프 도중 테이블이 삭제되거나 재작성되는 것을 막는다. 트랜잭션 시작과 잠금 획득 사이의 창을 줄이기 위해 이 함수를 일찍 실행한다.
DumpableObject 그래프: CatalogId와 DumpId
섹션 제목: “DumpableObject 그래프: CatalogId와 DumpId”모든 덤프 대상 객체는 두 가지 식별자를 가진다. CatalogId는 카탈로그 좌표다. (tableoid, oid), 즉 어느 카탈로그의 어느 행인지를 나타낸다. 서로 다른 질의로 알게 된 객체들을 교차 참조할 때 사용한다. DumpId는 객체가 생성될 때 순차적으로 할당되는 밀집된 작은 정수다. 의존 배열과 정렬에서 키로 사용된다. AssignDumpId가 두 식별자를 조회 구조에 연결한다.
// AssignDumpId — common.c (condensed)dobj->dumpId = ++lastDumpId;dobj->dump = DUMP_COMPONENT_ALL; /* default: dump everything */dobj->components = DUMP_COMPONENT_DEFINITION;dobj->dependencies = NULL; dobj->nDeps = 0; dobj->allocDeps = 0;
/* index by DumpId (dense array, grown by doubling) */dumpIdMap[dobj->dumpId] = dobj;
/* index by CatalogId (open-addressing hash) if it has one */if (OidIsValid(dobj->catId.tableoid)){ entry = catalogid_insert(catalogIdHash, dobj->catId, &found); entry->dobj = dobj;}인덱스가 두 개, 접근 패턴도 두 가지다. findObjectByDumpId는 배열 인덱싱 방식으로 정렬 시 사용하고, findObjectByCatalogId는 해시 조회 방식으로 한 카탈로그 읽기가 다른 읽기에서 발견한 OID를 참조할 때 사용한다. 의존 관계 에지는 각 객체에 붙은 DumpId 목록이다. addObjectDependency는 두 배로 늘어나는 배열에 추가하고, removeObjectDependency는 하나를 필터링해 제거한다. 이것들이 루프 수정 로직이 그래프를 재작성할 때 쓰는 기본 연산이다.
flowchart LR
subgraph idx["common.c 조회 테이블"]
DM["dumpIdMap[]<br/>DumpId → DumpableObject*"]
CH["catalogIdHash<br/>CatalogId → DumpableObject*"]
end
O1["TableInfo customers<br/>dumpId=12 catId=(1259,16400)"]
O2["TableInfo orders<br/>dumpId=15 catId=(1259,16410)<br/>deps=[12]"]
O3["ConstraintInfo orders_fk<br/>dumpId=22 deps=[15,12]"]
O1 --> DM
O2 --> DM
O3 --> DM
O1 --> CH
O2 --> CH
O2 -. 의존 .-> O1
O3 -. 의존 .-> O2
O3 -. 의존 .-> O1
그림 2 — 객체 그래프. orders는 외래 키 대상인 customers에 의존하고, FK 제약 조건은 양쪽 엔드포인트 모두에 의존한다. 정렬은 customers → orders → orders_fk 순서를 내보내야 한다. 만약 상호 FK가 사이클을 만들었다면, 루프 수정이 제약 조건을 분리해 post-data에 위치시킨다.
pg_depend에서 오는 명시적 의존과 합성 에지
섹션 제목: “pg_depend에서 오는 명시적 의존과 합성 에지”대부분의 에지는 카탈로그에서 직접 온다. getDependencies(pg_dump.c)는 pg_depend를 읽어 각 (classid, objid) → (refclassid, refobjid) 행을 findObjectByCatalogId로 DumpableObject 쌍에 매핑하고 addObjectDependency를 호출한다. 그 위에 추출 코드는 pg_depend가 모델링하지 않는 합성 에지를 추가한다. 예를 들어 컬럼 기본값은 반드시 테이블 뒤에 내보내야 한다는 에지(common.c의 addObjectDependency(&attrDef->dobj, tbinfo->dobj.dumpId)), 또는 buildMatViewRefreshDependencies가 구축하는 구체화 뷰 갱신 에지가 그 예다.
순서 결정: 타입-이름 사전 정렬 후 위상 정렬
섹션 제목: “순서 결정: 타입-이름 사전 정렬 후 위상 정렬”pg_dump.c는 두 단계로 정렬한다. 먼저 sortDumpableObjectsByTypeName이 안정적이고 결정론적인 기준 순서를 부여한다. 객체 타입 우선순위, 그 다음 스키마와 이름 순서다. 논리적으로 동일한 두 데이터베이스가 바이트 단위로 동일하게 덤프되도록 보장하기 위해서다. 덤프 비교(diff)에 중요하다. 그 다음 sortDumpableObjects가 실제 위상 정렬을 실행하며, 타입-이름 순서는 “불필요한 재배열을 최소화”하기 위한 동점 처리 기준으로만 쓰인다.
// sortDumpableObjects — pg_dump_sort.c (condensed)preDataBoundId = preBoundaryId; /* the pre-data/data section boundary */postDataBoundId = postBoundaryId; /* the data/post-data boundary */
ordering = pg_malloc(numObjs * sizeof(DumpableObject *));while (!TopoSort(objs, numObjs, ordering, &nOrdering)) findDependencyLoops(ordering, nOrdering, numObjs);
memcpy(objs, ordering, numObjs * sizeof(DumpableObject *));while 루프가 사이클 처리 구조다. TopoSort는 성공 시 true를 반환한다. false를 반환하면 배치할 수 없었던 객체들(사이클 내부 또는 하위에 있는 것들)로 ordering을 채운다. findDependencyLoops가 실제 사이클을 찾아 repairDependencyLoop를 호출해 끊는다. 그 후 정렬이 재시도된다.
두 경계 ID는 더미 객체(DO_PRE_DATA_BOUNDARY, DO_POST_DATA_BOUNDARY)를 가리킨다. 정렬은 이것들을 일반 노드처럼 취급한다. 모든 pre-data 객체를 pre-data 경계의 선행자로, 모든 data 객체를 pre-data 경계의 후계자이자 post-data 경계의 선행자로 만들면, 섹션 분리가 별도 패스 없이 같은 위상 정렬에서 자연스럽게 떨어진다.
루프 수정: 객체를 여러 TOC 항목으로 분리
섹션 제목: “루프 수정: 객체를 여러 TOC 항목으로 분리”repairDependencyLoop는 알려진 사이클 형태의 목록이다. 타입과 I/O 함수가 서로를 참조하는 경우, 뷰와 ON SELECT 규칙이 서로를 참조하는 경우, 두 테이블이 상호 외래 키를 가진 경우 등이 있다. 각 경우에 에지 하나를 제거하는 수동 코딩된 수정이 있다. 보통 객체의 일부를 post-data로 미루는 방식이다.
// repairDependencyLoop — pg_dump_sort.c (condensed)/* Datatype and one of its I/O or canonicalize functions */if (nLoop == 2 && loop[0]->objType == DO_TYPE && loop[1]->objType == DO_FUNC){ repairTypeFuncLoop(loop[0], loop[1]); return;}/* View (including matview) and its ON SELECT rule */if (nLoop == 2 && loop[0]->objType == DO_TABLE && loop[1]->objType == DO_RULE && (((TableInfo *) loop[0])->relkind == RELKIND_VIEW || … RELKIND_MATVIEW) && ((RuleInfo *) loop[1])->ev_type == '1' && … ){ repairViewRuleLoop(loop[0], loop[1]); /* dump view as CREATE+rule */ return;}/* … table-constraint loops, larger indirect loops, etc. … */자기 참조 뷰가 pre-data에서 CREATE VIEW … AS SELECT NULL(또는 더미 테이블)로, post-data에서 CREATE RULE "_RETURN"으로 덤프되는 이유가 바로 이것이다. 루프 수정이 그것을 분리했기 때문이다.
TOC 구성: ArchiveEntry
섹션 제목: “TOC 구성: ArchiveEntry”정렬이 끝나면 pg_dump.c는 정렬된 배열을 순회하면서 각 객체에 dumpDumpableObject를 호출한다. 타입별로 디스패치된다(dumpTableSchema, dumpType, dumpFunc, …). 각 dumpXxx는 객체의 SQL 텍스트를 구성하고 ArchiveEntry로 TOC 항목에 등록한다. 태그·소유자·섹션·CREATE/DROP SQL·선택적 COPY 문·선택적 데이터 덤퍼 콜백·의존 목록을 제공한다.
// ArchiveEntry — pg_backup_archiver.c (condensed)newToc = pg_malloc0(sizeof(TocEntry));/* link into the circular TOC list */newToc->prev = AH->toc->prev; newToc->next = AH->toc;AH->toc->prev->next = newToc; AH->toc->prev = newToc;
newToc->dumpId = dumpId;newToc->section = opts->section; /* PRE_DATA / DATA / POST_DATA */newToc->tag = pg_strdup(opts->tag);newToc->desc = pg_strdup(opts->description); /* "TABLE", "INDEX", … */newToc->defn = opts->createStmt ? pg_strdup(opts->createStmt) : NULL;newToc->dropStmt = opts->dropStmt ? pg_strdup(opts->dropStmt) : NULL;newToc->copyStmt = opts->copyStmt ? pg_strdup(opts->copyStmt) : NULL;newToc->dataDumper = opts->dumpFn; /* table-data callback */memcpy(newToc->dependencies, opts->deps, opts->nDeps * sizeof(DumpId));desc 문자열(“TABLE”, “INDEX”, “FK CONSTRAINT”, “TABLE DATA”, “DATABASE”, …)은 실질적 의미를 가진다. 복원 시 _tocEntryRequired 필터가 전적으로 desc 문자열 비교에 의존하기 때문이다. 설명자는 객체의 복원 정책 클래스 역할도 겸한다.
테이블 데이터: COPY 훅
섹션 제목: “테이블 데이터: COPY 훅”테이블의 정의는 TOC의 SQL 문자열이다. 데이터는 그렇지 않다. 데이터는 요청 시 행을 스트리밍하는 콜백(dataDumper)이다. 대부분의 테이블에서 그 콜백은 dumpTableData_copy다. 서버 측 COPY … TO stdout을 실행하고 프로토콜의 CopyData 행을 아카이브로 직접 중계한다.
// dumpTableData_copy — pg_dump.c (condensed)column_list = fmtCopyColumnList(tbinfo, clistBuf); /* explicit col order */if (tdinfo->filtercond || tbinfo->relkind == RELKIND_FOREIGN_TABLE) appendPQExpBuffer(q, "COPY (SELECT %s FROM %s %s) TO stdout;", …, fmtQualifiedDumpable(tbinfo), tdinfo->filtercond ? tdinfo->filtercond : "");else appendPQExpBuffer(q, "COPY %s %s TO stdout;", fmtQualifiedDumpable(tbinfo), column_list);데이터를 콜백으로 미루는 것이 아카이브 형식을 플러그 가능하게 만드는 핵심이다. plain 덤프는 콜백을 실행해 SQL을 인라인으로 기록한다. custom/directory 형식은 WriteDataChunks 중에 콜백을 실행하고 압축된 CopyData 블록을 TOC에 기록된 오프셋에 저장한다. 복원 시 그 위치로 직접 탐색할 수 있다. --inserts 모드는 콜백을 dumpTableData_insert로 교체해 INSERT 문을 내보낸다. 다른 엔진으로의 이식성은 높지만 훨씬 느리다.
하나의 vtable 뒤에 있는 네 가지 아카이브 형식
섹션 제목: “하나의 vtable 뒤에 있는 네 가지 아카이브 형식”CreateArchive가 형식을 선택하고, _allocAH가 그 형식의 초기화 함수로 디스패치한다. 초기화 함수는 함수 포인터 vtable(WriteDataPtr, StartTocPtr, ReadTocPtr, ClosePtr, …)을 채운다. 이후 아카이버의 모든 작업은 vtable 경유로 호출되며 구체적인 형식 이름은 드러나지 않는다.
// _allocAH — pg_backup_archiver.c (condensed)switch (AH->format){ case archCustom: InitArchiveFmt_Custom(AH); break; case archNull: InitArchiveFmt_Null(AH); break; /* plain text */ case archDirectory: InitArchiveFmt_Directory(AH); break; case archTar: InitArchiveFmt_Tar(AH); break; default: pg_fatal("unrecognized file format \"%d\"", …);}형식은 pg_dump의 main에서 -F 옵션 글자로 선택된다.
-F | 열거형 | 백엔드 | 탐색 가능 / 병렬 | TOC |
|---|---|---|---|---|
p / plain | archNull | pg_backup_null.c | 불가 / 불가 | 없음 — 순수 SQL 스트림 |
c / custom | archCustom | pg_backup_custom.c | 가능(fseeko일 때) / 복원만 | 단일 파일, 압축 블록 |
d / directory | archDirectory | pg_backup_directory.c | 가능 / 덤프·복원 모두 | 테이블별 파일 디렉터리 + toc.dat |
t / tar | archTar | pg_backup_tar.c | 제한적 / 불가 | 단일 tar, toc.dat 멤버 |
병렬 덤프는 directory 형식에서만 의미가 있다. 워커마다 별도 파일을 쓰기 때문이다. tar나 단일 custom 파일은 조율된 탐색이 필요해서 병렬 덤프를 지원하지 않는다. custom·directory·tar는 모두 실제 TOC를 가지므로 pg_restore를 통한 선택적·재정렬 복원이 가능하다. 일반 텍스트는 psql이 재생하는 일회성 SQL 스크립트로, 목록화나 필터링을 지원하지 않는다. main에서 plain 경로는 특별하다. 컨테이너 형식이 모든 출력을 CloseArchive로 미루는 것과 달리, SQL을 내보내기 위해 RestoreArchive를 즉시 실행한다.
복원: ReadToc, 요구 사항 필터링, 섹션 패스
섹션 제목: “복원: ReadToc, 요구 사항 필터링, 섹션 패스”pg_restore는 아카이브를 열고(OpenArchive → _allocAH 읽기 모드), ReadToc로 직렬화된 WriteToc 출력에서 TOC 목록을 재구성한다. ProcessArchiveRestoreOptions는 목록을 한 번 순회하면서 _tocEntryRequired로 각 항목에 reqs 비트마스크를 표시한다. 이 함수는 항목별로, 오직 desc/tag 문자열 비교와 RestoreOptions만으로, 항목의 스키마와 데이터를 내보낼지 결정한다.
// _tocEntryRequired — pg_backup_archiver.c (condensed)int res = REQ_SCHEMA | REQ_DATA;if (strcmp(te->desc, "ENCODING") == 0 || … "STDSTRINGS" … || … "SEARCHPATH" …) return REQ_SPECIAL;if (strcmp(te->desc, "STATISTICS DATA") == 0){ if (!ropt->dumpStatistics) return 0; res = REQ_STATS; }/* DATABASE / DATABASE PROPERTIES only when --create */if (strcmp(te->desc, "DATABASE") == 0 || … "DATABASE PROPERTIES" …) return ropt->createDB ? REQ_SCHEMA : 0;if (ropt->aclsSkip && _tocEntryIsACL(te)) return 0;if (ropt->no_comments && strcmp(te->desc, "COMMENT") == 0) return 0;/* … no_policies / no_publications / no_security_labels exclusions … */RestoreArchive는 TOC 순서대로 항목을 재생한다. restore_toc_entry는 각 항목을 스키마 부분(REQ_SCHEMA가 설정되면 _printTocEntry가 defn을 내보냄)과 데이터 부분(REQ_DATA가 설정되면 데이터 블록을 읽어 적용)으로 나눈다. 덤프 시 이미 위상 정렬이 끝났고 섹션 경계가 실제 TOC 항목이므로, 직렬 복원은 순서대로 걷는 것만으로 충분하다.
병렬 복원: 프리포크, 준비 힙, 멀티패스
섹션 제목: “병렬 복원: 프리포크, 준비 힙, 멀티패스”병렬 복원(-j N, 컨테이너 형식 + --use-db)은 세 단계 엔진이다. restore_toc_entries_prefork가 모든 pre-data 항목을 단일 부모 접속에서 실행한다. 이 항목들은 가볍고 순서에 민감하다. data와 post-data 항목은 pending_list에 쌓인다. 그 후 restore_toc_entries_parallel이 **준비 힙(ready heap)**을 소비하는 워커 풀을 구동한다. 힙은 데이터 크기 기준으로 정렬된다. 가장 큰 것이 먼저다. 풀이 거대한 테이블 하나에 굶주리지 않도록 하기 위해서다.
// restore_toc_entries_parallel — pg_backup_archiver.c (condensed)ready_heap = binaryheap_allocate(AH->tocCount, TocEntrySizeCompareBinaryheap, NULL);AH->restorePass = RESTORE_PASS_MAIN;move_to_ready_heap(pending_list, ready_heap, AH->restorePass);for (;;){ next_work_item = pop_next_work_item(ready_heap, pstate); if (next_work_item != NULL) DispatchJobForTocEntry(AH, pstate, next_work_item, ACT_RESTORE, mark_restore_job_done, ready_heap); else if (IsEveryWorkerIdle(pstate)) { if (AH->restorePass == RESTORE_PASS_LAST) break; AH->restorePass++; /* advance MAIN→ACL→POST_ACL */ move_to_ready_heap(pending_list, ready_heap, AH->restorePass); continue; } WaitForWorkers(AH, pstate, next_work_item ? WFW_ONE_IDLE : WFW_GOT_STATUS);}두 가지 정확성 메커니즘이 함께 작동한다. pop_next_work_item은 이미 실행 중인 워커와 잠금 충돌이 생길 항목을 디스패치하지 않는다(has_lock_conflicts). 같은 테이블에서 동시 인덱스 빌드와 ALTER TABLE … ADD CONSTRAINT가 충돌하는 경우가 그 예다. mark_restore_job_done은 reduce_dependencies를 호출한다. 완료된 항목에 의존하던 모든 항목의 블록 카운트를 감소시키고, 이제 준비 상태가 된 항목을 준비 힙으로 옮긴다. restorePass 카운터(RESTORE_PASS_MAIN → RESTORE_PASS_ACL → RESTORE_PASS_POST_ACL)는 ACL과 그에 대한 댓글/보안 레이블 같은 몇 가지 클래스를 그래프와 무관하게 나중 패스로 강제한다. 이 항목들의 의존 관계가 완전히 모델링되지 않았기 때문이다.
pg_dumpall: 클러스터 전역 객체와 데이터베이스별 위임
섹션 제목: “pg_dumpall: 클러스터 전역 객체와 데이터베이스별 위임”pg_dump는 하나의 데이터베이스에 접속하며 그 데이터베이스의 카탈로그 안에 있는 객체만 볼 수 있다. 공유 카탈로그(pg_authid, pg_db_role_setting, pg_tablespace)에 있는 클러스터 전역 객체는 덤프할 수 없다. pg_dumpall이 그 공백을 채운다. main이 하나의 데이터베이스(기본값 postgres)에 접속해 전역 객체를 SQL로 직접 내보낸 뒤, 데이터베이스별로 pg_dump를 실행한다.
// dumpDatabases / runPgDump — pg_dumpall.c (condensed)/* globals first, written straight to the output script */dumpRoles(conn); /* CREATE ROLE … from pg_authid */dumpRoleMembership(conn); /* GRANT role TO role from pg_auth_members */dumpTablespaces(conn); /* CREATE TABLESPACE … from pg_tablespace *//* then each database via a child pg_dump process */for each datname: create_opts = "--create"; /* or "--clean --create" */ ret = runPgDump(dbname, create_opts); /* exec pg_dump -Fp / -Fa */runPgDump는 pg_dump 바이너리를 -Fp(plain)로 호출하는 커맨드 라인을 구성한다. 파일에 쓸 때는 문서화되지 않은 -Fa(plain-append)를 사용해 여러 데이터베이스가 하나의 스크립트에 이어붙여지게 한다. 따라서 출력은 항상 plain SQL이다. pg_dumpall에는 자체 아카이브 형식이 없다. CREATE DATABASE … OWNER와 데이터베이스별 ALTER ROLE … SET 참조가 먼저 해결되어야 하므로 역할을 데이터베이스보다 먼저 덤프한다. template0은 건너뛰고, template1과 postgres는 이미 존재한다고 가정한다. 스크립트는 CREATE 대신 \connect를 사용한다.
소스 워크스루
섹션 제목: “소스 워크스루”심볼 이름에 앵커를 두어야 한다. 라인 번호는 사용하지 않는다. 재위치 확인은
git grep -n '<symbol>' src/bin/pg_dump/로 한다. 아래 테이블의 라인 번호는 커밋273fe94기준 힌트다.
드라이버와 옵션 처리 (pg_dump.c)
섹션 제목: “드라이버와 옵션 처리 (pg_dump.c)”main— 옵션 파싱,-F글자에 대한parseArchiveFormat, 접속,CreateArchive, 추출 → 정렬 →dumpDumpableObject루프 →SetArchiveOptions/ProcessArchiveRestoreOptions→RestoreArchive(plain) 또는CloseArchive(컨테이너).parseArchiveFormat—p/c/d/t(및 긴 이름)를archNull/archCustom/archDirectory/archTar에 매핑.getTableData/makeTableDataInfo—DO_TABLE_DATA객체를 생성하고 각 덤프 가능 테이블에dumpTableData데이터 덤퍼를 붙임.dumpTableData_copy/dumpTableData_insert— 두 가지 데이터 덤퍼 콜백(서버 측COPY … TO stdoutvs.INSERT문).dumpTableSchema— 테이블의CREATE TABLE텍스트를 구성하고ArchiveEntry로 등록;dumpDumpableObject가 타입 디스패치 허브.dumpDatabase—CREATE DATABASE/DATABASE PROPERTIES항목 내보내기.getDependencies—pg_depend를 읽어addObjectDependency에지로 변환.selectDumpableTable/selectDumpableNamespace— 포함/제외 패턴에서 각 객체의dump컴포넌트 마스크를 설정하는 정책 서브루틴.
객체 모델과 카탈로그 추출 (common.c)
섹션 제목: “객체 모델과 카탈로그 추출 (common.c)”getSchemaData— 순서가 정해진getXxx카탈로그 읽기 시퀀스;TableInfo배열 반환.AssignDumpId/createDumpId—DumpId할당,dumpIdMap과catalogIdHash에 등록.recordAdditionalCatalogID— 두 번째CatalogId를 기존 객체에 매핑(예: 배열 타입 ↔ 베이스 타입).findObjectByDumpId/findObjectByCatalogId/findTableByOid— 세 가지 조회 함수.addObjectDependency/removeObjectDependency— 객체의dependencies[]배열 변경.getDumpableObjects— 정렬을 위해dumpIdMap을 수정 가능한 배열로 평탄화.flagInhTables/flagInhAttrs— 자식 → 부모 링크를 연결하고 상속된 컬럼/기본값을 억제.
순서 결정 (pg_dump_sort.c)
섹션 제목: “순서 결정 (pg_dump_sort.c)”sortDumpableObjectsByTypeName/DOTypeNameCompare—dbObjectTypePriority[objType]후 스키마/이름 기준 안정 사전 정렬.dbObjectTypePriority[]—DumpableObjectType별 우선순위 배열(지정 이니셜라이저;StaticAssertDecl이 길이를 보호).sortDumpableObjects— 섹션 경계 ID 설정,TopoSort/findDependencyLoops루프(비순환이 될 때까지).TopoSort— 입력 순서를 보존하기 위해binaryheap우선순위 큐를 사용한DumpId기반 Knuth Vol. 1 위상 정렬.findDependencyLoops/repairDependencyLoop— 구체적인 사이클을 감지하고 형태별 수정으로 디스패치(repairTypeFuncLoop,repairViewRuleLoop, 테이블-제약 조건 분리 등).
아카이브 엔진 (pg_backup_archiver.c)
섹션 제목: “아카이브 엔진 (pg_backup_archiver.c)”CreateArchive/OpenArchive/_allocAH—ArchiveHandle할당, 형식 선택,InitArchiveFmt_*호출._discoverArchiveFormat— 복원 시-F미지정인 경우 입력 아카이브의 매직 탐지.ArchiveEntry—TocEntry를 구성하고 순환 TOC에 삽입.WriteToc/ReadToc— TOC 직렬화/역직렬화(reqs에REQ_SCHEMA|REQ_DATA|REQ_STATS|REQ_SPECIAL이 포함된 항목만).WriteDataChunks/WriteDataChunksForTocEntry— 각 항목의dataDumper실행; 병렬 덤프 경로는 크기 기준 정렬 후DispatchJobForTocEntry.ProcessArchiveRestoreOptions/_tocEntryRequired—RestoreOptions에서 항목별reqs비트마스크 계산.RestoreArchive/restore_toc_entry/_printTocEntry— 직렬 복원: TOC 순서대로 각 항목의 스키마 부분 후 데이터 부분 내보내기.restore_toc_entries_prefork/restore_toc_entries_parallel— 세 단계 병렬 복원 드라이버.pop_next_work_item/move_to_ready_heap/reduce_dependencies/mark_restore_job_done— 준비 힙 스케줄링과 의존 카운트 감소.parallel_restore—restore_toc_entry를 호출하는 워커 측 진입점.SortTocFromFile—pg_restore -L목록 파일에서 TOC 재정렬/필터링.
클러스터 전역 객체 (pg_dumpall.c)
섹션 제목: “클러스터 전역 객체 (pg_dumpall.c)”main— 접속, 선택적dropDBs,dumpRoles,dumpRoleMembership,dumpTablespaces,dumpDatabases순서.dumpRoles/dumpRoleMembership—pg_authid/pg_auth_members에서CREATE ROLE/GRANT.dumpTablespaces—pg_tablespace에서CREATE TABLESPACE.dumpDatabases/runPgDump—-Fp/-Fa로pg_dump바이너리에 데이터베이스별 위임.expand_dbname_patterns—pg_database에서--database패턴 해석.
위치 힌트 (2026-06-06 기준, REL_18 273fe94)
섹션 제목: “위치 힌트 (2026-06-06 기준, REL_18 273fe94)”| 심볼 | 파일 | 라인 |
|---|---|---|
main | pg_dump.c | 414 |
selectDumpableTable | pg_dump.c | 2030 |
dumpTableData_copy | pg_dump.c | 2326 |
dumpTableData | pg_dump.c | 2816 |
getTableData | pg_dump.c | 2966 |
dumpDatabase | pg_dump.c | 3228 |
getTables | pg_dump.c | 7026 |
dumpDumpableObject | pg_dump.c | 11487 |
dumpTableSchema | pg_dump.c | 16919 |
getDependencies | pg_dump.c | 19803 |
getSchemaData | common.c | 98 |
flagInhTables | common.c | 269 |
AssignDumpId | common.c | 657 |
findObjectByCatalogId | common.c | 778 |
getDumpableObjects | common.c | 797 |
addObjectDependency | common.c | 818 |
dbObjectTypePriority[] | pg_dump_sort.c | 105 |
sortDumpableObjectsByTypeName | pg_dump_sort.c | 192 |
sortDumpableObjects | pg_dump_sort.c | 558 |
TopoSort | pg_dump_sort.c | 610 |
findDependencyLoops | pg_dump_sort.c | 759 |
repairDependencyLoop | pg_dump_sort.c | 1173 |
CreateArchive | pg_backup_archiver.c | 230 |
RestoreArchive | pg_backup_archiver.c | 343 |
restore_toc_entry | pg_backup_archiver.c | 843 |
ArchiveEntry | pg_backup_archiver.c | 1240 |
_allocAH | pg_backup_archiver.c | 2376 |
WriteDataChunks | pg_backup_archiver.c | 2496 |
WriteToc | pg_backup_archiver.c | 2610 |
ReadToc | pg_backup_archiver.c | 2708 |
_tocEntryRequired | pg_backup_archiver.c | 2986 |
restore_toc_entries_prefork | pg_backup_archiver.c | 4311 |
restore_toc_entries_parallel | pg_backup_archiver.c | 4434 |
pop_next_work_item | pg_backup_archiver.c | 4711 |
reduce_dependencies | pg_backup_archiver.c | 5060 |
dumpRoles | pg_dumpall.c | 823 |
dumpTablespaces | pg_dumpall.c | 1382 |
dumpDatabases | pg_dumpall.c | 1628 |
runPgDump | pg_dumpall.c | 1724 |
소스 검증 (2026-06-06 기준)
섹션 제목: “소스 검증 (2026-06-06 기준)”커밋
273fe94의 소스에 관한 사실들이다. 외부 자료 없이 읽을 수 있다. 미해결 질문은 뒤에 이어진다.
검증된 사실
섹션 제목: “검증된 사실”-
네 가지 아카이브 형식은 정확히
archCustom,archTar,archNull,archDirectory다.pg_backup.h에서 확인했다.ArchiveFormat열거형은archUnknown = 0, archCustom = 1, archTar = 3, archNull = 4, archDirectory = 5다. 값 2는 의도적으로 없다.-F글자p/plain은archNull에 매핑되므로, 일반 텍스트 경로는 별도 형식이 아닌 null 백엔드를 재사용한다.pg_dump.c의parseArchiveFormat에서 확인했다. -
_allocAH는InitArchiveFmt_*로 형식 선택을 디스패치하며, 이후의 모든 아카이버 작업은ArchiveHandle의 함수 포인터 멤버로 처리된다._allocAH에서 확인했다.switch (AH->format)이 네 가지 초기화 함수 중 하나를 호출하고, 아카이버 본체(WriteToc,RestoreArchive등)는AH->WriteExtraTocPtr,AH->ReadExtraTocPtr,AH->ClosePtr등을 호출하며 구체적인 형식 함수를 직접 호출하지 않는다. 전략 패턴 분리다. -
의존 정렬은 사이클을 오류로 처리하지 않고 그래프 재작성으로 처리하는 재시도 루프로 감싸져 있다.
sortDumpableObjects에서 확인했다.while (!TopoSort(...)) findDependencyLoops(...).TopoSort는false를 반환하며 배치 불가 객체를 나열한다.findDependencyLoops→repairDependencyLoop가 그래프를 변형(removeObjectDependency와 객체 분리)하고 비순환이 될 때까지 정렬이 재시도된다. -
pre-data / data / post-data 섹션 분리는 같은 위상 정렬의 일반 노드로 구현된다. 확인:
sortDumpableObjects가preDataBoundId/postDataBoundId(DO_PRE_DATA_BOUNDARY/DO_POST_DATA_BOUNDARY더미 객체의DumpId)를 정적 변수에 저장하고, 경계 객체가 다른 것들처럼TopoSort에 참여한다. 별도의 섹션 정렬 패스는 없다. -
WriteToc는reqs비트마스크가 비어있지 않은 항목만 기록한다.WriteToc에서 확인했다.(te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS | REQ_SPECIAL)) != 0인 항목만 카운트하고 기록한다.reqs마스크는 앞서ProcessArchiveRestoreOptions/_tocEntryRequired가 계산한다. 덤프된 TOC는 이미 포함/제외 결정을 반영한다. -
_tocEntryRequired는desc문자열로 복원 정책을 결정한다. 확인: 함수가strcmp(te->desc, "ENCODING"),"STATISTICS DATA","DATABASE",_tocEntryIsACL,"COMMENT","POLICY","PUBLICATION","SECURITY LABEL", … 비교의 계단식 구조다. TOC 항목의 사람이 읽을 수 있는 설명자가 동시에 복원 클래스 키 역할을 한다. -
병렬 덤프는 directory 형식에서만 의미가 있고, 병렬 복원에는 실제 접속이 추가로 필요하다.
pg_dump의main에서 확인했다.numWorkers > 1요청은 형식이archDirectory가 아니면 오류가 된다.RestoreArchive에서 확인:parallel_mode = (AH->public.numWorkers > 1 && ropt->useDB). 단일 파일 custom/tar 덤프는 독립 워커가 쓸 수 없다. -
병렬 복원은 의존 관계와 잠금 충돌 모두를 준수한다. 확인:
reduce_dependencies(mark_restore_job_done에서 호출됨)가 블록 카운트를 감소시키고 새로 준비된 항목을 승격시킨다.pop_next_work_item은 현재 실행 중인 워커와has_lock_conflicts가 있는 준비 항목을 건너뛴다. 드라이버는restorePass값으로 ACL/post-ACL 클래스를 나중 패스에 강제 배정한다. -
pg_dumpall은 plain SQL만 생성하며 테이블 덤프를pg_dump바이너리에 위임한다.pg_dumpall.c의runPgDump에서 확인했다. 외부pg_dump를-Fp(또는 파일 쓰기 시-Fa)로 호출하는 커맨드 라인을 구성한다.pg_dumpall자체는 전역(dumpRoles,dumpRoleMembership,dumpTablespaces)만 인라인으로 내보낸다. 아카이브 형식이 없다.
미해결 질문
섹션 제목: “미해결 질문”-
병렬 덤프 워커의 스냅샷 동기화.
-j N덤프의 일관 스냅샷 보장은 리더가 스냅샷을 내보내고 각 워커가SET TRANSACTION SNAPSHOT으로 그것을 가져오는 방식에 의존한다. 정확한 핸드셰이크는parallel.c/pg_backup_db.c에 있으며 여기서는 개요만 다뤘다.set_archive_cancel_info와 워커 접속 설정을 추적하는 작업은parallel.c중심의 후속 문서에 남겨둔다. -
통계 덤프(
STATISTICS DATA). PG18은 옵티마이저 통계를 1급 TOC 클래스(REQ_STATS,DO_REL_STATS객체 타입,--statistics-only/--no-statistics)로 덤프한다. 통계가 직렬화되고 재적용되는 방식(pg_restore_relation_stats)은_tocEntryRequired에서 언급되지만 끝까지 추적하지는 않았다. -
custom 형식의 두 번째 TOC 쓰기.
WriteToc에는 두 번째 호출 시te->defnLen설정에 대한 특수 처리가 있다.fseeko가 사용 가능함을 확인한 후 데이터 오프셋을 제자리에 재작성하는 custom 형식만 이를 사용한다. 정확한 닫기 시점 재작성 시퀀스는pg_backup_custom.c의_CloseArchive에 있으며 언급만 하고 추적하지 않았다.
PostgreSQL 너머 — 비교 설계와 연구 방향
섹션 제목: “PostgreSQL 너머 — 비교 설계와 연구 방향”포인터만 제시한다. 각 항목은 후속 문서의 출발점이며 “같은 문제가 다른 곳에서는 어떻게 보이는가?”라는 질문으로 구성했다.
-
mysqldump— 스트리밍 SQL, 중간 그래프 없음. MySQL의 고전적 논리 덤프 도구는INFORMATION_SCHEMA와SHOW CREATE TABLE을 읽어 카탈로그 순서로CREATE/INSERTSQL을 stdout에 직접 스트리밍한다. 인메모리DumpableObject그래프도, 위상 정렬도 없다.pg_dump_sort.c가 구조적으로 해결하는 순서 문제를 복원 시SET FOREIGN_KEY_CHECKS=0으로 우회한다. 그 대가로mysqldump는 선택적 복원, 의존성 인식 병렬성, 재정렬 가능한 TOC를 제공하지 못한다. 컨테이너가 없고 스크립트만 있다. PostgreSQL에서 가장 유사한 것은-Fp(plain) 경로다. 이것도 일회성 스크립트다. PostgreSQL의 추가 메커니즘은 컨테이너 형식에서만 의미가 있다. -
MySQL Shell
util.dumpInstance/dumpSchemas. 현대적인 MySQL 덤프 도구(MySQL Shell 8.0+)는pg_dump의 컨테이너 모델로 의도적으로 이동한 것이다. 테이블별 청크 파일과 메타데이터로 이루어진 디렉터리를 기록하고, 병렬 덤프와 병렬 적재(util.loadDump)를 지원하며, 격리를 위해LOCK INSTANCE FOR BACKUP과 일관 스냅샷을 사용한다.pg_backup_directory.c의 워커별 파일 레이아웃 및 준비 힙 스케줄러와 나란히 비교하면 “청크 디렉터리 + 의존성 인식 로더” 설계의 두 가지 독립적 재발견을 보여줄 것이다. -
Oracle Data Pump(
expdp/impdp) — 마스터 테이블. Data Pump는 TOC를 덤프 스키마의 실제 데이터베이스 테이블(마스터 테이블)로 구현한다. 모든 객체, 메타데이터, 워커 진행 상황을 기록한다. 이것이 Data Pump 작업을 일시 중지하고 재개하며 다른 세션에서 연결할 수 있게 하는 이유다. PostgreSQL의 TOC는 아카이브 내부와 메모리에 있고, 라이브 테이블이 아니다.pg_restore작업은 중간에 재개할 수 없다. 재시작/재개 시맨틱 비교(Data Pump의 마스터 테이블 vs.pg_restore의 전부 아니면 전무--single-transaction)가 깔끔한 후속 문서 주제다. -
SQL Server SMO + BACPAC. SQL Server의 논리 내보내기(SqlPackage / SMO를 통한
.bacpac)는 스키마 모델(DACPAC)을 패키지된 데이터와 분리한다. SMO 객체 모델은sys.*를 탐색해 DDL을 스크립팅한다.dumpTableSchema가 하는 것과 같은 “카탈로그-to-DDL 디컴파일러” 역할이다. 스키마 모델은 배포 전에 단위로 검증된다. PostgreSQL에는 동등한 것이 없다.pg_restore실패는 문 단위로 감지된다. -
연속 덤프로서의 논리 복제.
pg_dump는 시점 논리 스냅샷이다. 논리 디코딩 /pgoutput(postgres-logical-decoding.md,postgres-pgoutput.md참조)은 “페이지가 아닌 의도를 전달한다”는 같은 아이디어의 스트리밍 형태다. 논리 복제 구독의 초기 테이블 동기화는 개념적으로 내보낸 스냅샷 아래에서 각 테이블을COPY기반으로 덤프하는 것과 같다.pg_dump -j가 사용하는 것과 동일한 스냅샷 내보내기 메커니즘이다.tablesync.c가 스냅샷 내보내기를 재사용하는 방식을 추적하면 두 이야기가 통합된다. -
--filter와 객체 선택 문법. PG17은 오래된-t/-T/-n/-N패턴 플래그 위에--filter=FILE옵션(파일에서 읽은 포함/제외 규칙)을 추가했다. 이 규칙은 동일한selectDumpableTable/selectDumpableNamespace컴포넌트 마스크 기계로 입력된다. Data Pump의INCLUDE/EXCLUDE메타데이터 필터 및mysqldump의 조잡한--ignore-table과 비교하면 객체 선택의 표현력 스펙트럼을 그릴 수 있다. -
연구 방향: 병렬 안전한 일관 논리 스냅샷.
-j N워커가 하나의 스냅샷을 공유하는 방법(SET TRANSACTION SNAPSHOT으로 리더가 내보낸 스냅샷 ID를 가져오는 방식)은 더 깊은 주제의 실용적 측면이다. MVCC 스냅샷을 세션 간에 내보내고 가져오면서 vacuum을 막는 것이다. 메커니즘은postgres-mvcc-snapshots.md와postgres-procarray.md에 있다. 비용 모델(멀티 시간 병렬 덤프가 클러스터 전체에서 오래된 행 버전을 얼마나 오래 고정하는가)은 백업 계획 측면에서 별도의 문서가 필요한 주제다.
참고 자료
섹션 제목: “참고 자료”PostgreSQL 소스 (/data/hgryoo/references/postgres, REL_18 273fe94)
섹션 제목: “PostgreSQL 소스 (/data/hgryoo/references/postgres, REL_18 273fe94)”src/bin/pg_dump/pg_dump.c— 드라이버:main,parseArchiveFormat,getTableData/makeTableDataInfo,dumpTableData_copy/dumpTableData_insert,dumpTableSchema,dumpDumpableObject,dumpDatabase,getDependencies,selectDumpableTable/selectDumpableNamespace.src/bin/pg_dump/common.c— 객체 모델:getSchemaData,AssignDumpId/createDumpId,recordAdditionalCatalogID,findObjectByDumpId/findObjectByCatalogId/findTableByOid,addObjectDependency/removeObjectDependency,getDumpableObjects,flagInhTables/flagInhAttrs.src/bin/pg_dump/pg_dump_sort.c— 순서 결정:sortDumpableObjectsByTypeName/DOTypeNameCompare,dbObjectTypePriority[],sortDumpableObjects,TopoSort,findDependencyLoops/repairDependencyLoop(및 형태별repairTypeFuncLoop/repairViewRuleLoop/ 테이블-제약 조건 수정).src/bin/pg_dump/pg_backup_archiver.c— 아카이브 엔진:CreateArchive/OpenArchive/_allocAH,_discoverArchiveFormat,ArchiveEntry,WriteToc/ReadToc,WriteDataChunks,ProcessArchiveRestoreOptions/_tocEntryRequired,RestoreArchive/restore_toc_entry/_printTocEntry,restore_toc_entries_prefork/restore_toc_entries_parallel,pop_next_work_item/move_to_ready_heap/reduce_dependencies/mark_restore_job_done.src/bin/pg_dump/pg_dumpall.c— 클러스터 전역:main,dumpRoles,dumpRoleMembership,dumpTablespaces,dumpDatabases/runPgDump,expand_dbname_patterns.src/bin/pg_dump/pg_backup.h—ArchiveFormat열거형(archCustom/archTar/archNull/archDirectory),RestoreOptions, publicArchive구조체.src/bin/pg_dump/pg_backup_archiver.h—ArchiveHandle,TocEntry, 함수 포인터 vtable 멤버,REQ_*및RESTORE_PASS_*상수.src/bin/pg_dump/parallel.c— (참조)ParallelState,DispatchJobForTocEntry,WaitForWorkers, 워커 포크/접속과 스냅샷 공유.- 형식 백엔드(참조, 미추적):
pg_backup_custom.c,pg_backup_directory.c,pg_backup_tar.c,pg_backup_null.c.
교재 (knowledge/research/dbms-general/)
섹션 제목: “교재 (knowledge/research/dbms-general/)”- Database System Concepts (Silberschatz, Korth, Sudarshan), “Recovery System” — 덤프 대 로그의 이중성; 논리 덤프는 바이트 복사가 아닌 재도출 레시피.
- Database Internals (Petrov), Part II — 논리 복제 대 물리 복제,
pg_dump와pg_basebackup을 가르는 이식성·정확성 교환관계. - The Art of Computer Programming, Vol. 1 (Knuth), §2.2.3 —
TopoSort가 구현하는 위상 정렬 알고리즘.
교차 참조 (형제 모듈 문서)
섹션 제목: “교차 참조 (형제 모듈 문서)”postgres-system-catalogs.md—getXxx추출기가 읽는 카탈로그(pg_class,pg_type,pg_proc,pg_constraint, …); 카탈로그 레이아웃 세부 사항은 그쪽으로 미룬다.postgres-dependency-tracking.md—getDependencies가 덤프 그래프로 변환하는pg_depend/pg_shdepend의미론; 의존 행 세부 사항은 그쪽으로 미룬다.postgres-mvcc-snapshots.md/postgres-procarray.md— 덤프(와 병렬 덤프)에 일관된 읽기를 제공하는 스냅샷 내보내기 메커니즘.postgres-copy.md—dumpTableData_copy가 사용하는COPY … TO stdout프로토콜; 복원 측COPY … FROM stdin.postgres-backup-basebackup.md/postgres-incremental-backup.md—pg_dump의 논리 대응물인 물리 백업 경로.postgres-pg-upgrade.md—pg_upgrade는pg_dump --binary-upgrade/pg_dumpall --globals-only를 사용해 메이저 버전 업그레이드 시 스키마를 전달한다.postgres-ddl-execution.md— 덤프가 내보내는 DDL을 재생하는 서버 측.