---
title: "DuckDB nhanh vì nó bỏ qua cái khúc ai cũng quên"
description: "Mổ xẻ bên trong DuckDB: vì sao một engine SQL chạy ngay trong tiến trình, bỏ qua mạng, lại nhanh hơn cả cụm máy chủ triệu đô, và dân HN nghĩ gì."
pubDate: "2026-06-19"
lang: "vi"
slug: "duckdb-skips-the-network-tax"
url: "https://hoangyell.com/vi/duckdb-skips-the-network-tax/"
author: "HoangYell"
translation_en: "https://hoangyell.com/duckdb-skips-the-network-tax/"
categories: ["System Design"]
tags: ["duckdb", "databases", "sql", "analytics", "performance"]
---

Lần đầu DuckDB làm tôi quê là khi tôi có file Parquet 6 GB và một cái deadline. Quy trình "đàng hoàng" là upload file lên đâu đó, ngồi chờ cái warehouse thức dậy, rồi gõ một câu `CREATE TABLE` mà tôi biết một tiếng sau sẽ xoá. Thay vào đó tôi gõ `SELECT * FROM 'orders.parquet'` ngay trên laptop. Kết quả về trước khi tay tôi kịp với ly cà phê.

Tôi tưởng có mánh gì đó. Không. Cái nhanh nằm ở chỗ nó _không_ làm gì cả.

Kyle Cheung bên Greybeam viết loạt bài ba phần mổ xẻ ruột gan DuckDB, và Phần 1 chính là cái lên top HN. Nó đi theo đúng một câu query, từ lúc bước vào engine đến lúc sẵn sàng chạy. Cái hay là suốt bài tác giả cứ lặp lại đúng một câu hỏi cộc lốc ở mỗi chặng: chỗ này nhanh nhờ đâu?

## Câu chuyện gói trong một câu

DuckDB là một cơ sở dữ liệu SQL phân tích chạy ngay trong tiến trình của bạn - một file binary chưa tới 20 MB, không server, không port, không daemon - bạn nạp nó như nạp một thư viện rồi chĩa thẳng vào đống file Parquet, CSV, JSON như thể chúng đã là database sẵn.

Hình dạng nó chỉ có vậy. `pip install duckdb`. Rồi viết SQL trên file. Không có connection string nào hết.

## Cái khúc ai cũng quên

*So sánh độ trễ: Chạy in-process trực tiếp trong bộ nhớ vs. đóng gói qua giao thức mạng:*

```markdy
scene width=1100 height=540
theme: light=doodle dark=nebula
layout auto

user Client1 "Client App (Python / Go)"
service Wire "TCP Socket / Giao thuc ODBC (Serialize + Network + Parse)"
database ServerDB "Database ngoai (PostgreSQL / Snowflake)"
user Client2 "Client App / Buffer RAM (Arrow / Pandas / NumPy)"
database DuckDB "DuckDB Engine (Bo nho chia se Zero-Copy)"

group traditional "1. Database Client-Server truyen thong":
  Client1
  Wire
  ServerDB

group duckdb_arch "2. Engine DuckDB trong tien trinh":
  Client2
  DuckDB

edge e1: Client1 -> Wire: "Gui cau query SQL"
edge e2: Wire -> ServerDB: "Do tre mang TCP"
edge e3: ServerDB -> Wire: "Serialize tung dong ket qua"
edge e4: Wire -> Client1: "Client parse lai byte stream"

edge e5: Client2 -> DuckDB: "Quet truc tiep RAM (Zero-Copy)"
edge e6: DuckDB -> Client2: "Tra ve con tro vung nho"

beat intro "DuckDB bo han cuoc xe qua mang" {
  show $nodes stagger=50ms
  glow DuckDB "Chay chung tien trinh - bo hoan toan chi phi serialize qua mang"
}

player:
    playback:
      loop true
    controls:
      speed true
      speeds "0.25"
      fullscreen true
      gif true
      theme true
    interaction:
      zoom true
      pan true
      click_to_play true
      double_click_to_reset true
      keyboard false
    chrome:
      badge true
      progress none
```

Lựa chọn thiết kế mà cả bài xoay quanh lại nằm ngoài engine query. Nó nằm ở chỗ: mạng.

Đa số database phân tích là server. Snowflake, BigQuery, Redshift, Postgres. Bạn mở kết nối, gửi SQL qua socket, kết quả chạy ngược về qua dây. Trên đường đi, mỗi giá trị trong kết quả bị serialize thành chuỗi byte, đẩy qua TCP, rồi parse lại ở đầu kia.

Năm 2017, Mark Raasveldt và Hannes Mühleisen - đúng hai người sau này làm ra DuckDB - đăng một bài báo với cái tựa hay nhất ngành database: _Don't Hold My Data Hostage_ (đừng bắt cóc dữ liệu của tôi). Họ đo xem chuyện gì thực sự xảy ra khi bạn kéo một tập kết quả ra khỏi warehouse. Bản thân cái giao thức client, ODBC với JDBC, thường là khúc chậm nhất trong cả câu query. Có khi nó tốn thời gian hơn cả việc tính ra đáp án.

Hai lý do. Đường gigabit chỉ tầm 125 MB/s, nên kết quả lớn có khi truyền lâu hơn tính. Và ODBC trả dữ liệu từng giá trị một - với kết quả 100 triệu dòng thì đó là hàng trăm triệu lần gọi hàm, mỗi lần tự copy bộ nhớ, tự kiểm tra kiểu.

DuckDB né cả hai bằng cách sống chung tiến trình với code của bạn. Không có gì để serialize hết. Khi bạn query một dataframe pandas, cái "replacement scan" cho phép DuckDB đọc thẳng buffer mà tiến trình Python của bạn đang giữ. NumPy bảo "đây, một triệu số int64", DuckDB đọc luôn đúng cái buffer đó. Zero copy, không sao chép.

## Từ SQL ra kế hoạch, trong khoảng một mili giây

Khi SQL đã vào trong engine, nó đi qua đúng các bước quen thuộc: parse, bind, plan, optimize. DuckDB fork luôn cái parser của Postgres, nên cú pháp của nó thấy thân quen lạ.

Khúc optimizer mới là chỗ làm tôi mở terminal ra gõ thật. DuckDB phơi luôn optimizer ra thành một danh sách các bước nhỏ có tên, bạn xem được và tắt được từng cái:

```sql
SELECT * FROM duckdb_optimizers();
-- 33 dòng: filter_pushdown, join_order, row_group_pruner,
-- late_materialization, common_subexpressions, ...
```

Bạn chạy `SET disabled_optimizers = 'filter_pullup, join_order'` rồi nhìn xem thiếu một bước thì nó hư ra sao. Cả pha optimize thường xong trong khoảng một mili giây.

Nặng nhất là chọn thứ tự join. Một câu join sáu bảng có 30.240 hình dạng cây khác nhau, và khoảng cách giữa cách tốt nhất với cách tệ nhất chênh nhau cả mấy bậc độ lớn. DuckDB mô hình hoá query thành một đồ thị, rồi dùng quy hoạch động (DPhyp, DPccp) để khỏi tính lại những thứ tự nó đã giải xong. Đúng cái mánh làm Fibonacci nhanh, đem áp vào chuyện bảng nào gặp bảng nào trước.

## Nửa còn lại: đừng bao giờ đọc cái mình không cần

File gốc của DuckDB là một file `.duckdb` duy nhất, học từ SQLite. Bên trong, dữ liệu xếp theo cột, chia thành block 256 KB, mỗi block mang một checksum để một bit lật trên ổ SSD laptop biến thành lỗi báo ra, chứ không thành đáp án sai.

Chi tiết quyết định tốc độ là **zone map**. Mỗi row group (tối đa 122.880 dòng) lưu min và max của từng cột, kèm số lượng null. Khi bạn chạy `WHERE event_date > '2026-01-01'`, DuckDB nhìn max của từng row group trước, thấy không khớp nổi thì bỏ qua nguyên cụm. Nó không đọc dữ liệu luôn.

Mánh này không có gì lạ. Đúng cái ý tưởng mà mấy warehouse đắt tiền xài, chỉ khác cái tên đẹp hơn:

| Engine     | Họ gọi là gì               |
| ---------- | -------------------------- |
| DuckDB     | zone map                   |
| Snowflake  | micro-partition pruning    |
| BigQuery   | block pruning              |
| ClickHouse | minmax data-skipping index |

Mà khi query Parquet trực tiếp, DuckDB còn chẳng cần tới định dạng riêng. Parquet vốn đã lưu min/max theo từng row group, nên DuckDB đọc footer, quyết xem row group nào thoả điều kiện, rồi chỉ lấy đúng mấy cột cần. Với file ở xa, nó gửi một request HTTP lấy mỗi cái footer, rồi range-request đúng số byte cần đọc. Một câu `WHERE` viết khéo trở thành cách để tải về ít file hơn.

## Dân HN đang cãi nhau chuyện gì

Cái thread phần lớn là khen, mà bản thân chuyện đó cũng là một dấu hiệu - DuckDB hiếm khi dính cái nghi ngờ kiểu HN. `steve_adams_86` chốt đúng cái đồng thuận: anh xài vì nó dễ, ở lại vì hoá ra nó "giỏi tới mức vô lý", và "tới giờ vẫn làm tôi nể đều đều". `anitil` chỉ ra chỗ đặc biệt: đa số dự án giỏi việc nhỏ _hoặc_ giỏi việc lớn. DuckDB giỏi cả hai.

`0xferruccio` đưa ra cái use case đậm chất 2026 nhất thread: bơm log mọi phiên Claude Code của từng kỹ sư từ S3 vào DuckDB để tìm chỗ developer experience đang hổng. Đúng cái việc keo dán âm thầm.

Nhưng comment sắc nhất lại là tiếng nói ngược. `willtemperley` nhắc rằng "chỉ là thư viện" có một cạnh sắc: nếu bạn không link động được - ví dụ trong môi trường App Store - thì DuckDB hơi khó xài, vì link tĩnh mấy extension của nó cực kỳ chật vật. "DuckDB tuyệt thật, nhưng nó giống hộp đen hơn là thư viện." Trong thế giới đó anh chọn Arrow C++, vì nó build portable hơn. Cái thiết kế chạy-trong-tiến-trình làm DuckDB nhanh, cũng chính là cái bắt bạn nhúng nó theo điều kiện của nó, chứ không theo điều kiện của bạn.

## Có nên đọc bài gốc không?

| Nên đọc nếu...                                                      | Khỏi đọc nếu...                                   |
| ------------------------------------------------------------------- | ------------------------------------------------- |
| Bạn xài DuckDB và muốn biết _vì sao_ câu `WHERE` lại bỏ qua file đó | Bạn chỉ cần công thức nấu ăn, không cần ruột gan  |
| Bạn muốn một mô hình rõ ràng parse → bind → optimize → plan         | Bạn đã thuộc lòng columnar storage với zonemap    |
| Bạn thích nhìn một câu query bị bám đuôi từ đầu tới cuối            | Bạn mong đọc Phần 2 - khúc thực thi nằm ở bài sau |

Đây là Phần 1 trong ba, và nó dừng ngay trước khúc thực thi: xử lý vector hoá với morsel-driven parallelism được để dành. Một cái kết treo hơi ác cho bài viết về một database.

Nhưng bài học của Phần 1 tự nó đứng vững. Chúng ta đổ biết bao công sức làm engine query khôn hơn - thứ tự join tốt hơn, optimizer chặt hơn, cắt tỉa thông minh hơn. DuckDB cũng làm hết mấy thứ đó. Nhưng cú tăng tốc lớn nhất của nó lại đến từ một chuyện đơn giản: request mạng nhanh nhất là cái request bạn không bao giờ phải gửi.

***

Thảo luận trên [Hacker News](https://news.ycombinator.com/item?id=48553388) · Nguồn: [greybeam.ai](https://www.greybeam.ai/blog/duckdb-internals-part-1) · Đăng bởi [marklit](https://news.ycombinator.com/user?id=marklit)
