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ì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íDuckDBSQLitePandas
LoạiOLAP AnalyticalOLTP TransactionalDataFrame library
StorageColumnar, file-basedRow-based, file-basedIn-memory arrays
SQL supportFull SQL (JOIN, GROUP BY, Window, CTEs)Full SQLLimited (cần .query())
Query file trực tiếpCSV, Parquet, JSON, ArrowCSV (import required)CSV, Excel (import required)
Aggregation speedRất nhanh (vectorized)Chậm (row-by-row)Trung bình
Memory usageAuto-managed, có thể limitFixed file sizeFull RAM
Best forAnalytics, BI, ETL, Data ScienceApp storage, mobile, small dataData 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

DatabaseLoạiDeploymentBest for
DuckDBOLAPEmbeddedLocal analytics, data processing, prototyping
SQLiteOLTPEmbeddedApp storage, mobile apps, small data
PostgreSQLOLTP/OLAPServerProduction apps, multi-user systems
SnowflakeOLAPCloudEnterprise data warehouse
ClickHouseOLAPServer/CloudReal-time analytics, high cardinality
PolarsDataFrameEmbeddedPython-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!

Chào các bạn mình là Quốc Hùng , mình sinh ra thuộc cung song tử ,song tử luôn khẳng định chính mình ,luôn luôn phấn đấu vượt lên phía trước ,mình sinh ra và lớn lên tại vùng đất võ cổ truyền ,đam mê của mình là coder ,ngày đi học tối về viết blog ...