Nếu bạn đang dùng Pandas để phân tích dữ liệu lớn và thấy nó chậm, hoặc đang cân nhắc cài đặt một database server phức tạp chỉ để chạy vài câu SQL — có lẽ bạn chưa biết đến DuckDB. Đây là embedded analytical database chạy trong cùng process với ứng dụng, không cần server riêng, nhưng xử lý truy vấn SQL nhanh hơn Pandas 10-50 lần cho các tác vụ phân tích.
Bài viết này giải thích DuckDB là gì, so sánh chi tiết với SQLite/Pandas, và hướng dẫn sử dụng thực tế trong Python. Xem thêm Cache là gì và API là gì — các khái niệm nền tảng trong hệ thống.
DuckDB là gì
DuckDB là một columnar in-process analytical database (OLAP), được thiết kế đặc biệt cho workload phân tích dữ liệu. Khác với PostgreSQL hay MySQL cần chạy server riêng, DuckDB nhúng trực tiếp vào ứng dụng của bạn — giống SQLite nhưng tối ưu cho analytical queries thay vì transactional operations.
Điểm mạnh nhất của DuckDB là khả năng query trực tiếp CSV, Parquet, JSON mà không cần import vào database. Bạn có thể chạy SQL phức tạp trên file hàng gigabyte ngay trong Python script.
Đặc điểm nổi bật
- In-process — Không cần server, chạy trong cùng process với Python/Java/Node. Zero configuration.
- Columnar storage — Lưu trữ theo cột (column-oriented), tối ưu cho aggregation và scan. Nhanh hơn row-based storage 10-100x cho analytical queries.
- Vectorized execution — Xử lý data theo batch (column vectors), tận dụng SIMD CPU instructions. Đây là lý do DuckDB nhanh hơn Pandas dù cùng chạy trên 1 máy.
- Full SQL support — Dùng PostgreSQL-compatible SQL syntax. JOIN, GROUP BY, window functions, CTEs — tất cả đều có.
- Direct file query — Query trực tiếp CSV, Parquet, JSON, Arrow mà không cần import.
SELECT * FROM 'file.csv'là tất cả những gì bạn cần. - Zero dependencies — Một file Python package, không cần cài đặt database server hay drivers.
DuckDB vs SQLite vs Pandas
Ba công cụ này phục vụ những mục đích khác nhau. Đừng chọn sai công cụ:
| Tiêu chí | DuckDB | SQLite | Pandas |
|---|---|---|---|
| Loại | OLAP Analytical | OLTP Transactional | DataFrame library |
| Storage | Columnar, file-based | Row-based, file-based | In-memory arrays |
| SQL support | Full SQL (JOIN, GROUP BY, Window, CTEs) | Full SQL | Limited (cần .query()) |
| Query file trực tiếp | CSV, Parquet, JSON, Arrow | CSV (import required) | CSV, Excel (import required) |
| Aggregation speed | Rất nhanh (vectorized) | Chậm (row-by-row) | Trung bình |
| Memory usage | Auto-managed, có thể limit | Fixed file size | Full RAM |
| Best for | Analytics, BI, ETL, Data Science | App storage, mobile, small data | Data wrangling, preprocessing |
Benchmark thực tế: Với 1 triệu rows và các truy vấn aggregation phức tạp, DuckDB nhanh hơn Pandas 10-50 lần, nhanh hơn SQLite 5-20 lần.
Cài đặt DuckDB
# Python (phổ biến nhất)
pip install duckdb
# R
install.packages("duckdb")
# Node.js
npm install duckdb
# Go
go get github.com/duckdb/duckdb-go
# Rust
cargo add duckdb
Sau khi cài, import và bắt đầu query ngay — không cần setup gì thêm.
Sử dụng DuckDB trong Python
Kết nối và tạo bảng
import duckdb
# Cách 1: File database (persistent)
con = duckdb.connect('analytics.db')
# Cách 2: In-memory (không lưu, nhanh hơn cho temporary analysis)
con = duckdb.connect(':memory:')
# Cách 3: Read-only mode (an toàn, không modify file)
con = duckdb.connect('data.parquet', read_only=True)
# Tạo bảng
con.sql('''
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name VARCHAR NOT NULL,
email VARCHAR UNIQUE,
created_at DATE DEFAULT CURRENT_DATE,
salary DECIMAL(10, 2),
is_active BOOLEAN DEFAULT true
)
''')
# Insert dữ liệu
con.sql("INSERT INTO users VALUES (1, 'Nguyen Van A', 'a@example.com', '2024-01-15', 1500.00, true)")
con.sql("INSERT INTO users VALUES (2, 'Tran Thi B', 'b@example.com', '2024-02-20', 2000.00, true)")
con.sql("INSERT INTO users VALUES (3, 'Le Van C', 'c@example.com', '2024-03-10', 1800.00, false)")
# Insert nhiều rows
con.sql("INSERT INTO users VALUES (4, 'Pham Van D', 'd@example.com', '2024-04-05', 2200.00, true)")
con.sql("INSERT INTO users VALUES (5, 'Hoang Thi E', 'e@example.com', '2024-05-01', 1900.00, true)")
Query dữ liệu cơ bản
# Query đơn giản
result = con.sql("SELECT * FROM users WHERE is_active = true").fetchall()
print(result)
# Query với alias
result = con.sql("""
SELECT
name,
salary,
salary * 12 AS annual_salary
FROM users
ORDER BY salary DESC
""").fetchall()
# Aggregation
stats = con.sql("""
SELECT
COUNT(*) as total_users,
AVG(salary) as avg_salary,
MIN(salary) as min_salary,
MAX(salary) as max_salary,
SUM(salary) as total_salary
FROM users
WHERE is_active = true
""").fetchone()
print(f"Total: {stats[0]}, Avg: {stats[1]:.2f}, Min: {stats[2]}, Max: {stats[3]}")
# GROUP BY với HAVING
by_status = con.sql("""
SELECT
is_active,
COUNT(*) as count,
AVG(salary) as avg_salary
FROM users
GROUP BY is_active
HAVING COUNT(*) > 0
ORDER BY count DESC
""").fetchall()
print(by_status)
Query trực tiếp CSV/Parquet (tính năng mạnh nhất)
Đây là điểm khác biệt quan trọng nhất của DuckDB. Bạn có thể query file CSV hoặc Parquet trực tiếp mà không cần import vào database:
# Query CSV trực tiếp
result = con.sql("""
SELECT
customer_id,
SUM(amount) as total_amount,
COUNT(*) as order_count
FROM read_csv_auto('transactions.csv')
WHERE date >= '2024-01-01'
GROUP BY customer_id
HAVING SUM(amount) > 1000
ORDER BY total_amount DESC
LIMIT 20
""").fetchall()
# Query Parquet (nhanh hơn CSV 5-10x)
result = con.sql("""
SELECT
product_category,
COUNT(*) as total_orders,
AVG(price) as avg_price,
SUM(quantity) as total_quantity
FROM read_parquet('orders/2024/*.parquet')
WHERE status = 'completed'
GROUP BY product_category
""").fetchdf() # Trả về Pandas DataFrame
# Query với wildcard (nhiều files)
result = con.sql("""
SELECT DATE_TRUNC('month', order_date) as month,
SUM(amount) as revenue
FROM read_parquet('sales_2024/*.parquet')
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month
""").fetchall()
# JOIN file CSV với file Parquet
result = con.sql("""
SELECT
o.order_id,
o.amount,
c.name,
c.region
FROM read_csv_auto('orders.csv') o
INNER JOIN read_parquet('customers.parquet') c
ON o.customer_id = c.id
WHERE o.date >= '2024-01-01'
""").fetchdf()
Tạo bảng từ Pandas DataFrame
import pandas as pd
# Tạo DataFrame
df = pd.DataFrame({
'name': ['A', 'B', 'C', 'D', 'E'],
'value': [100, 200, 300, 400, 500],
'category': ['x', 'y', 'x', 'y', 'z']
})
# Cách 1: Đăng ký như view (không copy data)
con.sql("CREATE VIEW my_data AS SELECT * FROM df")
# Cách 2: Tạo bảng thực sự (copy data vào DuckDB)
con.sql("CREATE TABLE my_table AS SELECT * FROM df")
# Cách 3: INSERT từ DataFrame
con.sql("INSERT INTO my_table SELECT * FROM df")
# Query kết quả trả về DataFrame
result_df = con.sql("SELECT * FROM my_data WHERE value > 200").fetchdf()
print(result_df)
# Hoán đổi: Export DuckDB query sang DataFrame
df_from_query = con.sql("""
SELECT category, SUM(value) as total
FROM my_table
GROUP BY category
""").fetchdf()
print(df_from_query)
Export kết quả
# Export to Parquet (recommended cho analytical workloads)
con.sql("""
COPY (SELECT * FROM users WHERE is_active = true)
TO 'active_users.parquet'
(FORMAT PARQUET, COMPRESSION 'zstd')
""")
# Export to CSV
con.sql("""
COPY (SELECT * FROM users)
TO 'all_users.csv'
(HEADER TRUE, DELIMITER ',')
""")
# Export to JSON
con.sql("""
COPY (SELECT * FROM users)
TO 'users.json'
(FORMAT JSON, ARRAY true)
""")
# Export to Pandas DataFrame
df_result = con.sql("SELECT * FROM users").fetchdf()
df_result.to_csv('users_export.csv', index=False)
Window Functions và CTEs
DuckDB hỗ trợ đầy đủ SQL features nâng cao:
# Window Functions - phân tích theo nhóm
result = con.sql("""
SELECT
department,
name,
salary,
AVG(salary) OVER (PARTITION BY department) as dept_avg,
salary - AVG(salary) OVER (PARTITION BY department) as diff_from_avg,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank_in_dept,
SUM(salary) OVER (PARTITION BY department) as dept_total
FROM employees
ORDER BY department, rank_in_dept
""").fetchdf()
# CTEs (Common Table Expressions) - query phức tạp dễ đọc hơn
result = con.sql("""
WITH monthly_sales AS (
SELECT
DATE_TRUNC('month', order_date) as month,
SUM(amount) as revenue,
COUNT(*) as orders
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
),
growth AS (
SELECT
month,
revenue,
LAG(revenue) OVER (ORDER BY month) as prev_revenue,
(revenue - LAG(revenue) OVER (ORDER BY month)) / LAG(revenue) OVER (ORDER BY month) * 100 as growth_pct
FROM monthly_sales
)
SELECT
month,
revenue,
growth_pct
FROM growth
ORDER BY month
""").fetchdf()
# Subqueries
result = con.sql("""
SELECT *
FROM users
WHERE salary > (SELECT AVG(salary) FROM users)
ORDER BY salary DESC
""").fetchall()
Performance tips và best practices
Tối ưu query
- Filter sớm (predicate pushdown) — Đặt WHERE clause càng sớm càng tốt để giảm data cần xử lý
- Chọn cột cần thiết — SELECT cột cụ thể thay vì SELECT * để giảm I/O
- Dùng Parquet thay vì CSV — Parquet nhanh hơn 5-10x, nén tốt, support predicate pushdown
- Partition data — Chia data theo date/region để query chỉ đọc partition cần
- Sort data — Nếu thường xuyên query theo date, sort data theo date
# TỐT: Filter sớm, đọc partition cần
result = con.sql("""
SELECT product_id, SUM(amount)
FROM read_parquet('orders/2024/*.parquet')
WHERE status = 'completed'
AND order_date >= '2024-01-01'
GROUP BY product_id
""")
# XẤU: Đọc tất cả rồi filter
result = con.sql("""
SELECT product_id, SUM(amount)
FROM read_parquet('orders/*.parquet')
WHERE status = 'completed' AND year(order_date) = 2024
""")
# TỐT: Chỉ SELECT cột cần
result = con.sql("""
SELECT customer_id, SUM(amount)
FROM read_parquet('orders.parquet')
GROUP BY customer_id
""")
# XẤU: SELECT * khi chỉ cần 2 cột
result = con.sql("SELECT * FROM read_parquet('orders.parquet')")
Cấu hình performance
# Giới hạn memory usage (mặc định: unlimited)
con.sql("SET memory_limit = '4GB'")
# Tăng threads cho parallel processing
con.sql("SET threads = 8")
# Enable/disable progress bar
con.sql("SET enable_progress_bar = true")
# Xem configuration hiện tại
config = con.sql("SELECT * FROM duckdb_settings()").fetchdf()
print(config)
Use cases phổ biến
1. Data Analysis cục bộ
Phân tích log files, exports, data dumps mà không cần setup database server. Chỉ cần cài DuckDB, đọc file, query. Lý tưởng cho data scientist làm EDA (Exploratory Data Analysis).
2. ETL Pipeline
Transform dữ liệu từ nhiều nguồn (CSV, Parquet, JSON) vào data warehouse cục bộ trước khi load lên cloud. DuckDB nhanh hơn Pandas 10-50x cho các tác vụ này.
# ETL: Combine multiple sources
con.sql("""
CREATE TABLE aggregated AS
SELECT
DATE_TRUNC('day', order_date) as date,
product_category,
COUNT(*) as orders,
SUM(amount) as revenue,
AVG(amount) as avg_order_value,
COUNT(DISTINCT customer_id) as unique_customers
FROM (
SELECT * FROM read_csv_auto('orders_2024.csv')
UNION ALL
SELECT * FROM read_parquet('orders_legacy/*.parquet')
UNION ALL
SELECT * FROM read_json('orders_api/*.json')
)
GROUP BY DATE_TRUNC('day', order_date), product_category
""")
# Export kết quả
con.sql("COPY aggregated TO 'daily_summary.parquet' (FORMAT PARQUET)")
3. Embedded Analytics
Nhúng trực tiếp vào ứng dụng Python để cung cấp analytical capabilities. Không cần backend database riêng, không cần API calls.
4. BI Tool Backend
Dùng làm fast analytical backend cho BI tools đơn giản. Thay thế việc query trực tiếp production database (vốn không an toàn và chậm).
So sánh với các database khác
| Database | Loại | Deployment | Best for |
|---|---|---|---|
| DuckDB | OLAP | Embedded | Local analytics, data processing, prototyping |
| SQLite | OLTP | Embedded | App storage, mobile apps, small data |
| PostgreSQL | OLTP/OLAP | Server | Production apps, multi-user systems |
| Snowflake | OLAP | Cloud | Enterprise data warehouse |
| ClickHouse | OLAP | Server/Cloud | Real-time analytics, high cardinality |
| Polars | DataFrame | Embedded | Python-native, similar to DuckDB |
Câu hỏi thường gặp
DuckDB có thay thế PostgreSQL không?
Không. DuckDB là embedded analytical database, không phải general-purpose transactional database. Dùng PostgreSQL cho production apps với concurrent users, writes, và ACID compliance. Dùng DuckDB cho analytics, data processing, và ETL.
DuckDB dùng bao nhiêu RAM?
Mặc định DuckDB sử dụng tất cả RAM có sẵn cho caching và vector operations. Có thể giới hạn: con.sql("SET memory_limit='4GB'")
Khi nào KHÔNG nên dùng DuckDB?
- Cần concurrent writes từ nhiều processes — DuckDB là single-writer
- Cần multi-user access đồng thời — dùng PostgreSQL hoặc cloud DB
- Data quá lớn không fit trên 1 máy — dùng Snowflake, BigQuery, hoặc Spark
- Cần real-time inserts từ nhiều sources — dùng ClickHouse hoặc TimescaleDB
DuckDB và Polars khác nhau thế nào?
Cả hai đều nhanh và embedded, nhưng khác nhau về API: Polars là DataFrame-first (giống Pandas), DuckDB là SQL-first. DuckDB mạnh hơn về SQL features phức tạp, Polars mạnh hơn về Python-native API. Có thể dùng cả hai cùng nhau: con.sql("SELECT * FROM df").fetchdf() để convert.
Kết luận
DuckDB là công cụ mạnh mẽ cho phân tích dữ liệu cục bộ. Nếu bạn đang dùng Pandas để xử lý data lớn và thấy chậm, thử DuckDB — bạn sẽ ngạc nhiên với tốc độ. Đặc biệt hữu ích khi:
- Cần query trực tiếp CSV/Parquet mà không import
- Phân tích data dump hoặc log files cục bộ
- Build ETL pipeline đơn giản, nhanh
- Prototype analytical features trước khi deploy lên production database
- Chạy BI queries trên local data mà không cần database server
Thử ngay: pip install duckdb và bắt đầu query!