# DuckDB: truy vấn file CSV và Parquet của khách hàng ngay trên laptop

> Khách vừa gửi bạn một thư mục dữ liệu export, và một giờ nữa là đến buổi họp tiếp theo. Lúc này bạn cần một database không phải dựng server, chạy ngay trong script của bạn.

Bản gốc: https://fdetimes.net/vi/cong-cu/duckdb-phan-tich-du-lieu-khach-tren-laptop/

Khách vừa gửi bạn một thư mục file export, có CSV, có Parquet, kèm một câu hỏi: "số liệu này có dùng được không?". Câu trả lời tốt nhất là vài câu SQL chạy xong trong vài phút, chứ không phải một ticket xin cấp hạ tầng.

DuckDB được làm ra cho đúng tình huống đó. Tại GOTO Amsterdam 2024, một kỹ sư của DuckDB trình bày cách công cụ này xử lý hàng trăm GB dữ liệu trên một chiếc laptop, đọc thẳng file CSV và Parquet.

Với một Forward Deployed Engineer, con số đó quan trọng không phải vì nó là kỷ lục. Nó có nghĩa là ngay ngày đầu bạn đã chạy được những truy vấn có ý nghĩa trên dữ liệu của khách, trước khi có ai dựng cho bạn một cụm máy.

## Vì sao không có server lại là ưu điểm?

Theo tài liệu chính thức, DuckDB không chạy thành một process riêng mà nằm luôn bên trong ứng dụng gọi nó. Repo duckdb/duckdb trên GitHub mô tả nó là một hệ quản trị cơ sở dữ liệu SQL in-process, thiết kế cho công việc phân tích, và phát hành theo giấy phép MIT.

Vì thế trên laptop của bạn không có server nào phải cài, cũng không có service nào phải quản lý. Nếu khách có quy trình duyệt phần mềm chặt, hãy nêu hai điểm này ngay trong lời đề nghị cài đặt: chạy nhúng, giấy phép rõ ràng. Bộ phận duyệt sẽ có ít thứ phải xem xét hơn.

## Câu hỏi đầu tiên: trong file này có gì?

Tính năng bạn sẽ dùng nhiều nhất là `read_csv`. Bộ đọc CSV của DuckDB tự nhận ra định dạng file và tự suy ra kiểu của từng cột, và tài liệu khuyên nên thử cách tự động này trước.

Thử hình dung khách gửi file `orders.csv` và muốn biết doanh thu theo tháng. Bạn không cần khai báo schema:

```sql
SELECT date_trunc('month', order_date) AS thang,
       count(*) AS so_don,
       sum(amount) AS doanh_thu
FROM read_csv('orders.csv')
GROUP BY thang
ORDER BY thang;
```

Nhưng bộ sniffer chỉ đọc một mẫu tuần tự của file, mặc định 20.480 dòng. Nếu những giá trị lạ nằm ở cuối file, nó có thể đoán sai kiểu cột. Đây là rủi ro về độ đúng của con số, không chỉ là chuyện tốc độ.

Vì vậy, trước khi đưa con số nào lên slide, hãy xem DuckDB đã đọc từng cột thành kiểu gì. Tài liệu làm theo hai bước: nạp file vào bảng, rồi chạy `DESCRIBE`.

```sql
CREATE TABLE orders AS SELECT * FROM read_csv('orders.csv');
DESCRIBE orders;
```

Nếu `order_date` hiện ra là chuỗi, bạn có ba cách sửa. Tăng `sample_size`, đặt `-1` để đọc cả file. Ghi đè kiểu của riêng một cột bằng `types`, vì sniffer luôn ưu tiên các tùy chọn người dùng tự đặt. Hoặc khai báo `dateformat` và `timestampformat` khi DuckDB đoán sai định dạng ngày giờ.

## Parquet: chỉ đọc những cột cần dùng

Nếu khách có sẵn Parquet, bạn sẽ đỡ vất vả hơn nhiều. Khi truy vấn một file Parquet, DuckDB chỉ đọc những cột câu lệnh thật sự cần, và bộ lọc được đẩy xuống ngay bước đọc file.

Thử hình dung một bảng giao dịch có 200 cột, còn câu hỏi của bạn chỉ dùng `customer_id`, `amount` và `created_at`. Với Parquet, chỉ ba cột đó được đọc, và điều kiện trong `WHERE` được áp dụng ngay khi đọc file.

```sql
SELECT customer_id, sum(amount)
FROM read_parquet('transactions.parquet')
WHERE created_at >= DATE '2024-01-01'
GROUP BY customer_id;
```

Khi khách hỏi bạn muốn nhận dữ liệu ở định dạng nào, hãy xin Parquet. Nếu chỉ có CSV mà bạn sẽ còn truy vấn nhiều lần, hãy chuyển sang Parquet một lần ngay từ đầu bằng câu lệnh `COPY`, mặc định nén Snappy.

**Điểm mấu chốt:** Trước khi dựng pipeline, hãy hỏi dữ liệu của khách vài câu bằng SQL ngay trên máy mình.

## Khi file lớn hơn RAM thì sao?

File lớn hơn RAM chưa phải lý do để dừng lại. Tài liệu tinh chỉnh hiệu năng của DuckDB cho biết các tác vụ lớn hơn bộ nhớ vẫn chạy được, vì hệ thống ghi tạm dữ liệu ra đĩa. Tài liệu cũng lưu ý DuckDB đôi khi mở quá nhiều luồng, chẳng hạn do HyperThreading, khiến máy chậm đi; lúc đó chỉnh lại bằng `SET threads`.

Mark Needham kể rằng khi chuyển CSV sang Parquet trong một container bị giới hạn bộ nhớ, anh phải đặt rõ giới hạn bộ nhớ cho DuckDB. Trên laptop hay máy ảo ít RAM ở chỗ khách, hãy làm theo cách đó, và đặt giá trị thấp hơn lượng RAM còn trống thực tế chứ không phải tổng RAM của máy.

Còn một điểm dễ quên: `COPY` vẫn đọc CSV qua chính bộ sniffer ở trên. Nếu một cột bị đoán sai kiểu, kiểu sai đó sẽ bị ghi cứng vào file Parquet, và mọi truy vấn sau đều thừa hưởng lỗi.

Vì thế hãy chạy `DESCRIBE` và sửa kiểu trước, rồi đưa đúng các tùy chọn đó vào câu lệnh chuyển đổi. Ví dụ dưới đây giả định máy chỉ còn trống khoảng 6 GB:

```sql
SET memory_limit = '4GB';
COPY (SELECT * FROM read_csv('events.csv', sample_size = -1))
TO 'events.parquet' (FORMAT parquet);
```

Chuyển xong, nên đếm số dòng ở cả hai đầu. Nếu `count(*)` trên file CSV và trên file Parquet không khớp nhau, hãy tìm ra nguyên nhân trước khi dùng file mới.

## Hai lỗi dễ mắc nhất là gì?

Lỗi nguy hiểm nhất là tin ngay vào kiểu dữ liệu sniffer đoán ra, rồi mang lên slide một cột ngày thực ra đang bị đọc thành chuỗi. Lỗi này làm sai chính con số bạn trình bày. Cách tránh: chạy `DESCRIBE`, và tăng `sample_size` hoặc tự khai báo kiểu khi thấy cột nào lạ.

Lỗi thứ hai mới là chuyện tốc độ: truy vấn đi truy vấn lại một file CSV lớn thay vì chuyển một lần sang Parquet. Trên máy ít RAM, thêm việc quên đặt `memory_limit` hay để quá nhiều luồng chạy cùng lúc. Lỗi này làm bạn chậm, nhưng không làm sai kết quả.

## Nên học gì trước, và ghi vào CV thế nào?

Hãy học theo đúng thứ tự công việc bạn sẽ gặp. Bắt đầu với `read_csv` và thói quen chạy `DESCRIBE`, kèm các tùy chọn `sample_size`, `types`, `dateformat`. Tiếp theo là Parquet và lý do nó nhanh, sau cùng là `COPY`, `memory_limit` và `threads` cho những ngày gặp file lớn.

Cách luyện nhanh nhất là tự đi lại cả vòng đó một lần với một file công khai: truy vấn CSV, kiểm tra kiểu cột, chuyển sang Parquet, đối chiếu số dòng, rồi so thời gian chạy.

Khi ghi vào CV, đừng chỉ liệt kê "DuckDB" trong mục kỹ năng. Nên mô tả bạn đã làm được gì với nó: dữ liệu lộn xộn đến mức nào, bạn trả lời câu hỏi gì, và mất bao lâu thì có câu trả lời.

Chẳng hạn, một dòng như "chuyển 50 GB CSV sang Parquet trên laptop, trả lời câu hỏi doanh thu của khách ngay trong buổi chiều", kèm một repo nhỏ để người khác kiểm chứng, nói được nhiều hơn hẳn một danh sách công cụ.

Lần tới khách gửi một thư mục export, đừng vội xin hạ tầng: hãy mở DuckDB, chạy `DESCRIBE`, và mang đến buổi họp một con số bạn đã tự kiểm tra kiểu dữ liệu.

**Thử ngay tuần này:**

- Lấy một file CSV công khai khá lớn, nạp bằng read_csv, chạy DESCRIBE để xem kiểu từng cột, rồi thử sample_size = -1 xem kết quả có khác không
- Chuyển chính file đó sang Parquet bằng COPY, chạy cùng một câu truy vấn trên cả hai định dạng và so thời gian
- Viết lại quy trình đó thành một mục ngắn trong README hoặc CV: dữ liệu bao nhiêu GB, bạn hỏi câu gì, và có câu trả lời sau bao lâu

## Nguồn

- [Why DuckDB](https://duckdb.org/why_duckdb)

- [GitHub - duckdb/duckdb](https://github.com/duckdb/duckdb)

- [CSV Import](https://duckdb.org/docs/current/data/csv/overview.html)

- [DuckDB's CSV Sniffer: Automatic Detection of Types and Dialects](https://duckdb.org/2023/10/27/csv-sniffer.html)

- [CSV Tips (DuckDB documentation)](https://duckdb.org/docs/current/data/csv/tips.html)

- [CSV Auto Detection](https://duckdb.org/docs/current/data/csv/auto_detection)

- [Reading and Writing Parquet Files](https://duckdb.org/docs/current/data/parquet/overview.html)

- [Tuning Workloads](https://duckdb.org/docs/current/guides/performance/how_to_tune_workloads)

- [DuckDB: Crunching Data Anywhere, From Laptops to Servers (GOTO Amsterdam 2024)](https://gotopia.tech/sessions/3114/duckdb-crunching-data-anywhere-from-laptops-to-servers)

- [Exporting CSV files to Parquet file format with Pandas, Polars, and DuckDB](https://markhneedham.com/blog/2023/01/06/export-csv-parquet-pandas-polars-duckdb)
