Hacker News

Why DuckDB 2.0 is faster

DuckDB 2.0은 왜 더 빠른가

DuckDB 2.0 alpha의 성능 개선을 S3 비동기 I/O, 재귀 CTE, JSON용 VARIANT 저장 방식으로 나눠 직접 측정합니다. 테스트 환경에서는 S3 읽기가 2~3배, 깊은 Git 커밋 계보 탐색이 최대 160배 빨라졌으며, JSON 필드 쿼리와 저장 공간도 개선됐습니다.

AI 요약

DuckDB 2.0 alpha는 쿼리 작성 방식을 바꾸지 않아도 S3 읽기 성능을 높이고, 깊은 계층 탐색과 JSON 필드 조회를 효율적으로 처리합니다. 글쓴이는 M5 노트북 한 대와 가정용 인터넷에서 직접 측정했으며, 네트워크 환경에 따라 결과가 달라질 수 있다고 밝혔습니다.

S3 읽기: 다운로드와 연산을 겹칩니다

DuckDB 1.5.5에서는 각 작업 스레드가 데이터를 내려받고 기다린 뒤 Parquet을 디코딩했습니다. 네트워크를 기다리는 동안 CPU가 쉬고, 디코딩하는 동안에는 새 다운로드가 진행되지 않았습니다. 2.0은 다운로드 전용 스레드 풀을 두고 여러 row group을 미리 받아 버퍼에 쌓습니다. 작업 스레드는 준비된 데이터를 디코딩하므로 네트워크와 CPU가 동시에 일합니다.

이 동작을 조절하는 설정은 read_ahead_depth입니다. 기본값 -1은 스레드 수에 맞춰 자동으로 미리 읽으며, 0으로 설정하면 1.5.5와 같은 방식으로 동작합니다. 2.2GB Parquet 파일에서 한 컬럼을 읽는 테스트는 18.8초에서 7.7초로 줄었습니다. 13.6GB 규모의 대형 Parquet 파일 23개는 11.8초에서 3.9초, 1.7GB CSV 파일은 116초에서 55초로 단축됐습니다. 반면 약 1MB짜리 작은 Parquet 파일 30개는 3.7초에서 3.3초로 차이가 작았습니다. 파일마다 footer와 데이터를 요청하는 왕복 시간은 미리 읽기로 없애기 어렵기 때문입니다.

재귀 CTE: 테이블을 매 단계 다시 읽지 않습니다

재귀 CTE(Recursive CTE)는 조직도, 폴더 트리, 데이터 계보처럼 부모와 자식 관계를 반복해서 따라가는 쿼리입니다. 1.5에서는 각 단계마다 테이블 전체를 다시 읽어 다음 행을 찾았습니다. 2.0은 테이블을 한 번 읽고 부모 컬럼의 조회 구조를 만든 뒤, 각 단계에서 새로 발견한 행만 찾습니다. 따라서 반복 횟수와 전체 테이블 크기를 곱한 비용 대신 실제로 방문하는 행에 가까운 비용이 듭니다.

글쓴이는 2만 개 커밋으로 만든 Git 저장소에서 HEAD부터 루트까지 재귀 CTE로 계보를 탐색했습니다. 1.5.5는 실행마다 1.8~16초가 걸렸고, 2.0 alpha는 매번 0.10초였습니다. 계층이 얕은 조직도에서는 차이가 작을 수 있지만, Git 기록이나 데이터 계보처럼 깊은 연결을 따라갈 때 효과가 큽니다. 계층은 정수 ID를 사용하는 부모·자식 테이블로 표현하고, 재귀 과정에서 깊이나 비용 같은 값을 전달할 때는 USING KEY를 고려하라고 설명합니다.

VARIANT: 규칙적인 JSON 필드를 컬럼처럼 저장합니다

DuckDB 2.0의 VARIANT는 JSON에서 자주 나타나며 값의 자료형도 일관적인 필드를 별도 하위 컬럼으로 저장합니다. 드물게 나타나거나 행마다 자료형이 달라지는 필드는 나머지 데이터에 남습니다. 예를 들어 로그마다 level, service, latency_ms가 같은 자료형으로 존재하면 이 필드들을 분리해 저장할 수 있습니다. latency_ms가 어떤 행에서는 숫자이고 다른 행에서는 문자열이면 나머지 데이터에 들어가 조회가 느려질 수 있습니다.

500만 건 이벤트를 JSON 문자열, VARIANT, 일반적인 형식 지정 컬럼으로 저장한 테스트에서 파일 크기는 각각 224MB, 85MB, 45MB였습니다. VARIANT는 JSON 문자열보다 2.7배 작았습니다. 필터 조회는 JSON 문자열의 366ms에 비해 VARIANT가 63ms였고, 국가별 금액 합계는 408ms와 61ms였습니다. 글쓴이는 필드 조회 성능이 일반 컬럼에 가까워졌다고 설명합니다. 다만 리스트 검색은 예외였습니다. VARIANT 리스트를 VARCHAR[]로 변환하는 데 1.96초가 걸려 JSON 경로 조회보다 느렸습니다. 자주 조회하는 필드는 일반 컬럼으로 승격하고, 나머지는 VARIANT에 두는 방식을 권합니다.

그 밖의 변경

2.0에는 트리거, 중첩 스키마, CTE 안의 DML도 들어갑니다. 외부 파일 캐시 블록을 메모리에서 내보낼 때 임시 디렉터리에 저장하는 설정도 추가됐습니다. 메모리 제한 300MB에서 S3의 854MB Parquet 파일을 두 번째로 읽는 시간은 23.9초에서 0.35초로 줄었다고 합니다. CLI에는 SQL 포매터와 쿼리 기록 조회 기능이 더해졌고, read_json은 시간대 오프셋이 포함된 ISO-8601 타임스탬프를 TIMESTAMPTZ로 읽습니다.

Hacker News 반응

  • @stacktraceyo — 시각화가 훌륭합니다. 덧붙이자면 새 C++ 확장 API도 확장 기능을 개발하고 배포하는 관점에서 더 빨라질 예정입니다.
  • @jiggawatts — Umbra나 CedarDB처럼 태스크 기반 설계를 쓰는 데이터베이스 엔진이 더 많아졌으면 합니다. 대부분의 데이터베이스 엔진은 여전히 교환 연산을 쓰는 n개 스레드 방식 병렬 처리와 부족한 비동기 I/O 관리에 머물러 있는 것 같습니다. DuckDB가 이 부분을 개선하고 있지만, 수십 년 된 연구와 구현을 따라잡는 측면도 있습니다. 코어 1,024개와 그에 맞는 네트워크·저장장치 대역폭이 있지만 지연 시간이 큰 컴퓨터를 한 쿼리만으로 100% 활용할 수 있느냐고 개발자에게 묻곤 합니다. 대부분의 소프트웨어는 그렇게 하지 못합니다. CPU와 I/O 작업을 겹쳐서 기다릴 필요가 없는 일이 서로를 기다리지 않게 하는 일은 중요합니다.
    • @dist-epoch — 파일 전체를 대상으로 하는 암호화 해시 연산은 병렬화할 수 없습니다. 청크별로 해시하거나 다른 종류의 해시를 쓰는 방법은 있을 수 있습니다.
    • @jiggawatts — 물론 가능합니다. Merkle tree를 쓰면 잎과 그 위의 노드를 병렬로 계산할 수 있습니다. 노드 크기가 4KB라면 4MB 이상인 파일은 코어 1,024개를 활용할 수 있습니다.
  • @robertclaus — AI가 쓴 글이라는 점은 괜찮았는데, 2.0에 트리거가 추가됐다는 부분에 이르러서도 S3 파일 접근 최적화보다 덜 중요한 일처럼 다루는 걸 보고는 더 못 읽겠습니다. 릴리스 노트를 직접 보겠습니다.
    • @tiagod — 저도 같은 말을 하려던 참입니다. 글 전체에서 S3와 재귀 CTE를 설명하면서 트리거는 대충 넘어갑니다.
  • @OneManHorde — 2.0에서 재귀 CTE 성능이 크게 좋아진 건 맞지만, 그래프 데이터를 다루는 DuckDB 확장 기능도 이미 있다는 점을 잊지 마세요. 일반적인 관계형 데이터베이스에는 까다로운 쿼리를 그 확장 기능으로 우아하게 처리하는 방법을 소개한 2025년 글이 있습니다.
  • @scythmic_waves — 시각화는 정말 좋지만, 문장에서는 LLM 느낌이 강하게 납니다. “한 가지 설정이 이걸 좌우합니다”나 “이제 비용은 반복 횟수와 테이블 크기의 곱이 아니라 실제로 건드리는 행에 따라 달라집니다” 같은 문장을 읽으려면 머리가 복잡해집니다. 제가 틀렸으면 미안하지만, 맞다면 글을 대신 쓰게 하려고 LLM을 쓰지 마세요. 독자에게 해롭습니다.
    • @gchamonlive — 제게는 사람들이 온라인에서 남의 행동을 보고 무의식적으로 따라 하는 집단 히스테리처럼 들립니다. 더 중요하고 유용한 정보가 점점 이런 식으로 전달될 텐데 피할 방법은 없습니다. 이런 댓글은 새로 온 사람이 별문제 없이 읽을 글을 싫어하게 만들 수 있습니다.
    • @johnfn — 낮은 노력으로 쓴 글을 “피상적인 비난”이나 “곁가지 불평”으로 보는 이유가 궁금합니다.
  • @kristianp — AI가 쓴 티가 몇 군데 났지만 글 전체가 아주 나쁘지는 않았습니다. 다만 재귀 CTE 부분은 설명이 매끄럽지 않았고 최적화 방법도 충분히 설명하지 않았습니다. 재귀 CTE가 개선된 방법을 설명하는 DuckDB 글이 따로 있습니다.
  • @rumbledownunda — 다음 Jupyter 노트북에서 써보겠습니다.

원문: MotherDuck / 번역·요약: Trawling