이 저장소가 대체 무엇인가.
pgrust는 세계에서 가장 널리 쓰이는 오픈소스 데이터베이스인 PostgreSQL의 서버 부분(백엔드)을 통째로 Rust로 다시 작성한 프로젝트다. "SQL을 흉내 내는 미니 DB"가 아니라, 실제 Postgres 18.3과 바이트 단위로 똑같이 동작하는 것을 목표로 한다. 얼마나 똑같은가? Postgres가 자기 자신을 검증할 때 쓰는 공식 회귀 테스트(46,000+ 쿼리)의 출력이 한 글자도 안 틀리게 일치하고, 기존 Postgres로 만들어 둔 데이터 폴더를 그대로 열어 부팅한다.
PostgreSQL은 30년 묵은 거대한 C 엔진이다 — 잘 돌지만, 내부를 뜯어고치기가 무섭다(수동 메모리 관리, 다중 프로세스). pgrust는 이 엔진을 부품 하나(C 소스 파일 하나)를 Rust 부품으로 교체할 때마다, 원래 자동차의 정비 매뉴얼(회귀 테스트)로 "여전히 똑같이 달리는가"를 즉시 확인하는 방식으로 옮긴다.
그래서 pgrust의 파일 구조는 Postgres의 C 소스 트리와 거의 1:1로 대응한다. src/backend/parser/analyze.c는 crates/backend/parser/.../parser_analyze 크레이트가 되는 식이다. "새 DB를 발명"한 게 아니라 "같은 DB를 다른 언어로 옮긴" 것이다.
라이선스는 AGPL-3.0(카피레프트·네트워크 사용 조항 포함)로, Postgres 자체의 관대한 라이선스보다 훨씬 엄격하다는 점은 짚고 가야 한다. 코드 규모는 순수 Rust 약 77만 줄, 파일 3,525개, 크레이트(작은 패키지) 1,464개에 달한다. 최신 커밋은 2026-07-10, 웹에서 바로 써 보는 WASM 데모(pgrust.com)와 Discord도 있다.
"Postgres를 Rust로 다시 썼다"는 건 겉모습이다. 이 레포의 진짜 가치는 진짜 프로덕션 DB의 파서→플래너→실행기→저장소→WAL→MVCC 전체 파이프라인을 읽을 수 있는 Rust로 볼 수 있다는 점, 그리고 "1,464개 크레이트 사이의 순환 의존을 끊는 seam 패턴", "C 메모리 컨텍스트를 Rust 수명(lifetime)으로 바꾸기", "bison 문법을 c2rust로 자동 변환", "진짜 회귀 테스트를 정답지로 쓰는 이식 규율" 같은 대규모 코드 이식의 실전 방법론을 통째로 담고 있다는 점이다.
이 공개 저장소는 호환성 중심이며, README도 "아직 성능 최적화 안 됨"이라고 명시한다. 화제가 된 "트랜잭션 50% 빠름·분석 쿼리 300배·스레드당 커넥션" 같은 수치는 이 코드에 들어있지 않은 미공개 WIP 버전에 대한 주장이며, "회귀 스위트 100% 통과"라는 헤드라인도 그 WIP 버전 이야기다(이 트리에서 검증되는 수치는 "46,000+ 쿼리 일치"). 또 PL/Python·Perl·Tcl 미이식, 일부 contrib만 이식됨, 확장(extension) 일반 호환 안 됨. "진지한 연구 프로젝트"이지 "지금 프로덕션에 올릴 DB"는 아니다.
트렌딩 이유와, 여느 "Rust로 만든 DB"들과의 결정적 차이.
Rust로 DB를 만든 프로젝트는 많지만 대개 "SQL 일부를 새로 설계한 장난감"이다. pgrust는 정반대다. Postgres 자신의 회귀 테스트를 그대로 정답지로 고정하고, 옮긴 코드가 그 46,000개 쿼리의 출력을 한 글자도 안 틀리게 재현할 때만 통과로 친다. 덕분에 대규모 AI 보조 이식이라는 위험한 작업을 하면서도 "동작이 원본과 같은가"를 사람 눈이 아니라 기계가 보증한다. 이게 이 프로젝트를 여느 "Rust DB"와 구분 짓는 핵심이다.
pgrust는 실제 postgres.bki와 시스템 SQL을 읽어 기존 Postgres 18.3 데이터 디렉토리로 부팅하고, v3 와이어 프로토콜(SCRAM·MD5·LDAP·PAM·TLS 인증 포함)을 말한다. 즉 평범한 psql·libpq 클라이언트가 코드 수정 없이 접속된다. 심지어 공식 postgres 도커 이미지의 드롭인 대체재로 설계되어(같은 환경변수·/docker-entrypoint-initdb.d·uid 999·5432 포트), docker run malisper/pgrust로 바로 띄울 수 있다.
Postgres의 C 코드는 잘 돌지만 30년치 복잡성이 쌓여 안전하게 바꾸기 어렵다. pgrust의 진짜 목적은 "Postgres를 안쪽에서 바꾸기 쉽게 만드는 것"이다 — 멀티스레드 내부, no-vacuum 저장소, AI가 만든 나쁜 SQL에 대한 가드레일 같은 더 깊은 서버 개조를 실험하는 발판으로. Rust의 borrow checker가 "이 메모리는 이 시점 뒤엔 못 쓴다"를 컴파일 타임에 강제하니, C에서는 무서웠던 개조를 시도해 볼 수 있다.
파서·플래너(1.2만 줄짜리 planner)·실행기(모든 nodeXxx + 병렬 쿼리)·인덱스 AM 전종(nbtree/hash/gin/gist/spgist/brin)·MVCC·WAL·복제·PL/pgSQL·13개 contrib 모듈까지 옮겨져 있다. "일부만 흉내"가 아니라 뜨거운 경로(hot path)의 대부분이 진짜로 구현되어 있고, 미구현은 조용히 넘어가지 않고 seam이 전체 경로를 찍으며 요란하게 panic한다. 그 완결성이 46,000+ 테스트 통과로 증명된다.
무엇으로 만들어졌나 — 언어·크레이트·의존성·빌드.
| 항목 | 내용 |
|---|---|
| 주 언어 | Rust (edition 2021, resolver v2) — 약 77만 줄, .rs 파일 3,525개 |
| 워크스페이스 | Cargo 워크스페이스, 멤버 크레이트 1,464개 (대략 C 소스 파일 1개 ≈ 크레이트 1개) |
| SQL / C 헤더 | SQL 34개(시스템/테스트), 벤더 C 헤더 4개(nodetags.h 등) |
| 라이선스 | AGPL-3.0 (Postgres 자체보다 엄격) |
| 최신 커밋 | 2026-07-10 (c9aecbd0) — WIP 신버전·성능 관련 |
| 데모 | pgrust.com (WASM 브라우저 빌드), Discord 운영 |
거의 전부 암호/인증 지원용이다: aes 0.8.4·blowfish·des·cast5(pgcrypto), md-5 0.9.1·sha-1/sha2 0.9.9·hmac 0.10.1·pwhash(SCRAM/MD5 인증), num-bigint·rand 0.8.6·getrandom, 그리고 hashbrown·ahash·libc 0.2.186·libm·zerocopy 0.8.52. tokio도, async 런타임도, rayon도 없다 — 진짜 Postgres처럼 커넥션당 프로세스 모델을 따른다(스레드당 커넥션은 미공개 WIP 이야기).
Cargo 커스텀 프로파일이 여럿 있다: dev(빠른 링크), fast-check, release(codegen-units=1, 심볼 스트립), profiling, 그리고 브라우저 데모용 wasm64 크기 최적화 프로파일(opt-level="z"). C 파일 하나를 크레이트 하나로 쪼갠 덕에 바뀐 파일만 다시 컴파일되어 거대 코드베이스에서도 증분 빌드가 견딜 만해진다.
쿼리 하나가 어떻게 흐르고, 어떤 설계가 이 거대한 이식을 지탱하는가.
트래픽 코프(traffic cop) 루프는 crates/backend/tcop/postgres/src/lib.rs의 PostgresMain에 있고, 흐름은 원본 Postgres와 동일하다:
여기가 pgrust 전체를 지탱하는 가장 영리한 장치다. C는 하나의 링크 단위라 어떤 함수든 서로 자유롭게 호출한다. 그런데 Rust 크레이트는 의존 그래프에 순환(cycle)이 있으면 안 된다. 서로 호출하는 두 C 파일을 각각 크레이트로 쪼개면 곧바로 순환이 생긴다.
pgrust는 이걸 603개의 *_seams 크레이트와 seam! 매크로로 푼다. 함수의 "선언(빈 슬롯)"은 X_seams 크레이트에 두고, "진짜 구현"은 소유 크레이트가 프로세스 시작 시 init::init_all()로 슬롯에 꽂아 넣는다. 호출하는 쪽은 슬롯만 알면 되므로 순환이 사라진다.
아직 안 옮긴 함수의 seam을 호출하면 전체 경로를 찍으며 요란하게 panic한다(조용한 폴백 없음). 게다가 CI 게이트(crates/_support/todo_guard)가 모든 소스를 훑어 todo!()·unimplemented!()가 다시 들어오면 빌드를 깨뜨린다. "어디가 아직 빈칸인가"가 코드 구조 자체로 드러난다.
bison이 생성한 gram.c와 flex 스캐너는 사람이 손으로 옮기기엔 너무 기계적이다. 그래서 c2rust로 자동 변환해 crates/_support/pgrust/gram_c2rust_fgram/src/gram.rs에 넣었다 — 무려 31만 줄(LR 파싱 테이블 + 약 4,800개 액션 블록). 손으로 쓴 support 모듈이 palloc·리스트 생성자·ereport와, 테스트된 Rust 스캐너로 잇는 base_yylex 다리를 제공한다.
Postgres는 전역 CurrentMemoryContext + MemoryContextSwitchTo로 메모리를 관리한다(실수하기 쉽다). pgrust의 crates/_support/mcx는 전역 컨텍스트를 아예 없앴다. MemoryContext는 이름 붙은 할당 도메인이고, 컬렉션은 Mcx<'mcx> 핸들을 통해 할당한다. 그 결과 할당된 메모리는 자기 컨텍스트보다 오래 살 수 없다 — borrow checker가 컴파일 타임에 강제한다. C의 컨텍스트 트리 계단식 삭제가 Rust 소유권(ownership)으로 바뀐 것.
| 서브시스템 | 상태 |
|---|---|
| 파서/분석 | 완전 — 스캐너 + c2rust 문법 + 모든 분석 패스(CTE·집계·타입 강제) |
| 플래너/옵티마이저 | 완전 — planner 1.2만 줄, createplan 8.7천 줄, GEQO 포함 |
| 실행기 | 사실상 완전 — 모든 nodeXxx, 병렬 쿼리(Gather), Memoize, SPI |
| 인덱스 AM | heap + nbtree·hash·gin·gist·spgist·brin |
| 트랜잭션/MVCC | 스냅샷·리소스 오너·교착 감지 |
| WAL·복제 | xlog·rmgr, 논리 복제 워커 타입 |
| PL | PL/pgSQL만 (Python·Perl·Tcl 미이식) |
| 미구현 | seam이 panic + todo_guard가 빌드 차단 (조용한 스텁 없음) |
어디에 무엇이 있나 — Postgres C 트리를 그대로 반영한 지도.
핵심은 디렉토리 이름만 봐도 원본 Postgres의 어느 부분인지 안다는 것. 각 크레이트의 문서 주석은 원본 파일 경로를 인용한다(예: main_main → "Port of PostgreSQL's main.c").
이 레포에서 실제로 배울 수 있는 것.
교과서로만 배우던 쿼리 파이프라인(파싱→분석→재작성→계획→실행)을 읽을 수 있는 코드로 본다. 특히 옵티마이저(planner/createplan), 실행기의 다양한 조인 노드(NestLoop·HashJoin·MergeJoin), 인덱스 접근법(B-tree·GIN·GiST), MVCC 스냅샷, WAL이 한 코드베이스에 모여 있다. "진짜 프로덕션 DB가 이렇게 생겼구나"를 배우는 최고의 표본.
seam 패턴(순환 의존 끊기), c2rust 자동 변환, 메모리 컨텍스트→수명 매핑은 어떤 대형 레거시 코드든 안전한 언어로 옮길 때 재사용 가능한 전략이다. "테스트를 정답지로 고정하고 파일 단위로 옮긴다"는 규율 자체가 소프트웨어 마이그레이션의 정석.
#![no_std] + alloc만으로 도는 할당기, 수명 매개변수로 안전성을 강제하는 API 설계, 1,464개 크레이트 워크스페이스의 빌드 구성, WASM 타깃 크로스 컴파일 등 대규모 Rust 프로젝트 운영의 실물 사례.
회귀 테스트를 정답지로 쓰는 하네스(scripts/run-regression), 미구현을 빌드 실패로 만드는 todo_guard 게이트는 "AI가 대량으로 코드를 짜도 품질을 무너뜨리지 않게 하는" 실전 가드레일의 좋은 예다.
① 한 쿼리를 따라가 보기: SELECT 1+1;이 PostgresMain에서 파서→플래너→실행기로 흐르는 경로를 crates/backend/tcop/postgres/src/lib.rs부터 grep으로 추적. ② seam 하나 열어보기: 아무 *_seams 크레이트를 열어 "빈 슬롯 선언 + init에서 채우기" 구조를 확인. ③ 내가 좋아하는 C 파일의 Rust 짝 찾기: Postgres의 nodeHashjoin.c가 pgrust에서 어떻게 생겼는지 대조.
무엇이 있어야 돌아가나.
| 항목 | 요구 |
|---|---|
| 툴체인 | Rust + Cargo만 (Postgres C 소스 불필요 — nodetags.h 등은 벤더됨) |
| 시스템 라이브러리 | ICU, OpenSSL 3, LDAP, PAM (Debian: libicu-dev libssl-dev libldap2-dev libpam0g-dev + postgresql-client-18) |
| 스택 크기 | 깊은 재귀 플래너/파서 때문에 큰 스택 필요 — ulimit -s 65520, RUST_MIN_STACK=33554432, -c max_stack_depth=60000 |
| 유일한 C 도구 | 클라이언트 psql(Postgres 18) — 테스트·접속용 |
# 1) 빌드 (벤더 share 디렉토리를 가리킨 채) PGRUST_PGSHAREDIR="$PWD/vendor/postgres-18.3/share" \ cargo build --release --locked --bin postgres # 2) pgrust 자체 initdb 로 데이터 폴더 생성 target/release/postgres --initdb -D /tmp/pgrust-data \ -L vendor/postgres-18.3/share --no-locale --encoding UTF8 -U postgres # 3) 큰 스택으로 서버 실행 → 평범한 psql 로 접속 ulimit -s 65520 RUST_MIN_STACK=33554432 target/release/postgres -D /tmp/pgrust-data \ -c max_stack_depth=60000
가장 쉬운 길은 도커다: docker run malisper/pgrust:v0.1 — 공식 postgres 이미지의 드롭인 대체(같은 환경변수·/docker-entrypoint-initdb.d·uid 999·5432 포트). 또는 설치 없이 pgrust.com WASM 데모.
scripts/run-regression이 (1) pgrust 릴리스 바이너리 + 벤더된 PG 18.3 테스트 파일(sql 232개, expected/*.out 265개)만으로, (2) pgrust 고유 --initdb로 부팅해 regression DB를 만들고, (3) 공식 parallel_schedule 순서대로 각 *.sql을 psql로 흘려 (4) 출력을 기대 파일과 diff — 0줄 차이일 때만 PASS. 하나라도 PASS가 아니면 스크립트가 non-zero로 종료. regress.c의 C 테스트 헬퍼 ~60개도 test_regress 크레이트로 인프로세스 재구현되어 .so 없이 로드된다.
코드를 안 짜도 되는 것부터, 실제로 빌드·개조까지.
git clone --depth 1 https://github.com/malisper/pgrust 후 crates/backend/ 아래를 ls로 훑어 Postgres C 트리와의 1:1 대응을 눈으로 확인한다. 좋아하는 서브시스템(예: executor) 하나를 골라 크레이트 목록을 적어 본다. 빌드 불필요.
아무 *_seams 크레이트와 crates/_support/seam/seam_core를 열어 seam! 매크로, "빈 슬롯 선언 → init_seams()에서 채움 → 미설치 호출 시 panic" 구조를 읽는다. 순환 의존이 왜 문제이고 어떻게 풀렸는지 한 단락으로 정리.
§7대로 빌드·initdb·실행 후 평범한 psql로 접속해 SELECT version();, 테이블 생성·인덱스·조인 쿼리를 날려 본다. "이게 진짜 Rust로 짜인 Postgres구나"를 체감. 도커 경로가 가장 빠르다.
scripts/run-regression을 실행해 46,000+ 쿼리가 어떻게 expected/*.out과 diff 판정되는지 관찰한다. 일부러 소스 한 줄을 바꿔 테스트가 어떻게 FAIL로 잡아내는지 확인 — "테스트 오라클"의 위력을 직접 본다.
EXPLAIN 한 줄이 파서→플래너→실행기로 흐르는 경로를 추적한 뒤, 실행기 노드 하나(예: nodeSeqScan)에 로그를 심어 스캔이 몇 번 호출되는지 관찰한다. Rust이므로 borrow checker가 실수를 잡아 준다 — C 원본에선 겁났을 개조를 안전하게 시도.
이 레포를 제대로 씹으려면 무엇을 곁들여 공부할까 (주차별).
| 주차 | 주제 | 무엇을 |
|---|---|---|
| 1주차 | DBMS 기초 | 쿼리 처리 단계(파서→플래너→실행기), 관계 대수, 실행 계획 읽는 법. CMU 15-445 강의가 정석. |
| 2주차 | Postgres 내부 | "The Internals of PostgreSQL" 무료 책 — MVCC, 버퍼 관리, WAL, 인덱스 AM. pgrust의 크레이트 이름과 대조. |
| 3주차 | Rust 소유권·수명 | The Rust Book 4·10장, no_std+alloc. mcx가 왜 수명으로 안전을 강제하는지 이해할 밑바탕. |
| 4주차 | Cargo 워크스페이스 | 다중 크레이트 구조, 프로파일, 피처 플래그. 크레이트 순환이 왜 금지인지 실습으로 재현. |
| 5주차 | 파서/컴파일러 | bison/flex, LR 파싱, c2rust. 왜 문법만 자동 변환하고 스캐너는 손으로 옮겼는지. |
| 6주차 | 쿼리 최적화 | 비용 기반 옵티마이저, 조인 순서, GEQO(유전 알고리즘). planner 크레이트를 읽으며. |
| 7주차 | 와이어 프로토콜·인증 | Postgres v3 프로토콜, SCRAM-SHA-256. libpq 크레이트로 "psql이 어떻게 붙나". |
이 문서에 나온 용어 빠른 정리.
*_seams 크레이트에, 구현은 소유 크레이트가 시작 시 꽂아 넣는다. 미설치 호출 = panic → 미구현 추적기 역할도 겸함.gram.c)에만 적용해 31만 줄 Rust로 만들었다.더 파고들 곳.