Cài đặt và kích hoạt Extension Iceberg cho DuckDB
Trước khi có thể truy vấn dữ liệu phân tán, bạn cần đảm bảo DuckDB đã được cài đặt extension chính thức của Apache Iceberg. Extension này đóng vai trò là cầu nối (bridge) để DuckDB hiểu định dạng file Parquet và metadata của Iceberg.
Tại sao phải làm bước này: DuckDB mặc định chỉ xử lý các file Parquet đơn lẻ hoặc các bảng in-memory. Để tương tác với Catalog Iceberg (nơi quản lý phiên bản và schema), engine cần plugin chuyên biệt để parse metadata file.
Kết quả mong đợi: Extension được tải về và kích hoạt thành công trong phiên làm việc hiện tại, không có lỗi dependency.
Chạy lệnh sau trong terminal của Ubuntu 24.04 để cài đặt extension:
duckdb -c "INSTALL iceberg; LOAD iceberg;"
Kiểm tra xem extension đã được load chưa bằng lệnh liệt kê:
duckdb -c "LIST extensions;"
Verify kết quả: Trong danh sách trả về, dòng chứa "iceberg" phải có trạng thái "LOADED". Nếu thấy "INSTALLED" nhưng không phải "LOADED", bạn cần chạy lệnh LOAD thêm một lần nữa.
Cấu hình kết nối Catalog Iceberg
Thiết lập môi trường biến cho S3/Storage
Để DuckDB có thể truy cập vào nơi lưu trữ các file data (thường là S3, MinIO hoặc local filesystem), bạn cần cấu hình các biến môi trường (Environment Variables). Trong bài này, chúng ta giả định bạn đã có một bucket S3 hoặc một thư mục local đang chứa catalog Iceberg.
Tại sao cần cấu hình: DuckDB cần biết endpoint của storage và key/secret để xác thực (hoặc cấu hình anonymous access nếu dùng local). Nếu không có biến này, các lệnh CREATE TABLE ICEBERG sẽ bị lỗi "Permission denied" hoặc "Connection refused".
Đối với trường hợp dùng local filesystem (phổ biến nhất khi test trên Ubuntu), ta chỉ cần set biến đường dẫn. Nếu dùng S3 thực tế (AWS/MinIO), bạn cần thêm AWS_ACCESS_KEY_ID và AWS_SECRET_ACCESS_KEY.
Thiết lập biến môi trường cho đường dẫn catalog (giả định bạn lưu catalog tại /var/data/iceberg_catalog):
export ICEBERG_CATALOG_PATH="/var/data/iceberg_catalog"
Tạo thư mục nếu chưa tồn tại để tránh lỗi:
mkdir -p /var/data/iceberg_catalog
Verify kết quả: Chạy lệnh echo $ICEBERG_CATALOG_PATH và đảm bảo nó trả về đường dẫn chính xác mà bạn vừa set.
Đăng ký Catalog trong DuckDB
Bây giờ, ta cần khai báo catalog này bên trong DuckDB để nó biết cách ánh xạ tên namespace (namespace) tới đường dẫn vật lý (path).
Tại sao phải đăng ký: DuckDB không tự động quét toàn bộ hệ thống tìm Iceberg catalog. Bạn phải khai báo rõ ràng: "Catalog tên là 'my_iceberg_catalog' nằm ở đường dẫn X và dùng loại backend Y (file, rest, s3)".
Đối với môi trường phát triển/kiểm thử trên Linux, ta thường dùng loại catalog là "file" để lưu metadata trực tiếp vào thư mục local. Dưới đây là lệnh SQL để tạo catalog:
duckdb -c "CREATE ICEBERG CATALOG my_catalog (TYPE 'file', BASE_DIR '/var/data/iceberg_catalog');" -c "LIST catalogs;"
Giải thích lệnh:
- TYPE 'file': Sử dụng hệ thống file local để lưu metadata Iceberg.
- BASE_DIR: Đường dẫn vật lý nơi chứa các snapshot và metadata table.
- Lệnh thứ hai dùng để xác nhận catalog đã được tạo.
Verify kết quả: Lệnh LIST catalogs phải trả về một bảng chứa dòng "my_catalog" với loại "iceberg".
Truy vấn dữ liệu phân tán qua SQL
Tạo bảng Iceberg từ dữ liệu mẫu
Để truy vấn, trước hết cần có dữ liệu trong bảng Iceberg. Chúng ta sẽ tạo một bảng mới từ một bảng tạm (temporary table) hoặc file CSV/Parquet có sẵn, và lưu nó vào catalog Iceberg vừa tạo.
Tại sao dùng cách này: Đây là quy chuẩn "Create Table As Select" (CTAS) của DuckDB để chuyển đổi dữ liệu in-memory sang định dạng Iceberg phân tán, bao gồm việc tự động tạo metadata và versioning.
Kết quả mong đợi: Một bảng Iceberg được tạo trong catalog, dữ liệu được viết vào các file Parquet và metadata được ghi vào thư mục catalog.
Thực hiện lệnh tạo bảng và chèn dữ liệu mẫu:
duckdb -c "CREATE ICEBERG TABLE my_catalog.public.sales (id INTEGER, amount DOUBLE, region VARCHAR) AS SELECT 1, 150.50, 'North' UNION ALL SELECT 2, 200.00, 'South';"
Lưu ý:
- my_catalog: Tên catalog đã đăng ký ở phần trên.
- public: Namespace (schema) mặc định của Iceberg.
- sales: Tên bảng.
Verify kết quả: Kiểm tra thư mục catalog trên hệ thống file:
ls -R /var/data/iceberg_catalog/public/sales
Bạn phải thấy các thư mục con như "data", "metadata" và các file .json hoặc .parquet bên trong.
Truy vấn trực tiếp bảng Iceberg
Giờ là lúc quan trọng nhất: truy vấn dữ liệu phân tán mà không cần load toàn bộ vào RAM. DuckDB sẽ chỉ đọc metadata để biết vị trí các file Parquet cần scan.
Tại sao lại hiệu quả: Khi dùng lệnh SELECT trên bảng Iceberg, DuckDB không copy data. Nó đọc metadata file của Iceberg, xác định các file Parquet chứa dữ liệu cần thiết (predicate pushdown), và chỉ scan các file đó.
Thực hiện truy vấn tổng hợp (aggregation) trên toàn bộ dữ liệu:
duckdb -c "SELECT region, SUM(amount) as total_revenue FROM my_catalog.public.sales GROUP BY region;"
Thực hiện truy vấn với bộ lọc (filter) để kiểm tra tính năng predicate pushdown:
duckdb -c "SELECT * FROM my_catalog.public.sales WHERE region = 'North';"
Verify kết quả:
- Lệnh đầu tiên phải trả về 2 dòng với tổng doanh thu của North và South.
- Lệnh thứ hai chỉ trả về 1 dòng (id=1).
- Không có lỗi "Table not found" hay "Catalog not found".
So sánh hiệu năng: DuckDB thuần vs DuckDB + Iceberg
Chuẩn bị kịch bản Benchmark
Để chứng minh sức mạnh của Iceberg trong việc quản lý dữ liệu phân tán, chúng ta sẽ so sánh thời gian truy vấn giữa hai cách: (1) Scan toàn bộ file Parquet thông thường và (2) Scan thông qua catalog Iceberg với metadata tối ưu.
Tại sao cần so sánh: Với dữ liệu nhỏ, hiệu năng chênh lệch không đáng kể. Nhưng với dữ liệu lớn (Big Data), Iceberg giúp DuckDB bỏ qua các file không liên quan nhờ metadata partition và statistics, giảm thời gian I/O đáng kể.
Tạo một dataset lớn hơn để test (ví dụ 1 triệu dòng):
duckdb -c "CREATE TABLE temp_large_data AS SELECT i, random() as val, (i % 100) as part FROM range(1000000) t(i);"
Export ra file Parquet thô (không qua Iceberg):
duckdb -c "COPY (SELECT * FROM temp_large_data) TO '/tmp/data_plain.parquet' (FORMAT PARQUET);"
Tạo bảng Iceberg từ cùng dataset đó:
duckdb -c "CREATE ICEBERG TABLE my_catalog.public.large_sales (i INTEGER, val DOUBLE, part INTEGER) AS SELECT * FROM temp_large_data;"
Thực thi Benchmark
Bây giờ, ta chạy cùng một câu lệnh SQL với bộ lọc (filter) trên cả hai bảng và đo thời gian thực thi (timing).
Tại sao dùng filter: Iceberg thực sự tỏa sáng khi bạn lọc theo partition hoặc column đã có index statistics. Nếu quét toàn bộ (SELECT *), hiệu năng có thể tương đương nhau.
Kết quả mong đợi: Truy vấn qua Iceberg (với partitioning tự động hoặc manual) sẽ nhanh hơn hoặc ít nhất là ổn định hơn khi dữ liệu tăng lên, đặc biệt là khi dữ liệu bị phân mảnh.
Lệnh benchmark cho file Parquet thuần:
time duckdb -c "SELECT COUNT(*) FROM '/tmp/data_plain.parquet' WHERE part = 5;"
Lệnh benchmark cho bảng Iceberg:
time duckdb -c "SELECT COUNT(*) FROM my_catalog.public.large_sales WHERE part = 5;"
Giải thích kết quả:
- Lệnh đầu: DuckDB phải scan file Parquet, đọc metadata của file đó, và lọc từng row.
- Lệnh sau: DuckDB đọc Iceberg metadata, biết rằng dữ liệu 'part=5' nằm ở một tập hợp file Parquet cụ thể nào đó, và chỉ scan các file đó. Nếu Iceberg được partition theo 'part', nó sẽ bỏ qua hoàn toàn các file không chứa 'part=5'.
Verify kết quả:
- So sánh thời gian thực thi (real time) của hai lệnh.
- Kiểm tra số lượng dòng trả về: Cả hai phải trả về cùng một số lượng (khoảng 10,000 dòng vì part = i % 100 = 5).
- Nếu thời gian Iceberg nhanh hơn đáng kể (thường là 2-5 lần với dữ liệu lớn), nghĩa là việc kết nối thành công và tối ưu hóa đã hoạt động.
Điều hướng series:
Mục lục: Series: Xây dựng Data Lakehouse Serverless với DuckDB, Apache Iceberg và Ubuntu 24.04
« Phần 3: Nhập môn Apache Iceberg: Kiến trúc và cài đặt thư viện Python
Phần 5: Xây dựng quy trình ETL Serverless: Load, Transform, và Append »