PostgreSQL 데이터 디렉토리(PGDATA)를 서버 없이 직접 읽는 포렌식 뷰어입니다. 테이블과 행을 보여 줄 뿐 아니라, 삭제된 행 · 수정 전 버전 · WAL 에만 남은 행까지 복구해 시각과 함께 보여 줍니다.
| 항목 | 내용 |
|---|---|
| 지원 버전 | PostgreSQL 17 · 18 (페이지·튜플·카탈로그 해석은 다른 버전도 대체로 동작, WAL 은 17 · 18 전용) |
| 입력 | PGDATA 폴더 또는 PGDATA 를 담은 zip (압축을 풀지 않고 읽음) |
| 원본 | 읽기 전용 — 원본 파일을 쓰거나 서버를 띄우지 않는다 |
| 엔진 | Python 3.12 표준 라이브러리만 사용 |
| 화면 | FastAPI + 단일 HTML (로컬 전용, 127.0.0.1) |
- 왜 서버를 띄우지 않는가
- 기능 한눈에 보기
- 설치와 실행
- 사용 설명서 (화면)
- 행의 상태와 출처 읽는 법
- 라이브러리로 쓰기 (상위 도구 연동)
- 검증 도구와 테스트
- 한계와 주의점
- 문제 해결
- 프로젝트 구조
- 라이선스
압수·수집한 PostgreSQL 폴더를 보는 흔한 방법은 같은 버전의 서버(예: Docker)를 띄워 SQL 로 조회하는 것입니다. 포렌식에서는 이 방법에 문제가 있습니다.
| 문제 | 설명 | 이 뷰어 |
|---|---|---|
| 증거 변형 | 서버가 시작되면 크래시 복구(WAL 재생)·체크포인트로 파일을 다시 쓴다 | 파일을 읽기만 한다 |
| 삭제 흔적 소실 | SQL 은 살아 있는 행만 보여 주고, autovacuum 이 지운 흔적을 정리한다 | 삭제·수정 전 행을 그대로 보여 준다 |
| WAL 미활용 | WAL 에 남은 옛 데이터·커밋 시각을 SQL 로는 볼 수 없다 | WAL 을 직접 해석한다 |
| 환경 의존 | 메이저 버전·아키텍처가 맞는 서버가 필요하다 | Python 만 있으면 된다 |
- 데이터베이스 · 스키마 · 테이블 목록: 카탈로그(
pg_class·pg_attribute·pg_type…)를 직접 해석 - 행 보기: 검색 · 정렬 · 페이지 나누기 · 상태/출처 필터 · CSV/JSON 내보내기
- 타입 디코딩: 정수·실수·문자열·
numeric·jsonb·bytea·배열·enum·도메인·날짜/시각·uuid·inet등 - 긴 값(TOAST): pglz/lz4 압축 해제, 외부 저장 청크 결합
- DROP 된 컬럼: 테이블을 다시 쓰기 전까지 남아 있는 원시 바이트 표시
| 기능 | 무엇을 찾나 |
|---|---|
| 삭제·수정 전 행 | 파일에 아직 남은 deleted · updated(구버전) · aborted(롤백) 튜플 |
| WAL 분석 | 트랜잭션 커밋/롤백 시각, WAL 에만 남은 행(전체 페이지 이미지 · INSERT/UPDATE 레코드), 파일에서 정리된 TOAST 청크 보충 |
| 재활용 세그먼트 | 이름과 다른 옛 WAL 이 남은 pg_wal 파일까지 페이지 주소로 정렬해 읽음 |
| 빈 공간 카빙 | 라인 포인터가 가리키지 않는 페이지 영역에 남은 옛 튜플 |
| 수정 이력 체인 | 같은 행의 버전들을 xmax = 다음 버전의 xmin 규칙으로 잇고, 바뀐 컬럼만 비교 |
| 인덱스 흔적 | 행은 지워졌는데 B-tree 인덱스에 남은 키 (사라진 행의 ID 등) |
- 시각 변환: epoch 밀리초로 저장된 정수 컬럼 자동 감지 → 날짜로 표시, 행마다 만든/지운 시각
- ID 링크: 값이 다른 테이블의 기본키와 같으면 클릭해 그 행으로 이동 (돌아가기 지원)
- 인용 복사: 입력 해시 · 파일 · 오프셋 · 트랜잭션 · 값 · 튜플 SHA-256 을 보고서용 텍스트로
- 행 상세: 튜플 헤더, 컬럼별 바이트 위치, 헥스 덤프(컬럼에 마우스를 올리면 해당 바이트 강조)
- 무결성: 입력 SHA-256 기록, 분석 후 재확인
- Windows / macOS / Linux 어디서나 Python 3.12 이상
- 화면을 쓰려면 FastAPI · Uvicorn (아래 명령으로 설치). 엔진만 쓰면 추가 설치가 필요 없다.
git clone https://github.com/ws1004/postgresql-viewer.git
cd postgresql-viewer
py -m venv .venv
.venv\Scripts\python -m pip install -r requirements.txt.venv\Scripts\python -X utf8 -m uvicorn pgviewer.api.main:app --host 127.0.0.1 --port 8000브라우저에서 http://127.0.0.1:8000 을 엽니다. 끝낼 때는 터미널에서 Ctrl+C.
로컬 전용입니다.
--host 127.0.0.1로만 띄우세요. 경로 입력 기능이 이 PC 의 파일을 읽기 때문에, 다른 Host 헤더 · 다른 출처의 요청 · 네트워크(UNC) 경로는 서버가 거부합니다.
화면 맨 위 입력칸에 경로를 넣고 [열기] 를 누릅니다.
| 입력 | 예 |
|---|---|
| zip | C:\cases\case01\postgresql.zip |
| PGDATA 폴더 | D:\case01\var\lib\postgresql\17\main |
| 그 상위 폴더 | D:\case01 (네 단계 아래까지 PG_VERSION + global/pg_control 을 찾는다) |
- [zip 업로드] 는 파일을 서버의
work/uploads/에 복사해 엽니다. 같은 PC 라면 원본을 복사하지 않는 경로 입력을 권장합니다. - 열면 오른쪽 위에 PG 버전 · 클러스터 상태 · SHA-256 이 표시됩니다.
shut down(초록) = 정상 종료. 다른 상태는 크래시나 운영 중 복사본일 수 있다.
- 네트워크 경로(
\\서버\공유)와 드라이브 루트(C:\)는 열 수 없습니다. 로컬로 복사한 뒤 여세요.
- 왼쪽 위 드롭다운: 데이터베이스. 템플릿이 아닌 첫 사용자 DB 가 자동 선택된다.
- 왼쪽 목록: 스키마별 테이블. 오른쪽 숫자는 파일 크기(0 B = 빈 테이블).
테이블 이름 필터로 검색시스템 카탈로그 · TOAST 보기를 켜면pg_catalog등 내부 테이블도 보인다
public.events table · oid 16569 · filenode 17010 base/16390/17010 수정 이력 7개
[✓ live 79] [✓ updated 14] │ [✓ 현재 파일 86] [✓ WAL 7] │ [ ] 수정 이력만
[값 검색 (Enter)] ✓시스템 정보 ✓삭제된 컬럼 ✓시각 변환 [CSV] [JSON]
| 조작 | 동작 |
|---|---|
| 상태 칩 체크 해제 | 그 상태의 행을 숨김 (예: live 를 끄면 삭제·수정 흔적만) |
| 출처 칩 체크 해제 | 현재 파일 / 빈 공간 / WAL 중 일부만 보기 |
| 수정 이력만 | 버전 체인이 있는 행만, 이력 순서대로 |
| 값 검색 | 모든 컬럼에서 부분 일치 (Enter) |
| 컬럼 머리글 클릭 | 정렬 (한 번 더 누르면 반대로) |
| 시스템 정보 | 상태 · 출처 · tid · xmin · xmax · 이력 열 표시 |
| 삭제된 컬럼 | DROP 된 컬럼(취소선 머리글)의 남은 바이트 표시 |
| 시각 변환 | epoch ms 컬럼을 날짜로, 만든 시각/지운 시각 열 추가 (UTC) |
이력 2/5 클릭 |
상세 패널에서 버전 비교로 바로 이동 |
| 밑줄 친 값 클릭 | 같은 값을 기본키로 가진 다른 테이블의 행으로 이동 |
| 빨간 점선 칸 | 해석 경고 (마우스를 올리면 이유) |
행 배경색: 빨강 = deleted, 주황 = updated, 회색 = aborted/unknown.
오른쪽에 패널이 열립니다. Esc 또는 [닫기] 로 닫습니다.
| 섹션 | 내용 |
|---|---|
| 위치 / 출처 | 현재 파일: 파일 경로 · 파일 내 오프셋(헥스 에디터로 바로 찾아갈 수 있는 값) · 블록/라인 포인터 · 페이지 LSN 빈 공간: 페이지 내 오프셋 · 신뢰도 · 판정 메모 WAL: 처음 나타난 LSN · 사용한 기록(전체 페이지 이미지/INSERT/UPDATE) · 결말(언제 DELETE/UPDATE 됐는지) · 헤더가 원본인지 합성인지 |
| 트랜잭션 · 튜플 헤더 | xmin(만든 트랜잭션)·xmax(지운/바꾼 트랜잭션)와 각각의 커밋 시각, ctid, infomask 플래그 |
| 수정 이력 | 버전 목록과 바뀐 컬럼만 비교 (노란 칸 = 이전 버전과 다른 값). 버전을 누르면 그 버전 상세로 |
| 컬럼 | 컬럼별 튜플 내 위치(오프셋+길이) · 저장 방식(short/plain/compressed/external) · 값. 정수 시각은 날짜 힌트 |
| 원시 바이트 | 튜플 헥스 덤프. 컬럼 행에 마우스를 올리면 해당 바이트가 노랗게 |
[인용 복사] 를 누르면 다음과 같은 텍스트가 클립보드에 들어갑니다.
[PostgreSQL Viewer 인용]
입력: C:\cases\case01\postgresql.zip (SHA-256 179b8ead…)
PGDATA: 17/main/ · PostgreSQL 17
데이터베이스: appdb (oid 16390) · 테이블: public.events (oid 16569)
행: h:0:22 · 출처 현재 파일 · 상태 live
위치: base/16390/17010 + <파일 내 오프셋> (0x…) · 블록 0 · lp 22
xmin 2888 (committed, 2026-10-23T14:42:35…) · xmax 0 (none) · ctid (0,22)
수정 이력: 2개 버전 중 2번째
값:
id = …
title = …
튜플 SHA-256: …
컬럼 번호 · 이름 · 타입 · 길이 · 정렬 · NOT NULL · 비고(DROP 된 컬럼, 시각으로 보이는 컬럼).
선택한 테이블 파일에 대한 WAL 기록을 시간순으로 보여 줍니다.
| 열 | 뜻 |
|---|---|
| LSN | WAL 위치 |
| 트랜잭션 시각 | 그 레코드를 쓴 트랜잭션의 커밋(또는 롤백) 시각 (UTC) |
| 작업 | INSERT · UPDATE · UPDATE(HOT) · DELETE · MULTI_INSERT · 페이지 이미지 · PRUNE … |
| 블록 / 오프셋 | 대상 위치. UPDATE 는 → (새 블록, 새 오프셋) |
위쪽 선택 상자로 작업 종류를 거를 수 있습니다. 많으면 앞 3000건만 표시합니다.
B-tree 인덱스의 리프 키 중 힙과 맞지 않는 것을 보여 줍니다.
| 상태 | 뜻 |
|---|---|
| 힙에 없음 | 가리키는 라인 포인터가 비었다 — 행이 지워지고 정리됐다 |
| 힙 슬롯 DEAD | 행이 지워지고 정리 직전 |
| 값 불일치 | 그 슬롯을 다른 행이 재사용 중 |
| 해석 불가 | 키나 힙 값을 해석하지 못해 비교할 수 없다 |
복구된 행 링크를 누르면 같은 위치·같은 키 값으로 복구한 행(WAL/빈 공간)의 상세로 이동합니다. HOT 정리로 생긴 REDIRECT 는 대상 행을 따라가 비교하므로 흔적으로 보고하지 않습니다.
- 입력: 경로 · 형식 · SHA-256 · [원본 해시 다시 확인] (분석 후 원본이 바뀌지 않았는지)
- pg_control: 버전 · CRC · 상태 · 마지막 갱신/체크포인트 시각 · 다음 XID/OID · WAL 레벨 · 블록 크기 · 데이터 체크섬
- 데이터베이스 목록
- WAL: 레코드 수(CRC 오류 수) · LSN 범위 · 시각 범위 · 세그먼트 파일별 시작 LSN과 재활용 여부
- 해석 검증: 카탈로그 자체 검증 결과 (✓/✗)
[CSV] / [JSON] 은 지금 걸린 필터(상태·출처·검색·수정 이력만)대로 내보냅니다.
앞쪽에 메타 열이 붙습니다: _rid _source _state _tid _xmin _xmin_time _xmax _xmax_time _chain _file _file_offset _wal_lsn.
- CSV 는
= + - @로 시작하는 문자열 앞에'를 붙입니다(스프레드시트 수식 실행 방지). 원본 값 그대로는 JSON 을 쓰세요. - JSON 은 WAL 출처 정보(
_wal)와 해석 경고(_errors)도 담습니다.
보고서에 링크로 남기거나 상위 도구에서 연결할 때 씁니다.
http://127.0.0.1:8000/?path=C:/cases/pg.zip&db=appdb&rel=public.events&rid=h:0:22&versions=1&time=1
| 파라미터 | 뜻 |
|---|---|
path |
입력 경로 (필수) |
db |
데이터베이스 이름 |
rel |
스키마.테이블 |
rid |
행 ID (아래 5-3) — 상세를 연다 |
versions=1 |
상세를 수정 이력 위치로 스크롤 |
chained=1 |
수정 이력만 보기 |
time=1 |
시각 변환 켜기 |
tab |
paneWal · paneIdx · paneInfo · paneCols |
| 상태 | 뜻 | 판정 근거 |
|---|---|---|
live |
현재 유효한 행 | 만든 트랜잭션 커밋 + 지운 트랜잭션 없음/롤백/잠금 전용 |
deleted |
삭제된 행 | 지운 트랜잭션 커밋 + ctid 가 자기 자신 |
updated |
UPDATE 이전 버전 | 지운 트랜잭션 커밋 + ctid 가 새 버전을 가리킴 |
aborted |
롤백된 INSERT | 만든 트랜잭션 롤백 (정상 종료 후 커밋 기록이 없는 경우 포함) |
in_progress |
진행 중 | 커밋 기록이 없고 판단 불가 (비정상 종료 복사본 등) |
unknown |
판단 근거 부족 | MultiXact, 서브트랜잭션, 파일에서 사라졌는데 삭제 기록이 없는 복구 행 등 |
커밋 여부는 튜플의 힌트 비트 → pg_xact(커밋 로그) → WAL 의 COMMIT/ABORT 기록 순으로 확인합니다.
| 출처 | 뜻 | 주의 |
|---|---|---|
현재 파일 (heap) |
테이블 파일에서 라인 포인터가 가리키는 튜플 | 가장 신뢰도가 높다 |
WAL (wal) |
WAL 의 전체 페이지 이미지나 INSERT/UPDATE 레코드에만 남은 튜플 | INSERT/UPDATE 레코드에서 온 행은 헤더 23바이트가 합성(xmin = 레코드 트랜잭션, ctid = 자기 위치). UPDATE 의 생략된 앞뒤 바이트를 WAL 안의 옛 튜플로 채운 행은 "재구성"으로 표시 |
빈 공간 (carved) |
페이지에서 라인 포인터가 가리키지 않는 영역에서 찾은 튜플 | 원래 라인 포인터를 모른다. 삭제/수정 구분이 WAL 로 확인되지 않으면 "불확실" 메모. 문자열 등 가변 길이 컬럼이 없는 테이블은 신뢰도 "낮음" |
같은 튜플이 여러 곳에 있으면 현재 파일 → WAL → 빈 공간 순으로 하나만 남깁니다.
| 형식 | 예 | 뜻 |
|---|---|---|
h:블록:lp |
h:0:22 |
현재 파일, 블록 0 · 라인 포인터 22 |
c:블록:오프셋 |
c:3:4096 |
빈 공간, 블록 3 · 페이지 내 오프셋 4096 |
w:블록:lp:xmin |
w:0:21:2884 |
WAL, 원래 위치 (0,21) · 만든 트랜잭션 2884 |
- exact: 다음 버전의 위치와 xmin 이 정확히 맞는 연결
- (추정): 빈 공간 행이라 위치(lp)를 모르지만 후보가 하나뿐이라 이은 연결. 후보가 여럿이면 잇지 않는다.
모든 시각은 UTC 입니다. 트랜잭션 시각은 WAL 의 커밋/롤백 기록에서 오므로, WAL 이 남아 있는 범위의 트랜잭션에만 붙습니다. "시각 변환"이 바꿔 보여 주는 epoch 정수 컬럼은 표시만 바꾸며 원래 값은 마우스를 올리면 보입니다.
엔진(pgviewer/engine/postgres)은 웹 없이 단독으로 import 할 수 있고 표준 라이브러리만 씁니다.
from pgviewer.engine.postgres import open_cluster
c = open_cluster(r"C:\cases\case01\postgresql.zip") # 폴더도 된다
print(c.info()["control"]["state_name"]) # 'shut down'
for d in c.databases(): # pg_database
print(d["oid"], d["name"], d["has_dir"])
db = c.database(16390) # 데이터베이스 하나
events = db.relation_by_name("public", "events")
cols = [a.name for a in events.attrs]
for row in db.rows(events.oid): # 모든 출처 · 모든 상태
rec = dict(zip(cols, row["values"]))
print(row["rid"], row["source"], row["state"], row["xmin_time"], rec["title"])
if row.get("chain"): # {id, pos, len, prev, next, link}
print(" 버전", row["chain"]["pos"], "/", row["chain"]["len"])
db.rows(events.oid, {"heap"}) # 현재 파일의 행만
db.row_detail(events.oid, "h:0:22") # 헤더 · 컬럼 위치 · 헥스 · 커밋 시각 · 버전 목록
db.index_traces(events.oid) # 인덱스 흔적
db.wal_events(events.oid) # 이 테이블의 WAL 기록
c.wal_summary() # WAL 범위 · 레코드 수 · CRC 오류
c.close()row 의 주요 필드:
| 필드 | 뜻 |
|---|---|
rid · source · state · tid |
행 ID · 출처 · 상태 · (블록, lp) |
xmin · xmax · xmin_status · xmax_status |
트랜잭션 번호와 커밋 상태 |
xmin_time · xmax_time |
커밋 시각 (WAL, UTC ISO 문자열) |
values · errors |
컬럼 값 목록(attrs 순서) · 컬럼별 해석 경고 |
file · offset · length |
세그먼트 파일 · 파일 내 오프셋 · 튜플 길이 (현재 파일 · 빈 공간) |
chain |
수정 이력 정보 |
wal |
WAL 출처 정보 {first_lsn, lsn, sources, fate, synthetic_header, reconstructed} |
carve |
빈 공간 정보 {page_offset, confidence, note} |
라이브러리는 Python 값을 그대로 돌려줍니다(큰 정수도 int). 웹 API 는 JavaScript 정밀도 때문에 2^53 이상 정수와 NaN 을 문자열로 보냅니다.
프로젝트 폴더에서 실행합니다.
py -X utf8 -m unittest discover -s tests -t . # 단위 테스트 + 샘플 검증 (35개)
py -X utf8 tools/validate.py [입력] # 모든 DB · 모든 테이블 디코딩 전수 점검
py -X utf8 tools/inspect_rel.py appdb public.events 10 # 테이블 행을 터미널에 출력
py -X utf8 tools/smoke.py [입력] # 빠른 동작 확인서버 없이 정답과 비교할 수 없어서, 서로 독립적인 출처가 일치하는지로 검증합니다. PG 17 샘플 데이터로 확인한 것:
| 검증 | 결과 |
|---|---|
| pg_control CRC-32C | 일치 |
| 카탈로그 자기 검증 (pg_attribute 22개 컬럼 정렬) | 일치 |
모든 테이블 relnatts = 컬럼 수 |
일치 |
| 디코딩 (5개 DB, 12만여 행, 복구 행 포함) | 오류 0, TOAST 소실 0 |
| WAL 레코드 CRC-32C (64,910개) | 오류 0 |
| WAL 최대 xid + 1 = pg_control nextXid | 일치 |
| 인덱스 흔적의 키 = 같은 위치에서 WAL 로 복구한 행의 키 | 50건 이상 일치 |
수정 이력의 모든 연결이 xmax = 다음 xmin 규칙 만족 |
일치 |
구조 해석의 근거(오프셋·상수·PostgreSQL 소스 위치)는 각 엔진 모듈의 주석에 [검증] / [문서] / [추정] 으로 구분해 적어 두었습니다.
- WAL 은 PostgreSQL 17 · 18 형식만 해석합니다. 그 외 메이저의 WAL 은 읽지 않고 클러스터 정보 탭에 이유를 표시합니다.
- 압축 방식: pglz · lz4 지원, zstd 로 압축된 전체 페이지 이미지는 미지원.
- 2단계 커밋(PREPARE/COMMIT PREPARED)의 시각은 붙이지 않습니다.
- MultiXact 상태는 해석하지 않습니다 (해당 행은
unknown). - 빈 공간 복구는 일부만 남은(잘린) 튜플 조각은 찾지 않습니다.
- 다른 테이블스페이스(
pg_tblspc/)는 폴더 입력에서 링크가 살아 있을 때만 읽습니다. - 성능: 테이블과 WAL 을 메모리에 올려 처리합니다. 수 GB 급 테이블은 느리거나 메모리가 많이 들 수 있습니다.
- ID 링크는 단일 컬럼 기본키 값(8자 이상 문자열)의 일치로 잇습니다. 우연히 같은 값이면 무관한 행이 이어질 수 있습니다.
- 판정은 근거와 함께 읽으세요. 상세 패널의 출처 · 신뢰도 · 판정 메모 · 합성 헤더 표시를 보고서에 함께 남기는 것을 권장합니다.
| 증상 | 확인할 것 |
|---|---|
| "PGDATA 를 찾지 못했습니다" | 입력 안에 PG_VERSION 과 global/pg_control 이 같이 있는지. zip 안에 PGDATA 가 여러 개면 하나만 담아서 다시 |
| "네트워크(UNC) 경로는 열지 않습니다" | 로컬 디스크로 복사한 뒤 열기 |
| "로컬 전용 도구입니다 (Host 불일치)" | 주소를 127.0.0.1 또는 localhost 로 열기 |
| 처음 테이블을 열 때 느림 | 처음 한 번 WAL 전체를 읽는다 (샘플 32MB 기준 약 2초). 이후는 캐시 |
| 빨간 점선 칸 "TOAST 청크가 남아 있지 않음" | 긴 값의 조각이 파일과 WAL 모두에서 정리됨 — 복구 불가 |
| WAL 탭에 "읽지 못했습니다" | 클러스터 정보 탭의 WAL 카드에서 이유 확인 (버전, 파일 없음 등) |
한글이 \xNN 으로 보임 |
DB 인코딩이 지원 목록(UTF8 · EUC_KR · EUC_JP · EUC_CN · LATIN1) 밖이거나 손상된 바이트 |
| 서버 종료 | 실행한 터미널에서 Ctrl+C |
PostgreSQL Viewer/
├── README.md · LICENSE
├── requirements.txt # 웹 계층 의존성 (엔진은 표준 라이브러리만)
├── pgviewer/
│ ├── source.py # 입력: 폴더 / zip (읽기 전용)
│ ├── engine/postgres/ # 저장소 엔진
│ │ ├── control.py # global/pg_control
│ │ ├── relmap.py # pg_filenode.map
│ │ ├── clog.py # pg_xact (커밋 상태)
│ │ ├── heap.py # 페이지 · 튜플 헤더 · 상태 판정
│ │ ├── varlena.py # varlena · TOAST · pglz · lz4
│ │ ├── types.py # 타입 디코더
│ │ ├── wal.py # WAL 레코드 · 커밋 시각 · WAL 튜플
│ │ ├── carve.py # 빈 공간 카빙
│ │ ├── btree.py # B-tree 리프 (인덱스 흔적)
│ │ ├── recovery.py # 행 조립 · 수정 이력 · 인덱스 흔적 · 키 링크
│ │ ├── crc32c.py
│ │ └── cluster.py # 카탈로그 부트스트랩 · 공개 API
│ └── api/main.py # FastAPI (엔진 호출만)
├── web/index.html # 화면 (단일 HTML, 모든 증거 값은 textContent 로만 표시)
├── tests/test_sample.py # 단위 테스트 + 샘플 불변식
├── tools/ # validate · inspect_rel · smoke
└── work/ # 업로드 사본 (git 제외)
- 입력 — 폴더 또는 zip 에서 PGDATA 를 찾고 SHA-256 을 기록한다.
- 컨트롤 파일 —
global/pg_control에서 버전 · 블록 크기 · 상태 · 다음 XID 를 읽고 CRC 로 확인한다. - 카탈로그 부트스트랩 —
pg_attribute앞 5개 컬럼만 고정 정의로 읽고,pg_attribute가 자기 자신을 설명하는 행에서 나머지 구조를 알아낸 뒤 정렬 값을 자체 검증한다. 이후 모든 테이블은 카탈로그에 적힌 정의로 읽는다. - 힙 페이지 — 8KB 페이지 → 라인 포인터 → 튜플 헤더(xmin/xmax/infomask) → 컬럼 분해 → 타입 변환.
- 상태 판정 — 힌트 비트 ·
pg_xact· WAL 커밋 기록으로 live/deleted/updated/aborted 를 정한다. - 복구 — WAL(페이지 주소 순, CRC 검증) · 빈 공간 카빙 · 인덱스 흔적을 더하고, 같은 튜플은 하나로 합친다.
- 관계 — 수정 이력 체인, 기본키 링크, 커밋 시각을 붙인다.
MIT License. 자유롭게 사용·수정·재배포할 수 있으며, 저작권 표시와 라이선스 전문을 함께 유지하면 됩니다. 무보증(as-is)으로 제공됩니다.