PostgreSQL로 분석 쿼리를 돌려 본 사람은 다들 한 번쯤 궁금해했을 거예요. 같은 데이터를 두고 왜 컬럼 스토어 엔진들과 이렇게까지 차이가 날까요. 인덱스나 통계의 문제가 아니라 더 아래층의 문제라는 감은 오는데, 그 아래층이 정확히 뭘까요.
7월 초 regression test 전체 통과 소식으로 화제가 됐던 Rust 재작성 프로젝트 pgrust가 7월 30일 v0.2를 릴리스하면서 성능 주장을 들고 나왔고, 며칠 뒤 저자가 그 성능이 어디서 나왔는지 해설한 글을 올렸습니다. 해설 글이 Hacker News 339점을 받으며 이 기간 PostgreSQL 관련 글 중 가장 크게 회자됐는데, pgrust 홍보를 걷어내고 봐도 PostgreSQL executor가 어디서 시간을 쓰는지 보여주는 교재로 훌륭합니다.
Volcano 모델: 우아함의 청구서
PostgreSQL의 executor는 Volcano 모델(iterator 모델)입니다. 실행 계획의 각 노드가 next()를 구현하고, 부모 노드가 자식에게 next()를 호출하면 행이 한 번에 하나씩 위로 올라옵니다.
이 모델의 장점은 우아함입니다. 어떤 연산자든 같은 인터페이스로 조립되고, 중간 결과를 통째로 메모리에 들 필요가 없습니다. OLTP처럼 행 몇 개를 만지는 쿼리에서는 완벽합니다.
문제는 5억 행을 집계할 때입니다. 행 하나마다 함수 호출 체인이 반복되니 호출 오버헤드가 5억 번 쌓이고, CPU 입장에서는 분기가 많고 예측이 어려운 코드가 됩니다. 파이프라이닝과 캐시가 일을 못 합니다. 저자의 측정에서 5억 행 합계 쿼리가 PostgreSQL에서 약 20초 걸렸는데, 이 시간 대부분이 데이터가 아니라 행을 나르는 절차에 쓰인 셈입니다.
세 단계 처방
해설 글은 같은 쿼리를 세 단계로 빠르게 만듭니다. 측정 환경은 AWS c8g.4xlarge(Graviton4, 16 vCPU), PostgreSQL 18.4 대비, 병렬 실행은 꺼서 엔진 구조 차이만 비교했습니다.
| 단계 | 실행 시간 | 직전 대비 |
|---|---|---|
| Volcano 방식 재구현 | 1.3초 | 기준 |
| + batching (1024행) | 480ms | 2.7배 |
| + operator fusion | 358ms | 1.3배 |
| + SIMD | 135ms | 2.7배 |
Batching은 next()를 next_batch()로 바꿔 1024행씩 나르는 것입니다. 함수 호출 오버헤드가 1/1024로 줄고, 루프가 단순해져 CPU가 파이프라이닝할 수 있는 코드가 됩니다. 이 한 가지만으로 2.7배입니다.
Operator fusion은 인접한 노드를 하나로 합치는 것입니다. sequential scan과 aggregation이 별도 노드면 batch를 만들어 넘기고 다시 읽는 복사가 생기는데, 합치면 스캔하면서 바로 집계합니다. 중간 버퍼가 사라집니다.
SIMD는 마지막 단계입니다. batch로 정렬된 데이터는 벡터 연산에 이상적인 모양이라, ARM64 벡터 명령으로 덧셈을 처리하면 일반 루프보다 3배 빨라집니다. batching이 먼저 있었기에 가능한 최적화입니다. 행 단위로 흩어진 데이터에는 SIMD를 적용할 자리가 없습니다.
결과적으로 20초가 135ms로, 약 150배입니다. 릴리스 노트는 여기에 columnar 저장 포맷까지 얹어 ClickBench 종합에서 ClickHouse를 앞선다고 주장합니다. 이 주장에는 유보를 달아 두는 게 좋겠습니다. 자체 벤치마크이고, 독립 검증이 쌓이려면 시간이 필요합니다. OLTP 쪽 30% 향상 주장도 마찬가지입니다.
PostgreSQL은 이걸 몰랐을까
당연히 알고 있습니다. 이 글이 보여준 처방은 학계와 업계에서 오래된 레시피입니다. MonetDB/X100 계열 연구가 20년 전에 벡터화 실행을 정립했고, DuckDB와 ClickHouse가 그 위에 서 있습니다.
PostgreSQL 본가에도 부분 부분 들어와 있습니다. JIT 컴파일이 expression 평가를 통째로 컴파일해 호출 오버헤드를 줄이고, executor 내부에 부분적인 batch 처리가 도입되는 흐름도 있습니다. 다만 35년 된 C 코드베이스에서 executor의 기본 단위를 행에서 batch로 바꾸는 것은, 백지에서 Rust로 다시 짜는 것과는 전혀 다른 난이도입니다. pgrust가 유리한 것은 Rust여서라기보다 백지여서입니다.
그래서 이 프로젝트를 보는 제 관점은 “PostgreSQL의 대체재가 나타났다"가 아닙니다. 재미있는 것은 두 가지입니다. 하나, PostgreSQL 호환(문법, 프로토콜, regression test)을 유지하면서 executor만 현대식으로 갈아 끼우면 어디까지 가는지 보여주는 실험이라는 점. 둘, vacuum이 미운 날에서 다뤘던 것과 같은 질문, 즉 PostgreSQL의 설계 결정들이 어떤 트레이드오프였는지를 실물로 보여준다는 점입니다. Volcano 모델은 게으름이 아니라 1980년대의 합리적 선택이었고, 그 청구서가 분석 워크로드에서 날아오고 있을 뿐입니다.
실무 관점의 결론은 소박하게 남겨 둘게요. 오늘 PostgreSQL에서 느린 집계를 빠르게 하는 검증된 길은 여전히 병렬 쿼리, JIT, 사전 집계, 그리고 필요하면 분석 전용 엔진의 병행입니다. pgrust는 북마크해 두고, regression test 통과라는 출발점이 성능 주장 이후에도 유지되는지 지켜보겠습니다.
참고 자료
- Making Postgres 300x faster for analytics: batching, operator fusion, and SIMD - malisper.me, 2026-08-03
- pgrust v0.2 릴리스 노트 - GitHub, 2026-07-30
- PostgreSQL 문서: JIT compilation