DuckDB — bỏ server, query thẳng từ Python
Hướng dẫn dựng pipeline analytics với DuckDB-Python: SQL, DataFrame, Parquet, UDF — tất cả trong một buổi chiều, không cần database server.
Bụi WireBao nhiêu tool trên bàn mới đủ?
Mình hỏi thật: lần cuối bạn chạy một cái analytics query, bạn phải mở bao nhiêu tab? Jupyter cho Pandas, một cái terminal cho PostgreSQL, có khi thêm cả DBeaver để xem kết quả cho dễ. Rồi export CSV, import lại, chạy thêm vài dòng Python xử lý ngoại lệ...
Nếu bạn đang gật đầu, thì DuckDB sinh ra chính là để giải quyết mớ bòng bong này.
Trước DuckDB — mỗi bước một công cụ
Giả sử team bạn 4 người, đang phân tích dữ liệu bán hàng từ file Parquet. Workflow cũ trông kiểu:
- Pandas đọc file Parquet vào DataFrame
- Viết Python loop để aggregate — chậm, tốn RAM
- Muốn chạy window function? Sorry, Pandas không native support — phải hack bằng
.groupby().transform() - Kết quả cần pivot? Thêm một đống code nữa
- Muốn join với bảng khác? Load thêm DataFrame, RAM tăng gấp đôi
Mỗi bước là một "thế" riêng lẻ, nhưng không ai dạy bạn nối các thế lại thành bài quyền hoàn chỉnh. Kết quả: code dài, khó maintain, và cứ mỗi lần data source thay đổi là refactor cả đống.
Sau DuckDB — một engine, mọi thứ
DuckDB chạy in-process, không cần server. Bạn pip install duckdb, import, và query thẳng lên Pandas DataFrame, Polars DataFrame, hay PyArrow Table — không cần load lại data.
import duckdb
import pandas as pd
df = pd.read_parquet("sales_2025.parquet")
result = duckdb.sql("""
SELECT
category,
month,
SUM(revenue) as total_revenue,
SUM(revenue) OVER (PARTITION BY category ORDER BY month) as cumulative
FROM df
WHERE region = 'VN'
ORDER BY category, month
""").df()
Window function, aggregate, filter — tất cả trong một câu SQL, trả về DataFrame ngay. Không server, không connection string, không docker-compose.
Nói thẳng ra thì DuckDB biến Python notebook thành một analytics engine đầy đủ, mà bạn không cần rời khỏi ecosystem Python.
Kịch bản thật: pivot báo cáo hàng tuần
Ví dụ cụ thể: team bạn cần báo cáo doanh thu theo category, pivot theo tháng, mỗi thứ Hai gửi cho sếp.
pivot = duckdb.sql("""
PIVOT df
ON month
USING SUM(revenue)
GROUP BY category
""").df()
Trước đây bạn viết cỡ 15–20 dòng Pandas với pivot_table(), xử lý NaN, rename columns. Giờ 5 dòng SQL, đọc là hiểu.
Một kịch bản khác — giả sử bạn lưu data theo Hive-style partitioned Parquet (partition theo năm và tháng). Muốn đọc chỉ Q1/2025:
q1 = duckdb.sql("""
SELECT * FROM read_parquet('data/year=2025/month={1,2,3}/*.parquet')
""").df()
DuckDB chỉ đọc đúng partition cần thiết — không scan toàn bộ folder. Với dataset lớn, khác biệt về tốc độ rất rõ rệt.
Thử ngay trong 30 phút
Bước 1 — Cài đặt
pip install duckdb pandas pyarrow
Bước 2 — Tạo data mẫu
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
'date': pd.date_range('2025-01-01', periods=10000, freq='h'),
'category': np.random.choice(['Electronics', 'Food', 'Fashion'], 10000),
'region': np.random.choice(['HN', 'HCM', 'DN'], 10000),
'revenue': np.random.uniform(100, 5000, 10000).round(2)
})
Bước 3 — Query thử
import duckdb
top5 = duckdb.sql("""
SELECT category, region, SUM(revenue) as total
FROM df
GROUP BY category, region
ORDER BY total DESC
LIMIT 5
""").df()
print(top5)
Bước 4 — Thử UDF (User-Defined Function)
from duckdb import typing as T
@duckdb.create_function(duckdb.default_connection, "vnd_format", [T.DOUBLE], T.VARCHAR)
def vnd_format(amount):
return f"{amount:,.0f} VND"
formatted = duckdb.sql("""
SELECT category, vnd_format(SUM(revenue)) as total_vnd
FROM df
GROUP BY category
""").df()
print(formatted)
Bước 5 — Export kết quả
duckdb.sql("""
COPY (SELECT * FROM df)
TO 'output' (FORMAT PARQUET, PARTITION_BY (region))
""")
Từ cài đặt đến export partitioned Parquet — đủ cả trong một buổi chiều.
Ba cái bẫy mình từng dính
Bẫy 1: Quên close connection khi dùng persistent database. DuckDB lock file khá chặt. Mình từng mở 2 notebook cùng trỏ vào một file .duckdb, cả hai đều treo cứng. Giải pháp: dùng in-memory (:memory:) cho ad-hoc analysis, chỉ dùng persistent khi thật sự cần lưu trữ.
Bẫy 2: Column name trùng keyword SQL. Column tên order, group, select — DuckDB sẽ parse lỗi mà error message không rõ ràng lắm. Tip: bọc trong dấu ngoặc kép "order" hoặc rename column trước khi query.
Bẫy 3: Nghĩ DuckDB thay thế được production database. DuckDB là analytical engine — giống luyện kata trong dojo để thành thạo kỹ thuật phân tích, nhưng ra trận OLTP thật thì vẫn cần PostgreSQL hay MySQL. DuckDB mạnh ở OLAP: scan lớn, aggregate nặng, analytical query phức tạp. Đừng ép nó làm transactional workload.
Khi nào nên rút DuckDB ra dùng?
- Ad-hoc analysis trên notebook: thay vì viết Pandas dài dòng, SQL cho nhanh
- ETL pipeline nhẹ: đọc Parquet, transform, ghi ra Parquet — không cần Spark
- Prototype trước khi deploy lên data warehouse: viết SQL chuẩn, migrate sau dễ hơn
- Local development: không Docker, không database server, không drama
DuckDB không phải giải pháp cho mọi bài toán. Nhưng trong vùng nó mạnh — analytical workload trên Python — thì nó như võ sĩ đã luyện đủ bài: gọn, nhanh, và đánh đúng chỗ cần đánh.
---
Bụi Wire — nghiện đọc release notes lúc 2 giờ sáng