1차 공개일 · 8월 18일1차 강의가 모두 공개됩니다
TECH 으로 돌아가기
TECH HACKER NEWS 오늘 8분 읽기 25 READS

DuckDB v2.0, S3 병목을 겨누는 비동기 I/O가 온다

DuckDB v2.0, S3 병목을 겨누는 비동기 I/O가 온다
SOURCE IMAGE · HACKER NEWS

데이터베이스에서 연산자가 아무리 빨라도 데이터를 제때 끌어오지 못하면 소용이 없다. DuckDB는 오랫동안 이 문제를 정면으로 풀기보다 우회했다. 필터와 프로젝션을 스캔 단계로 밀어 넣어(pushdown) 실제로 필요한 데이터만 읽는 방식이었다. 로컬 SSD에 놓인 파일을 낮은 지연·높은 대역폭으로 읽는 것이 주 사용처였기 때문에, 병목은 오히려 서브쿼리·조인·집계 쪽에 있었고 데이터 접근 경로 자체는 동기 방식으로도 충분했다. 즉 '빠른 로컬 디스크'라는 전제가 설계의 바탕에 깔려 있었다.

그러나 사용 맥락이 달라졌다. DuckDB의 구조가 DuckLake 같은 데이터 레이크 등 원격에 저장된 대규모 데이터셋을 질의하는 데 잘 맞는다는 점이 확인됐고, 올해 5월부터는 Quack 프로토콜을 통해 서버로도 구동할 수 있게 됐다. 데이터가 늘 로컬 SSD에 있다는 전제는 더 이상 항상 성립하지 않는다. 전형적인 데이터 레이크 구성은 S3 같은 블롭 스토리지에 데이터를 두고 같은 리전의 EC2에서 처리하는 형태인데, 여기서는 지연과 대역폭이 성능을 크게 좌우한다. 동시 요청을 충분히 던지지 못해 네트워크 대역폭을 다 쓰지 못하면, 스레드가 처리 대신 원격 읽기를 기다리며 시간을 허비하게 된다.

동기 읽기가 만드는 유휴 시간

Parquet 스캔은 로우 그룹 단위 작업(job)으로 나뉘고, 각 작업은 바이트 범위 요청을 던지는 하나 이상의 페치 태스크로 구성된다. 동기 I/O에서는 워커 스레드가 데이터가 도착할 때까지 블로킹된 채 디코딩·집계 같은 실제 작업을 시작하지 못한다. 원격 스토리지처럼 지연이 큰 환경에서 이 대기 시간은 그대로 손실이 된다. DuckDB 팀이 만든 해법은 개념적으로 단순하다. I/O를 요청한 워커 스레드를 멈춰 세우지 않고 읽기를 시작할 수 있게 하는 것, 즉 비동기 I/O다.

구현의 핵심은 두 개의 별도 스레드 풀이다. 원격 I/O를 담당하는 ASYNC 스레드는 HTTP 응답을 기다리며 대부분의 시간을 블로킹 상태로 보내 CPU 사용률이 매우 낮다. 그래서 이 풀은 일반 워커보다 훨씬 많은 스레드를 두는데, 기본값은 시스템 스레드의 4배이며 최대 256개로 제한된다. ASYNC 스레드들이 다음 작업의 데이터를 미리 당겨오는 동안 일반 워커는 현재 작업을 디코딩한다. 요청 시점에 읽는 대신 앞선 작업의 페치 태스크를 미리 예약하는 '읽기 선행(read-ahead)' 전략으로, 원격 스토리지의 지연을 감추기에 충분한 만큼의 요청을 항상 공중에 띄워 둔다.

작업의 정의는 포맷마다 다르다. Parquet에서는 한 파일의 로우 그룹 하나가, CSV에서는 대체로 고정 바이트 범위를 덮는 스캔 경계 하나가 작업이 된다. 큐를 채우는 데 전용 생산자 스레드는 필요 없다. 스캔 작업을 찾으러 온 워커가 허용된 범위까지 큐를 먼저 채우고, 워커가 큐에서 가장 오래된 작업을 가져가 카운트다운을 확인한다. I/O가 끝났으면 곧장 디코딩하고, 아직이면 스캔 태스크를 주차(park)시킨 뒤 다른 파이프라인 작업으로 넘어간다. 마지막 페치 태스크가 완료되면 주차됐던 스캔이 임의의 워커에서 재개된다.

메모리를 사고파는 선행 읽기

읽기 선행은 처리량을 얻는 대가로 메모리를 붙잡는다. 디코딩이 느리고 네트워크가 빠르면 미리 받아 둔 데이터가 쌓여 메모리 부족을 일으킬 수 있다. 이를 막기 위해 비동기 메모리 거버넌스가 함께 들어갔고, read_ahead_depth 설정으로 예산을 조절한다. 기본 모드에서는 조인·정렬·윈도우 연산자에 메모리를 배분하는 임시 메모리 관리자와 예산을 협상한다. 메모리 압박이 큰 연산자가 돌고 있으면 큐 예약이 즉시 예산을 초과해 한 번에 한 작업만 허용되고, 스캔은 사실상 동기 스캔에 가깝게 동작한다. 그 연산자가 끝나면 예산이 풀리며 큐가 다시 채워진다. 성능과 안정성을 자동으로 저울질하는 셈이다.

효과는 원격 환경에서 뚜렷했다. S3에 올린 SF100 데이터로 TPC-H Q6를 r7i.16xlarge(64 vCPU, 512GB) EC2에서, 파일 캐시를 끈 채 실행한 결과, 약 22GB·약 4,880개 로우 그룹의 Parquet 파일에서 평균 실행 시간이 v1.5.5의 8.230초에서 2.844초로 줄어 약 3배 빨라졌다. 네트워크 처리량 그래프에서 v1.5.5는 동기 읽기로 요청을 충분히 띄우지 못해 25Gbit/s 회선에서 5Gbit/s 부근에 머문 반면, v2.0.0-dev는 회선 한계에 근접했다. async_threads=48, http_retries=8 등으로 튜닝하고 선행 작업을 64개로 제한한 버전은 2.227초까지 내려가 v1.5.5 대비 약 3.7배 빨랐다.

다만 원격 이득만큼 로컬 이득이 크지는 않다는 점은 실무 판단에 중요하다. M4 Max 맥북 프로에서 OS 캐시를 지우고 콜드 상태로 같은 질의를 돌렸을 때 비동기 I/O는 약 1.5배(33%) 빨랐는데, SSD의 지연과 대역폭이 EC2/S3 네트워크보다 훨씬 유리하기 때문이다. 데이터가 캐시에 있는 핫 실행에서는 차이가 거의 없다. 또한 모든 실험에서 첫 데이터 전송까지 수백 밀리초의 공백이 관찰됐는데, 연결 개설과 TLS 핸드셰이크, 푸터 다운로드와 파싱에 드는 시간으로, 팀은 v2.0 정식 출시 전까지 개선 여지가 있다고 본다.

정리하면 비동기 I/O는 현재 Parquet과 압축되지 않은 탐색 가능 UTF-8 CSV에 적용되며, DuckDB 자체 포맷과 JSON 지원은 아직 남아 있다. 지금은 v2.0.0-dev 프리뷰 빌드로 시험해 볼 수 있고, 올가을 예정된 v2.0부터 기본값으로 켜진다. 컴퓨트와 스토리지를 분리한 S3/EC2 구성에서 스캔이 병목이라면 상당한 시간을 절약할 수 있지만, 로컬 SSD나 캐시된 데이터가 중심인 환경이라면 체감 이득은 제한적일 수 있다는 점을 함께 고려해 도입 시점을 판단할 만하다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://duckdb.org/2026/07/31/asynchronous-io
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기
처리 중...