Thú thật, mình đã làm việc với Pandas hơn 6 năm — và chưa bao giờ nghĩ sẽ đến ngày một thư viện khác khiến mình phải "yêu lại từ đầu". Vậy mà 2026 đến, và DuckDB đã làm được điều đó. Phiên bản DuckDB 1.5.2 (ra mắt tháng 4/2026) không còn là "SQLite cho phân tích" như cách nhiều người vẫn gọi nữa — nó là một engine columnar đa luồng, có thể bắn SQL trực tiếp lên Pandas DataFrame, file Parquet hay CSV mà đôi khi nhanh gấp 50 lần Pandas trên cùng tập dữ liệu.
Bài này mình sẽ chia sẻ cách kết hợp DuckDB với Pandas trong Python theo đúng quy trình mình đang dùng ở production: code thực tế, benchmark cập nhật 2026, và vài cái bẫy mà mình từng dính (để bạn khỏi mất một buổi tối debug như mình).
DuckDB Là Gì Và Tại Sao Phải Quan Tâm Trong 2026?
Nói gọn lại: DuckDB là một hệ quản trị cơ sở dữ liệu phân tích OLAP in-process. Nghĩa là nó chạy ngay bên trong tiến trình Python của bạn — không server riêng, không xác thực, không cấu hình mạng lằng nhằng. pip install duckdb, import vào, dùng. Hết.
Khác biệt cốt lõi so với Pandas thì sao?
- Pandas là thư viện DataFrame in-memory, đơn luồng, thiết kế cho thao tác linh hoạt trên dữ liệu vừa với RAM.
- DuckDB là engine SQL columnar, vector hóa, đa luồng, có trình tối ưu hóa truy vấn cost-based, và (đây mới là phần hay) có thể xử lý dữ liệu lớn hơn RAM nhờ streaming với spill-to-disk.
Trên các benchmark 33 triệu hàng, Pandas thường tràn bộ nhớ (OOM). Trong khi đó DuckDB thường về đích đầu tiên, theo sau là Polars với lazy evaluation. Nói cách khác: Pandas crash, DuckDB cười.
Vậy Khi Nào Nên Dùng DuckDB Thay Pandas?
- Tập dữ liệu lớn hơn vài GB, hoặc lớn hơn RAM khả dụng.
- Truy vấn phân tích phức tạp với nhiều JOIN, GROUP BY, window function.
- Cần đọc trực tiếp file Parquet/CSV/JSON mà không cần tải toàn bộ vào memory.
- Muốn dùng cú pháp SQL quen thuộc, thay vì chuỗi method dài dằng dặc của Pandas.
- Pipeline ETL cần hiệu năng cao và RAM thấp (rất hay gặp trên server staging giới hạn 8GB).
Cài Đặt DuckDB Cho Python Trong 2026
Một dòng lệnh duy nhất, không hơn:
pip install duckdb pandas pyarrow polars
Cài 4 gói cùng lúc giúp bạn có đủ đồ chơi cho quy trình hybrid mà mình sẽ nói ở dưới. pyarrow đặc biệt quan trọng vì DuckDB dùng Apache Arrow làm định dạng trao đổi dữ liệu zero-copy — đây chính là "bí mật" giúp pipeline Python 2026 nhanh đến vậy.
Kiểm tra phiên bản:
import duckdb
print(duckdb.__version__)
# 1.5.2 (tháng 4/2026)
Truy Vấn Pandas DataFrame Bằng SQL — Zero-Copy
Đây là tính năng "thần kỳ" mà mình thấy ấn tượng nhất khi mới chuyển sang DuckDB: bạn có thể bắn SQL thẳng vào Pandas DataFrame như thể nó là một bảng trong cơ sở dữ liệu. Không copy, không chuyển đổi, không serialize. Cơ chế này gọi là replacement scan — DuckDB tự dò biến Python trong scope hiện tại và "mượn" DataFrame ở chế độ chỉ đọc.
import duckdb
import pandas as pd
# Tạo DataFrame mẫu
df = pd.DataFrame({
"san_pham": ["Cafe", "Tra", "Banh", "Cafe", "Tra"],
"thanh_pho": ["HCM", "HN", "DN", "HCM", "HN"],
"doanh_thu": [120000, 80000, 50000, 150000, 95000]
})
# Truy vấn SQL trực tiếp trên DataFrame
ket_qua = duckdb.sql("""
SELECT
san_pham,
SUM(doanh_thu) AS tong_doanh_thu,
COUNT(*) AS so_don
FROM df
GROUP BY san_pham
ORDER BY tong_doanh_thu DESC
""").df()
print(ket_qua)
Phương thức .df() chuyển kết quả thành Pandas DataFrame. Muốn Polars? Dùng .pl(). Cần Arrow Table thuần? Dùng .arrow(). Đơn giản vậy thôi.
Tại Sao Replacement Scan Lại Nhanh Đến Vậy?
Khi bạn dùng df.to_sql() kiểu Pandas truyền thống để đẩy dữ liệu vào SQLite, Pandas phải sinh ra hàng đống câu lệnh INSERT INTO, chuyển từng giá trị thành Python object — chậm khủng khiếp. DuckDB thì đọc thẳng mảng numpy/Arrow nằm bên dưới DataFrame, gần như tức thời. Đó là lý do trên các phép GROUP BY, DuckDB nhanh hơn Pandas tới 50 lần khi chạy với AVX-512 vector hóa.
Đọc Trực Tiếp File CSV, Parquet, JSON
Một điểm cộng nữa: DuckDB có thể truy vấn file ngay tại chỗ, không cần load vào DataFrame trước. Tiết kiệm một bước, và (quan trọng hơn) tiết kiệm RAM.
Truy Vấn CSV
import duckdb
# Truy vấn trực tiếp file CSV
ket_qua = duckdb.sql("""
SELECT thanh_pho, AVG(doanh_thu) AS doanh_thu_tb
FROM 'du_lieu/ban_hang_2026.csv'
WHERE doanh_thu > 100000
GROUP BY thanh_pho
ORDER BY doanh_thu_tb DESC
""").df()
DuckDB tự phát hiện schema, dấu phân cách, kiểu dữ liệu. Khoảng 90% trường hợp là chạy luôn không cần can thiệp. Nhưng nếu CSV của bạn "kỳ quặc" (ai đó đã từng giao mình một file dùng dấu | làm phân cách), bạn có thể chỉ định rõ qua read_csv:
SELECT * FROM read_csv(
'du_lieu/log.csv',
delim='|',
header=True,
columns={'thoi_gian': 'TIMESTAMP', 'gia_tri': 'DOUBLE'}
)
Truy Vấn Parquet (Khuyến Nghị Cho Production)
Parquet là định dạng cột nén — gần như là default cho phân tích nghiêm túc. DuckDB tận dụng projection pushdown và predicate pushdown để chỉ đọc cột và dòng cần thiết, bỏ qua phần còn lại:
import duckdb
# Truy vấn nhiều file Parquet cùng lúc bằng glob
ket_qua = duckdb.sql("""
SELECT
thanh_pho,
COUNT(*) AS so_giao_dich,
SUM(doanh_thu) AS tong_doanh_thu
FROM read_parquet('du_lieu/ban_hang_*.parquet')
WHERE nam = 2026
GROUP BY thanh_pho
""").df()
Khi bạn viết SELECT *, optimizer của DuckDB chỉ thực sự đọc các cột được dùng — bạn không phải tự lo projection pushdown thủ công. Đây là một trong những điểm mà mình thấy "đáng tiền" nhất so với việc tự viết PyArrow.
Hive Partitioning
Tính năng partitioned Parquet trong DuckDB 1.5.2 cho phép tổ chức dữ liệu theo cấu trúc thư mục chuẩn Hive (kiểu nam=2026/thang=04/...):
# Ghi DataFrame ra Parquet phân vùng theo năm và tháng
duckdb.sql("""
COPY (SELECT * FROM df)
TO 'du_lieu/ban_hang'
(FORMAT 'parquet', PARTITION_BY (nam, thang))
""")
# Đọc selective — DuckDB chỉ quét các phân vùng phù hợp
duckdb.sql("""
SELECT *
FROM read_parquet('du_lieu/ban_hang/**/*.parquet', hive_partitioning=true)
WHERE nam = 2026 AND thang IN (3, 4)
""").df()
Benchmark Hiệu Năng 2026: DuckDB vs Pandas vs Polars
Bảng dưới đây tổng hợp benchmark trên tập dữ liệu 10–67 triệu hàng (CSV ~9 GB), được thực hiện bởi vài nhóm độc lập trong 2026 (mình tổng hợp lại từ vài nguồn — số có thể chênh nhẹ tùy phần cứng):
| Tác vụ | Pandas (baseline) | Polars | DuckDB |
|---|---|---|---|
| Đọc & lọc CSV | 1.0× | 5.0× nhanh hơn | 2.4× chậm hơn |
| GROUP BY tổng hợp | 1.0× | 2.1× nhanh hơn | 9.4× nhanh hơn |
| Sắp xếp (sort) | 495s (rất chậm) | 54× nhanh hơn | 13.5× nhanh hơn |
| JOIN bảng | 1.0× | 3.6× nhanh hơn | 1.2× nhanh hơn |
| 33M+ hàng | OOM (tràn bộ nhớ) | Hoàn thành (lazy) | Nhanh nhất |
| Bộ nhớ đỉnh | Cao nhất | Thấp (pruning) | Thấp + spill-to-disk |
Kết luận thực dụng: không có công cụ nào "thắng tuyệt đối". Polars vô địch ở DataFrame thuần Python một máy. DuckDB dẫn đầu ở SQL trên file lớn và dữ liệu lớn hơn RAM. Còn Pandas? Vẫn là điểm khởi đầu thân thiện nhất, với hệ sinh thái phong phú nhất. Đừng ép bản thân chọn một.
Quy Trình Hybrid 2026: DuckDB + Polars + Pandas
Một sự thật: các kỹ sư dữ liệu giàu kinh nghiệm trong 2026 không còn "trung thành" với một công cụ duy nhất nữa. Họ kết hợp cả ba trong cùng một pipeline, tận dụng zero-copy của Apache Arrow để chuyển dữ liệu giữa chúng mà không tốn thêm bộ nhớ:
- DuckDB: Quét và lọc các file Parquet/CSV khổng lồ trên đĩa, làm các JOIN và aggregation nặng.
- Polars: Áp dụng các phép biến đổi phức tạp tốc độ cao trên kết quả đã được lọc.
- Pandas: Định dạng kết quả cuối cùng để đưa vào scikit-learn, matplotlib, hoặc seaborn.
Đây là pipeline thực tế mà mình đang chạy trên một dự án phân tích bán lẻ:
import duckdb
import polars as pl
import pandas as pd
# Bước 1: DuckDB lọc 100GB Parquet trên S3 xuống còn 1 triệu hàng
df_loc_arrow = duckdb.sql("""
SELECT khach_hang_id, san_pham, doanh_thu, ngay_mua
FROM read_parquet('s3://bucket/giao_dich_2026/*.parquet')
WHERE doanh_thu > 500000
AND ngay_mua >= '2026-01-01'
""").arrow() # Trả về Arrow Table — zero-copy
# Bước 2: Polars biến đổi tốc độ cao
df_polars = pl.from_arrow(df_loc_arrow)
df_tong_hop = (
df_polars
.with_columns(thang=pl.col("ngay_mua").dt.month())
.group_by(["khach_hang_id", "thang"])
.agg([
pl.col("doanh_thu").sum().alias("tong_thang"),
pl.col("san_pham").n_unique().alias("so_san_pham")
])
)
# Bước 3: Pandas cho scikit-learn
df_pandas = df_tong_hop.to_pandas()
# Tiếp tục với feature engineering, train model, vẽ biểu đồ...
Tính Năng Mới Trong DuckDB 1.5.2 (Tháng 4/2026)
AsOf Join — Ghép Theo Thời Gian Gần Nhất
AsOf JOIN là giải pháp SQL native cho việc ghép dữ liệu theo trục thời gian — ví dụ kinh điển là ghép giao dịch chứng khoán với giá cổ phiếu gần nhất tại thời điểm đó:
SELECT
g.giao_dich_id,
g.thoi_gian_giao_dich,
p.gia
FROM giao_dich g
ASOF LEFT JOIN gia_co_phieu p
ON g.ma_co_phieu = p.ma_co_phieu
AND g.thoi_gian_giao_dich >= p.thoi_gian_cap_nhat
Appender Interface — Insert Hàng Loạt Tốc Độ Cao
Appender API có thể nạp 50.000 hàng vào bảng DuckDB trong dưới 1 giây — nhanh hơn nhiều so với INSERT INTO truyền thống (mình đã từng đợi 4 phút cho cùng dataset với executemany, đến lúc đổi sang Appender thì xong trong nháy mắt):
import duckdb
con = duckdb.connect('analytics.duckdb')
con.execute("CREATE TABLE log_su_kien (id INT, thoi_gian TIMESTAMP, su_kien VARCHAR)")
appender = con.appender('log_su_kien')
for i in range(50000):
appender.append_row(i, '2026-04-25 10:00:00', f'event_{i}')
appender.close()
DuckLake — Mở Rộng Sang Data Lake
Phiên bản 1.5.2 đưa extension DuckLake lên trạng thái production, cho phép DuckDB đọc/ghi data lake định dạng Iceberg và Delta Lake một cách nguyên bản. Với mình, đây là bước ngoặt biến DuckDB thành công cụ phân tích doanh nghiệp đầy đủ — chứ không còn là "đồ chơi cho laptop" như nhiều người vẫn nghĩ.
Các Lỗi Thường Gặp Và Cách Khắc Phục
1. Python UDF Quá Chậm (Overhead 100×)
Tránh dùng Python UDF trên các đường nóng (hot path). Mỗi lần DuckDB phải gọi qua Python, bạn mất hết hiệu năng vector hóa. Hãy ưu tiên SQL thuần, hoặc nếu thực sự cần custom logic thì viết extension C++:
# KHÔNG NÊN — chậm 100×
@duckdb.create_function("dao_nguoc", str, str)
def dao_nguoc(s):
return s[::-1]
# NÊN — dùng SQL native
duckdb.sql("SELECT reverse(ten) FROM bang")
2. Thiếu Thống Kê (Statistics)
Sau khi insert hàng loạt, hãy chạy ANALYZE để planner ước lượng đúng cardinality, tránh nested loop join chậm "rùa bò":
duckdb.sql("ANALYZE bang_lon")
3. Bộ Nhớ Tràn Khi Xử Lý > RAM
Đặt memory_limit ở khoảng 80% RAM khả dụng để DuckDB tự động spill ra đĩa thay vì crash:
duckdb.sql("SET memory_limit = '12GB'")
duckdb.sql("SET temp_directory = '/tmp/duckdb_spill'")
So Sánh Nhanh: Khi Nào Dùng Cú Pháp Nào?
| Trường hợp | Khuyến nghị |
|---|---|
| EDA, làm sạch dữ liệu nhỏ < 1GB | Pandas |
| Pipeline tốc độ cao, dữ liệu vừa | Polars (lazy) |
| SQL trên file Parquet/CSV lớn | DuckDB |
| JOIN nhiều bảng lớn | DuckDB |
| Window function, CTE phức tạp | DuckDB |
| Tích hợp với scikit-learn, matplotlib | Pandas (cuối pipeline) |
| Dữ liệu lớn hơn RAM | DuckDB hoặc Polars streaming |
Câu Hỏi Thường Gặp (FAQ)
DuckDB có thay thế hoàn toàn Pandas không?
Không. DuckDB và Pandas bổ sung cho nhau, không cạnh tranh. DuckDB mạnh ở các truy vấn SQL OLAP trên dữ liệu lớn; Pandas vẫn là lựa chọn tốt nhất cho EDA tương tác, custom logic phức tạp, và tích hợp với scikit-learn hay matplotlib. Quy trình tối ưu trong 2026 là dùng cả hai song song — và mình thực sự không thấy lý do gì để phải chọn một bỏ một.
DuckDB có chạy được trên dữ liệu lớn hơn RAM không?
Có. DuckDB có cơ chế streaming và spill-to-disk tự động. Nếu RAM không đủ, DuckDB dùng đĩa làm vùng tạm — chậm hơn một chút nhưng không bị crash như Pandas. Bạn nên cấu hình SET memory_limit và SET temp_directory để kiểm soát hành vi này.
DuckDB và Polars: Nên chọn cái nào trước?
Nếu đội của bạn quen SQL và cần truy vấn phân tích phức tạp trên file Parquet/CSV lớn, hãy bắt đầu với DuckDB. Nếu đội thích cú pháp DataFrame fluent kiểu Spark/Pandas và cần tốc độ trên một máy, hãy chọn Polars. Tin vui: cả hai đều dùng Apache Arrow, nên bạn có thể chuyển dữ liệu giữa chúng zero-copy bất cứ lúc nào.
DuckDB có hỗ trợ ghi vào S3 không?
Có. DuckDB hỗ trợ đọc và ghi trực tiếp với S3, Cloudflare R2, Azure Blob Storage, Google Cloud Storage qua extension httpfs. Bạn chỉ cần cấu hình credential rồi dùng URL s3://bucket/file.parquet ngay trong câu lệnh SQL — không cần SDK riêng.
Có nên dùng DuckDB cho production OLTP (xử lý giao dịch) không?
Không nên. DuckDB được thiết kế cho OLAP (phân tích), không phải OLTP (giao dịch). Cho ứng dụng cần INSERT/UPDATE đồng thời với độ trễ thấp, hãy dùng PostgreSQL, MySQL hoặc SQLite. DuckDB tỏa sáng ở các tác vụ "đọc nhiều, ghi ít, truy vấn phức tạp".
Kết Luận
DuckDB đã hoàn thiện vai trò là công cụ phân tích in-process tiêu chuẩn của Python trong 2026. Với khả năng truy vấn SQL trực tiếp trên Pandas DataFrame và file Parquet/CSV, hiệu năng vượt trội trên dữ liệu lớn, và tích hợp Arrow zero-copy với Polars, DuckDB không thay thế Pandas — nó nâng cấp toàn bộ pipeline phân tích dữ liệu của bạn.
Bước tiếp theo? Cài DuckDB 1.5.2, thử convert một workflow Pandas hiện có sang DuckDB SQL, và đo benchmark trên chính dữ liệu của bạn. Mình khá chắc bạn sẽ ngạc nhiên — đặc biệt khi dataset vượt qua mốc vài GB. Cứ thử đi rồi quay lại bảo mình kết quả nhé.