본문으로 건너뛰기

"DuckDB" 태그로 연결된 2개 게시물개의 게시물이 있습니다.

모든 태그 보기

DuckDB MySQL 500GB 실험

· 약 4분

MySQL에서 분석 쿼리를 억지로 버텨 본 경험이 있다면, 이 숫자들이 남 일 같지 않을 거예요. InnoDB로 28시간 넘게 돌리고도 다 못 끝낸 TPC-H 22개 쿼리를, 같은 MySQL 프로세스 안의 DuckDB 엔진이 185.6초에 끝냈습니다.

Percona가 8월 7일 공개한 실험입니다. DuckDB를 MySQL의 스토리지 엔진으로 붙인 실험 프로젝트를 TPC-H 스케일팩터 500, 그러니까 원본 CSV 500GB(약 30억 행) 규모에서 검증했습니다. MySQL은 스토리지 엔진을 갈아 끼울 수 있는 구조라는 걸 다들 알지만, 그 자리에 컬럼 지향 분석 엔진을 꽂는 발상을 실제 수치로 검증한 것은 드문 일입니다.

숫자부터

테스트 환경은 80코어, 187.5GB RAM 서버 한 대입니다. 같은 데이터를 InnoDB, MySQL+DuckDB 엔진, 순수 DuckDB 세 가지로 적재하고 비교했습니다.

항목InnoDBMySQL+DuckDB 엔진순수 DuckDB
적재 시간15시간 21분36분 5초(동일 계열)
디스크 사용673.2 GB132.4 GB132.4 GB
TPC-H 22개 쿼리28시간+ (4개 미완주)185.6초152.7초

쿼리별로 보면 격차가 더 생생합니다. Q1은 InnoDB 11,864초가 11.1초로, Q6는 3,539초가 1.3초로 줄었습니다. InnoDB는 쿼리당 2시간 제한을 두었는데도 4개를 완주하지 못했습니다.

디스크 방향도 반대입니다. 원본 CSV 500GB가 InnoDB에서는 673GB로 늘어났고, DuckDB에서는 132GB로 압축됐습니다. 행 지향 저장과 인덱스 오버헤드 대 컬럼 저장과 압축의 차이가 그대로 드러납니다.

정확성 검증도 곁들여져 있습니다. 22개 쿼리 중 21개가 순수 DuckDB 결과와 소수점 4자리까지 일치했습니다.

어떻게 동작하나

핵심은 MySQL의 스토리지 엔진 인터페이스입니다. 애플리케이션은 여전히 MySQL 프로토콜로 접속해 SQL을 실행하고, 해당 테이블의 저장과 스캔을 DuckDB가 담당합니다. 적재가 25.5배 빨랐던 비결도 여기 있는데, 엔진이 LOAD DATA를 행 단위 삽입으로 처리하지 않고 DuckDB의 COPY로 직접 전달해 배치 적재로 바꿉니다.

시도해 보는 것도 쉽게 만들어 놨습니다. Docker 이미지 한 줄로 실행됩니다.

docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=secret \
perconalab/ducksdb-mysql-engine:latest

냉정하게 볼 부분

Percona 스스로 못박은 제약들이 있습니다.

첫째, 이것은 실험 소프트웨어입니다. 운영 투입 대상이 아니라고 글에서 반복해 강조합니다. 둘째, 분석 전용입니다. 포인트 조회 같은 OLTP 패턴은 여전히 행 경로가 낫고, 이 엔진의 목적이 아닙니다. 셋째, 메모리 관리가 아직 거칩니다. 메모리 제한을 넘는 쿼리는 디스크 스필 설정을 손봐야 합니다. 넷째, 서버 한 대의 특정 워크로드만 검증한 결과입니다.

그리고 벤치마크 자체의 성격도 감안해야 합니다. TPC-H는 컬럼 스토어에 유리한 순수 분석 워크로드입니다. InnoDB가 28시간 걸렸다는 것은 InnoDB가 나쁘다는 뜻이 아니라, 애초에 이 일을 시키면 안 되는 엔진에 이 일을 시켰다는 뜻에 가깝습니다. 문제는 현실의 많은 조직이 정확히 그렇게 쓰고 있다는 점이고, 이 실험의 가치는 그 간극을 숫자로 보여준 데 있습니다.

이어지는 실험: 복제로 OLTP와 분석 분리

Percona는 이 실험을 한 단계 더 밀고 나가는 중입니다. 8월 13일 후속 글 Replicating from InnoDB into a DuckDB storage engine에서는 primary의 InnoDB 테이블을 replica의 DuckDB 엔진 테이블로 복제하는 구성을 다뤘습니다. 쓰기는 InnoDB가 받고, 분석은 DuckDB replica가 받는 그림입니다.

이 방향이 흥미로운 이유는 기존 선택지와의 비교 때문입니다. MySQL의 분석 부하를 떼어내는 전통적인 답은 별도 웨어하우스(ClickHouse, BigQuery 등)로의 CDC 파이프라인인데, 그 순간 스키마 동기화, 지연, 운영 부담이 따라옵니다. replica 한 대의 스토리지 엔진만 바꿔서 같은 효과를 얻는다면, MySQL 프로토콜과 권한 체계 안에서 문제가 끝납니다. PostgreSQL 진영에서 pg_duckdb가 겨냥하는 자리와 정확히 같은 자리입니다.

아직 붙일 이름은 실험이지만, 방향은 뚜렷해 보여요. 분석 엔진을 밖에 두고 데이터를 나르는 대신, 익숙한 DB 안으로 분석 엔진을 들여오는 흐름입니다. Docker 이미지가 공개되어 있으니 가벼운 데이터로 직접 실행해 보고, 결과가 재미있으면 후속으로 다루겠습니다.

참고 자료

DuckDB v2.0 비동기 I/O

· 약 4분

작년에 PostgreSQL 18의 비동기 I/O를 다루면서, DB 엔진들이 하나둘 동기 I/O와 결별하는 중이라고 썼어요. 이번엔 DuckDB 차례입니다. 7월 31일 공식 블로그 글이 가을 출시 예정인 v2.0의 비동기 I/O 구조와 벤치마크를 공개했는데, 숫자가 눈에 띄어서 정리합니다.

문제: 대역폭이 아니라 대기가 병목

DuckDB의 기존 실행 모델은 CPU 스레드당 워커 하나입니다. 로컬 NVMe에서는 이걸로 충분합니다. 읽기 지연이 짧아서 워커가 I/O를 기다리는 시간이 얼마 안 되기 때문입니다.

S3 같은 오브젝트 스토리지에서는 사정이 다릅니다. HTTP 요청 하나의 지연이 수십 ms 단위라, 동기 방식으로는 워커가 요청을 보내고 응답을 기다리며 잠들어 있는 시간이 압도적으로 깁니다. 결과적으로 동시 요청 수가 부족해서 네트워크 대역폭을 채우지 못합니다. 실측에서 기존 버전(v1.5.5)은 25 Gbit/s를 쓸 수 있는 인스턴스에서 5 Gbit/s밖에 못 썼습니다. 인프라는 놀고 쿼리는 느린, 돈이 새는 구간입니다.

구조: 워커 풀과 I/O 풀의 분리

v2.0은 스레드 풀을 둘로 나눕니다.

크기역할
REGULARCPU 스레드당 1개 (기본)디코딩, 조인, 집계 등 실제 연산
ASYNC시스템 스레드의 4배, 최대 256개 (기본)블로킹 I/O 전담

ASYNC 풀을 CPU 수보다 훨씬 크게 잡을 수 있는 이유는 이 스레드들이 대부분의 시간을 HTTP 응답 대기로 보내기 때문입니다. CPU를 거의 안 쓰니 수백 개를 띄워도 부담이 없고, 그만큼 동시 요청 수가 올라가 네트워크 대역폭이 채워집니다.

여기에 read-ahead가 얹힙니다. 정규 워커가 데이터를 소비하는 속도보다 앞서서 fetch 작업을 큐에 넣어 두는 방식입니다. 작업 단위는 Parquet이면 row group, CSV면 고정 바이트 범위입니다.

미리 받아 두는 만큼 메모리를 먹으니 제어 장치도 있습니다. read_ahead_depth 설정이 기본 -1(무제한, 메모리 예산으로만 제한)이고, 양수로 제한하거나 0으로 끌 수 있습니다. 메모리가 부족해지면 임시 메모리 관리자와 협상해 큐 크기를 스스로 줄입니다.

숫자: Parquet 3배, CSV 19배

공개된 벤치마크는 EC2 r7i.16xlarge에서 S3의 TPC-H SF100 데이터를 읽는 구성입니다.

워크로드v1.5.5v2.0.0-dev배율
S3 Parquet 읽기8.230초2.844초2.9배
S3 CSV 읽기 (80.89GB)877.563초45.264초19.4배
로컬 디스크 (M4 MacBook Pro)1.321초0.883초1.5배

CSV의 19배가 특히 극적인데, 뒤집어 보면 기존 CSV 리더가 원격 스토리지에서 그만큼 직렬화되어 있었다는 뜻입니다. 네트워크 사용률은 5 Gbit/s에서 25 Gbit/s 포화로 올라갔습니다.

동시성 수치도 흥미롭습니다. 쿼리 4개를 동시에 돌렸을 때 v1.5.5는 평균 5.9코어(활용률 6%)를 쓰며 35.8초가 걸렸고, v2.0.0-dev는 48.1코어(75%)를 쓰며 15.6초에 끝냈습니다. I/O 대기에 묶여 있던 CPU가 풀려난 그림입니다.

제한도 명시되어 있습니다. 현재 구현된 것은 Parquet과 비압축 UTF-8 CSV뿐이고, JSON과 DuckDB 네이티브 포맷은 추후 예정입니다. row group이 거대해서 파일 안 병렬성이 부족한 경우에는 효과가 줄어듭니다.

PostgreSQL 18과 나란히 놓고 보면

같은 문제를 두 엔진이 어떻게 다르게 푸는지 비교하면 각자의 처지가 보입니다.

PostgreSQL 18DuckDB v2.0
대상 I/O로컬 디스크 (heap 읽기)원격 오브젝트 스토리지
방식io_method (worker / io_uring)ASYNC 스레드 풀 + read-ahead
단위블록row group / 바이트 범위
배경17년 만의 아키텍처 전환분석 엔진의 클라우드 이행

PostgreSQL은 커널 인터페이스(io_uring)까지 내려가 로컬 블록 I/O를 비동기화했고, DuckDB는 HTTP 위의 원격 읽기를 스레드 물량으로 병렬화했습니다. 방식은 달라도 결론은 같습니다. 스토리지가 어디에 있든, 워커가 I/O를 기다리며 잠드는 구조는 더 이상 용납되지 않는다는 것입니다.

같은 글에서 DuckDB는 v2.0과 함께 가는 방향도 살짝 내비쳤습니다. 8월 5일 40,000 스타 기념 글에서는 다중 동시 쓰기를 지원하는 원격 프로토콜 Quack까지 언급했는데, 임베디드 분석 엔진이라는 출발점에서 점점 멀어지는 중입니다. 오브젝트 스토리지 위의 분석 스택에서 DuckDB의 자리가 어디까지 커질지, v2.0이 나오면 직접 실행해 보고 후속으로 다루겠습니다.

참고 자료