# pg_duckdb 1.0: PostgreSQL 안에 DuckDB 컬럼 엔진을 내장해 분석 쿼리를 2–7배 빠르게 만드는 방법
요약
PostgreSQL은 OLTP(트랜잭션 처리)에 강하지만, 수백만 행에 걸친 집계·조인·분석 쿼리에서는 컬럼형 스토리지 엔진에 비해 성능 격차가 크다. 이를 해결하기 위해 MotherDuck과 DuckDB 팀이 공동 개발한 pg_duckdb 익스텐션은 PostgreSQL 프로세스 안에 DuckDB 인스턴스를 직접 내장해, 분석 쿼리를 PostgreSQL SQL 인터페이스 그대로 실행하되 DuckDB의 벡터화 실행 엔진을 활용한다. 2026년 7월 정식 출시된 v1.0.0은 병렬 테이블 스캔, 신규 타입 지원, 커뮤니티 익스텐션, MotherDuck 서버리스 오프로드를 포함하며 TPC-H 벤치마크 기준 2–7배 쿼리 속도 향상을 보고했다.
문제: PostgreSQL의 분석 쿼리 병목
PostgreSQL의 실행 엔진은 행 단위(row-at-a-time) Volcano 모델로 설계되어 있다. OLTP 워크로드에서는 레이턴시와 동시성이 핵심이라 이 설계가 적합하지만, OLAP 쿼리에서는 세 가지 구조적 병목이 드러난다.
1. 행 지향 스토리지 I/O 비효율
SELECT SUM(price * quantity) FROM orders WHERE year = 2025;이 쿼리는 price와 quantity 두 컬럼만 읽으면 되지만, PostgreSQL은 전체 행(row)을 메모리에 올린다. 컬럼이 많을수록, 행이 많을수록 불필요한 I/O가 증가한다.
2. 벡터화 실행 부재
행 단위 처리는 CPU의 SIMD(Single Instruction, Multiple Data) 명령어를 활용하기 어렵다. 컬럼형 엔진은 같은 타입의 값을 배열로 처리하기 때문에 자연스럽게 SIMD 최적화가 가능하다.
3. 병렬 스캔 한계
PostgreSQL의 병렬 쿼리는 플래너 추정에 의존하며, 대형 분석 쿼리에서 예측 오차로 인해 충분한 병렬도를 내지 못하는 경우가 많다.
pg_duckdb의 접근 방식
pg_duckdb는 별도 프로세스나 사이드카 없이 PostgreSQL 프로세스 내부에 DuckDB를 라이브러리로 로드한다. SQL 계층은 PostgreSQL이 담당하므로 기존 연결, 인증, 권한, 트랜잭션 의미론을 그대로 유지하면서, 실행 계획의 물리 실행 단계만 DuckDB 엔진으로 위임한다.
아키텍처 흐름:
클라이언트 SQL
│
PostgreSQL 파서·플래너
│
pg_duckdb 훅 (실행 위임 여부 판단)
├─ DuckDB 실행 경로 (분석 쿼리)
│ └─ 벡터화 실행 → PostgreSQL 결과 반환
└─ PostgreSQL 기본 실행 경로 (DML, 트랜잭션)활성화는 세션 수준 GUC 하나면 충분하다.
SET duckdb.execution = true;이 설정이 켜진 세션에서 SELECT, COPY, CREATE TABLE AS SELECT 등 읽기 중심 쿼리가 DuckDB로 라우팅된다. INSERT, UPDATE, DELETE와 DDL은 여전히 PostgreSQL이 처리한다.
1.0의 핵심 변경 사항
병렬 테이블 스캔
기존 버전에서는 PostgreSQL 힙(heap) 테이블을 DuckDB에서 스캔할 때 단일 스레드로만 읽었다. 1.0부터 DuckDB의 병렬 스캔 기능이 PostgreSQL 테이블에도 적용되어, duckdb.max_threads 설정에 따라 복수의 DuckDB 스레드가 동시에 힙 블록을 읽는다.
SET duckdb.max_threads = 8;
SET duckdb.execution = true;
SELECT region, SUM(revenue)
FROM sales
GROUP BY region
ORDER BY SUM(revenue) DESC;신규 타입 지원
1.0은 DuckDB 특화 타입과 PostgreSQL 타입 간 매핑을 크게 확장했다.
| 추가된 타입 | 설명 |
|---|---|
DOMAIN | 기반 타입 위에 제약을 얹은 사용자 정의 타입 |
VARINT | 가변 정밀도 정수 |
TIME, TIMETZ | 시간대 포함/미포함 시간 타입 |
BIT, VARBIT | 비트 문자열 |
UNION | 여러 타입 중 하나를 가질 수 있는 태그드 유니온 |
MAP | 키-값 매핑 |
STRUCT | 이름 붙인 필드 집합 (중첩 레코드) |
STRUCT와 MAP은 JSON 형태로 저장된 반정형 데이터를 SQL로 집계할 때 특히 유용하다.
커뮤니티 익스텐션 연동
DuckDB 생태계에는 httpfs(S3/HTTP 직접 읽기), spatial(지리 데이터), iceberg(Apache Iceberg 테이블) 등 다양한 커뮤니티 익스텐션이 있다. pg_duckdb 1.0은 이를 PostgreSQL 세션에서 직접 로드할 수 있도록 허용한다.
-- S3의 Parquet 파일을 PostgreSQL 쿼리로 직접 읽기
SET duckdb.execution = true;
SELECT year, SUM(amount)
FROM read_parquet('s3://my-bucket/events/*.parquet')
WHERE event_type = 'purchase'
GROUP BY year;COPY 자동 감지
COPY 명령이 .parquet, .json, .ndjson 파일 확장자를 자동으로 감지하고, Azure Blob Storage 및 HTTP URL을 소스로 지원한다.
COPY orders FROM 's3://data-lake/orders/2025/*.parquet';
COPY products FROM 'https://cdn.example.com/catalog.json';아키텍처 다이어그램
기존 접근 방식과 비교
| 방식 | 장점 | 단점 |
|---|---|---|
| PostgreSQL 단독 | 완전한 ACID, 운영 단순 | OLAP 쿼리 느림 |
| Citus 분산 PostgreSQL | 수평 확장 | 운영 복잡도 높음, 단일 노드 OLAP은 여전히 행 엔진 |
| FDW(Foreign Data Wrapper)로 DuckDB | 기존 방식 | 별도 프로세스, 네트워크 직렬화 오버헤드 |
| TimescaleDB | 시계열에 특화 | 범용 OLAP은 제한적 |
| pg_duckdb 1.0 | 인프라 추가 없이 OLAP 성능, 동일 연결·권한 체계 | UPDATE/DELETE 경로에서는 이점 없음, PostgreSQL 힙 스캔이 병목이 될 수 있음 |
성능 결과
공식 벤치마크(TPC-H Scale Factor 10, PostgreSQL 16, 32-core 서버 기준):
| 쿼리 유형 | PostgreSQL | pg_duckdb 1.0 | 배속 |
|---|---|---|---|
| TPC-H Q1 (집계 스캔) | 18.4 s | 2.6 s | 7.1× |
| TPC-H Q6 (필터 집계) | 12.1 s | 3.1 s | 3.9× |
| TPC-H Q18 (다중 조인) | 45.2 s | 18.7 s | 2.4× |
| TPC-H Q22 (서브쿼리) | 31.8 s | 14.4 s | 2.2× |
단순 집계·스캔일수록 향상 폭이 크고, 복잡한 조인이 섞일수록 PostgreSQL 힙 읽기 비중이 높아져 상대적으로 개선 폭이 줄어든다.
MotherDuck 서버리스 오프로드
pg_duckdb는 MotherDuck 클라우드 서비스와 통합되어 스파이크성 분석 부하를 서버리스 DuckDB 클러스터로 오프로드할 수 있다.
-- MotherDuck 연결 설정
ALTER SYSTEM SET duckdb.motherduck_token = 'md_token_xxxx';
SELECT pg_reload_conf();
-- 로컬 PostgreSQL 테이블을 MotherDuck으로 동기화
CREATE TABLE motherduck.main.orders_summary AS
SELECT date_trunc('month', order_date) AS month,
SUM(total) AS revenue
FROM orders
GROUP BY 1;이 패턴은 PostgreSQL이 OLTP 기록 시스템으로 남고, 월별 보고서·BI 쿼리처럼 비용이 큰 분석은 MotherDuck이 담당하는 "하이브리드 레이크하우스" 구조를 최소 설정으로 구현한다.
운영 고려사항
1. 세션별 활성화 vs. 전역 활성화
SET duckdb.execution = true는 세션 수준이라 특정 세션만 DuckDB 경로를 탄다. 전역으로 켜려면 postgresql.conf에 duckdb.execution = true를 추가하면 되지만, OLTP 쿼리가 많은 환경에서는 불필요한 라우팅 시도가 생길 수 있으므로 애플리케이션 수준에서 세션 설정을 제어하는 방식을 권장한다.
2. 메모리 경쟁
DuckDB는 분석 쿼리에서 duckdb.memory_limit 설정까지 메모리를 적극 사용한다. PostgreSQL의 shared_buffers, work_mem과 별도로 동작하므로, 두 엔진 합산 메모리가 시스템 RAM을 초과하지 않도록 주의해야 한다.
권장: duckdb.memory_limit ≤ (총 RAM − shared_buffers) × 0.53. MVCC 일관성 경계
DuckDB가 PostgreSQL 힙을 읽을 때 PostgreSQL의 MVCC 스냅샷을 존중하지만, DuckDB 내부 임시 파일이나 MotherDuck에 복제된 데이터는 PostgreSQL 트랜잭션 경계 밖에 있다. CREATE TABLE AS로 MotherDuck에 쓴 데이터는 PostgreSQL ROLLBACK의 영향을 받지 않는다.
4. 인덱스 활용 없음
DuckDB는 PostgreSQL 인덱스를 인식하지 못한다. 포인트 조회나 좁은 범위 스캔은 PostgreSQL 기본 경로가 더 빠를 수 있으니, EXPLAIN으로 어느 경로가 선택되는지 확인하고 필요시 SET duckdb.execution = false로 전환한다.
5. PostgreSQL 버전 요구
pg_duckdb 1.0은 PostgreSQL 14 이상을 요구하며, PostgreSQL 16에서 최적화 테스트를 주로 수행했다.
설치 및 시작 방법
# Ubuntu 22.04 / PostgreSQL 16 기준
sudo apt-get install postgresql-16-pg-duckdb
# 또는 소스 빌드
git clone https://github.com/duckdb/pg_duckdb
cd pg_duckdb && make && sudo make install-- postgresql.conf 또는 세션에서
CREATE EXTENSION pg_duckdb;
SET duckdb.execution = true;
-- 설치 확인
SELECT duckdb.version();
-- → v1.0.0References
- MotherDuck Blog, "pg_duckdb 1.0: Bringing Analytical Speed to PostgreSQL" (2026-07)
- GitHub:
duckdb/pg_duckdb— v1.0.0 Release Notes - DuckDB Documentation: pg_duckdb Extension
- TPC-H Benchmark Specification: TPC-H Rev. 3.0.1