Cơn ác mộng rò rỉ bộ nhớ lúc 2 giờ sáng
Đã 2 giờ sáng, và quạt tản nhiệt laptop của tôi kêu rú lên như động cơ phản lực sắp cất cánh. Tôi đang cố gắng chạy một lệnh tổng hợp dữ liệu đơn giản trên file CSV 15GB bằng Pandas. 32GB RAM đã bị chiếm dụng đến 99%, vùng nhớ đệm (swap space) hoạt động quá tải, và rồi điều gì đến cũng phải đến: MemoryError. Kernel bị sập, và 30 phút xử lý dữ liệu tan thành mây khói.
Trong sự nghiệp của mình, tôi đã tin dùng MySQL cho các ứng dụng web, Postgres vì sự tin cậy, và MongoDB cho dữ liệu phi cấu trúc. Nhưng để xử lý dữ liệu cục bộ? Việc dựng lên một RDBMS đầy đủ giống như dùng dao mổ trâu để giết gà vậy. Tôi không muốn quản lý một service chạy ngầm, loay hoay với các chuỗi kết nối, hay chờ đợi lệnh COPY FROM chậm chạp. Tôi chỉ muốn truy vấn dữ liệu của mình.
Rồi tôi tìm thấy DuckDB. Hãy coi nó như ‘SQLite dành cho Phân tích’. Nó chạy trực tiếp bên trong tiến trình Python của bạn, không cần cấu hình, và có thể xử lý những tập dữ liệu khổng lồ vốn thường làm Pandas ‘nghẹt thở’.
Tại sao Pandas không phải lúc nào cũng là giải pháp tối ưu
Pandas là tiêu chuẩn của ngành, nhưng nó đi kèm với một khoản ‘thuế bộ nhớ’ nặng nề. Đó là một thư viện hướng dòng (row-oriented) và luôn cố gắng đẩy mọi byte dữ liệu vào RAM. Nếu tập dữ liệu của bạn là 5GB, Pandas thường tiêu tốn từ 15GB đến 20GB RAM do chi phí quản lý đối tượng và các bản sao nội bộ.
SQLite là một lựa chọn thay thế phổ biến, nhưng nó là cơ sở dữ liệu lưu trữ theo dòng được tối ưu hóa cho các tác vụ giao dịch (OLTP). Nếu bạn yêu cầu SQLite tính giá trung bình của 100 triệu dòng, nó phải quét qua từng cột trong các dòng đó. Điều này cực kỳ kém hiệu quả trong khoa học dữ liệu.
DuckDB thay đổi cuộc chơi với kiến trúc lưu trữ theo cột (columnar-store) được thiết kế cho các tác vụ phân tích (OLAP). Nếu bạn chỉ cần trung bình cộng của cột ‘Price’, DuckDB sẽ bỏ qua tất cả các cột khác trên đĩa. Kết hợp với việc thực thi truy vấn vector hóa (vectorized query execution), nó mang lại hiệu năng sánh ngang với Snowflake hay BigQuery, nhưng chạy hoàn toàn trên CPU cục bộ của bạn.
Thực tế về DuckDB: Ưu và Nhược điểm
Tại sao nó chiến thắng
- Không phụ thuộc: Chỉ là một file thực thi C++ duy nhất. Với người dùng Python, chỉ cần
pip install duckdbmà không cần thêm driver bên ngoài. - Tối ưu tốc độ: Sử dụng thực thi SIMD vector hóa. Điều này cho phép CPU xử lý hàng nghìn dòng dữ liệu trong một chu kỳ lệnh duy nhất.
- Ngôn ngữ SQL thuần túy: Bạn không cần học một API riêng biệt nào cả. Nếu bạn biết viết câu lệnh
SELECT, bạn đã biết cách dùng DuckDB. - Truy cập file trực tiếp: Bạn có thể truy vấn trực tiếp các file CSV, Parquet và JSON mà không cần bước ‘import’ bắt buộc.
- Nén dữ liệu cực tốt: Một file CSV 1GB thường giảm xuống còn chưa đầy 150MB khi lưu trữ dưới định dạng gốc của DuckDB.
Những điểm hạn chế
- Không dành cho đa người dùng: Giống như SQLite, nó không được thiết kế để hàng chục người dùng ghi vào cùng một file cùng lúc. Đây là công cụ cho các nhà phân tích và kỹ sư, không phải backend cho một mạng xã hội.
- Kiểu dữ liệu nghiêm ngặt: Pandas rất linh hoạt với kiểu dữ liệu, nhưng DuckDB thì không. Bạn có thể thấy sự khắt khe này hơi phiền phức cho đến khi nó cứu bạn khỏi một lỗi tính toán do một chuỗi ký tự ẩn trong cột số gây ra.
Bộ công cụ (Stack) khuyến nghị
Cài đặt DuckDB còn nhanh hơn cả việc pha một tách cà phê. Tôi đề xuất bộ công cụ tinh gọn này cho kỹ thuật dữ liệu cục bộ:
# Tạo môi trường ảo mới
python -m venv venv
source venv/bin/activate
# Cài đặt DuckDB và các thư viện dữ liệu hiện đại
pip install duckdb pandas pyarrow
Nếu bạn thích dòng lệnh, hãy cài DuckDB CLI. Trên macOS, brew install duckdb cung cấp cho bạn một giao diện terminal mạnh mẽ. Bạn có thể chạy SQL trực tiếp trên các file thô mà không cần viết một dòng mã Python nào.
Triển khai: Vượt xa khỏi Pandas
Hãy xem sự khác biệt trong thực tế. Giả sử chúng ta có một file 10GB tên là logs.csv. Cách tiếp cận truyền thống với Pandas thường như sau:
import pandas as pd
# Lệnh này có khả năng làm treo một chiếc laptop thông thường
df = pd.read_csv('logs.csv')
result = df.groupby('status').agg({'response_time': 'mean'})
Với DuckDB, file được xử lý như một bảng ảo. Nó truyền dữ liệu theo luồng (stream), nghĩa là nó không bao giờ cần tải toàn bộ 10GB vào RAM của bạn:
import duckdb
# DuckDB quét tiêu đề file và xử lý theo từng cụm dữ liệu (chunk)
query = """
SELECT status, AVG(response_time)
FROM 'logs.csv'
GROUP BY status
"""
result = duckdb.sql(query).df()
print(result)
Lợi thế “Zero-Copy”
DuckDB có thể truy vấn các DataFrame của Pandas hoặc bảng Arrow hiện có mà không cần sao chép chúng. Nó trỏ trực tiếp đến địa chỉ bộ nhớ nơi dữ liệu của bạn đang nằm. Điều này cho phép bạn sử dụng SQL để lọc một DataFrame với tốc độ ánh sáng.
import pandas as pd
import duckdb
my_df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})
# DuckDB tự động 'nhìn thấy' biến my_df trong phạm vi Python của bạn
result = duckdb.sql("SELECT SUM(a) FROM my_df").fetchone()
print(result[0])
Xử lý tập dữ liệu Parquet khổng lồ
Các đường ống dữ liệu (pipeline) hiện đại hiếm khi dựa vào CSV. DuckDB cực kỳ xuất sắc trong việc đọc các thư mục Parquet được phân vùng. Bạn có thể sử dụng các mẫu glob để tổng hợp hàng tỷ dòng dữ liệu trên hàng trăm file chỉ trong vài giây:
-- Lệnh này hoạt động trong CLI hoặc thông qua Python API
SELECT
count(*),
sum(total_sales)
FROM 'data/sales/*/*.parquet'
WHERE region = 'APAC';
Lời kết
Lần tới khi bạn đang nhìn chằm chằm vào thanh tiến trình trong khi Pandas vật lộn để phân tích một file lớn, hãy dừng lại. Đừng tìm đến một container Docker nặng nề hay một cụm máy chủ đám mây. Hãy thử DuckDB trước. Nó đã thay đổi quy trình làm việc của tôi, cho phép tôi xử lý 100 triệu dòng trên một chiếc laptop bình thường trong khi nhâm nhi tách cà phê.
Nó sẽ không thay thế instance Postgres đang chạy thực tế (production) của bạn. Tuy nhiên, để khám phá dữ liệu, xây dựng pipeline CI/CD và kỹ thuật đặc trưng (feature engineering) cục bộ, đây là công cụ hiệu quả nhất trong bộ công cụ dữ liệu hiện đại.

