So sánh mô hình Data Lake truyền thống và Data Lakehouse
Trước khi đi sâu vào Iceberg hay Delta, bạn cần hiểu rõ sự khác biệt về kiến trúc giữa Data Lake cũ và Lakehouse mới để chọn đúng công nghệ.
Data Lake truyền thống lưu trữ dữ liệu thô (raw data) dưới dạng file (Parquet, Avro) trên object storage (S3, GCS) mà không có schema enforcement hay transaction management. Điều này dẫn đến "Data Swamp" nơi dữ liệu mất chất lượng và khó truy vấn.
Data Lakehouse kết hợp tính linh hoạt của Data Lake với tính nhất quán (ACID) của Data Warehouse. Nó áp dụng một lớp Table Format (Iceberg/Delta/Hudi) lên trên file storage để quản lý metadata, versioning và schema evolution.
Thao tác: Kiểm tra cấu trúc file trên Object Storage
Chúng ta sẽ mô phỏng cấu trúc file của Data Lake truyền thống so với Data Lakehouse bằng cách tạo thư mục và file trên hệ thống local để bạn hình dung sự khác biệt.
Tạo thư mục Data Lake truyền thống với file Parquet rời rạc không có metadata file.
mkdir -p /var/data/data_lake/traditional/users
echo '{"id": 1, "name": "Alice", "created_at": "2023-10-01"}' > /var/data/data_lake/traditional/users/part-00000.parquet
echo '{"id": 2, "name": "Bob", "created_at": "2023-10-02"}' > /var/data/data_lake/traditional/users/part-00001.parquet
ls -R /var/data/data_lake/traditional/
Kết quả mong đợi: Bạn chỉ thấy các file .parquet nằm chung trong thư mục, không có file metadata nào (như manifest list, snapshot log). Nếu một process ghi đè file này, dữ liệu sẽ mất hoặc bị lỗi schema.
Tạo thư mục mô phỏng Data Lakehouse với cấu trúc metadata riêng biệt (simplified view).
mkdir -p /var/data/lakehouse/iceberg/users/data
mkdir -p /var/data/lakehouse/iceberg/users/metadata
echo '{"id": 1, "name": "Alice", "created_at": "2023-10-01"}' > /var/data/lakehouse/iceberg/users/data/v1-000-000-000.parquet
echo '{"version": 1, "schema_id": 0, "snapshot_id": 123456789}' > /var/data/lakehouse/iceberg/users/metadata/snap-123456789-v1.avro
echo '{"version": 2, "schema_id": 0, "snapshot_id": 987654321}' > /var/data/lakehouse/iceberg/users/metadata/snap-987654321-v1.avro
ls -R /var/data/lakehouse/iceberg/users/
Kết quả mong đợi: Bạn thấy rõ sự tách biệt giữa thư mục `data` (lưu file Parquet) và `metadata` (lưu thông tin snapshot, schema). Đây là cốt lõi của Iceberg/Delta giúp hỗ trợ ACID transactions và Time Travel.
Verify kết quả
So sánh hai thư mục bằng lệnh `tree` để thấy rõ sự khác biệt về độ phức tạp và cấu trúc quản lý.
tree /var/data/data_lake/traditional/
tree /var/data/lakehouse/iceberg/users/
Kết quả: Thư mục traditional chỉ có 1 lớp dữ liệu phẳng. Thư mục lakehouse có cấu trúc phân tầng với metadata riêng biệt, cho phép hệ thống xác định phiên bản dữ liệu (snapshot) tại một thời điểm cụ thể.
Tổng quan về Apache Iceberg: Cấu trúc bảng và Snapshot Isolation
Apache Iceberg được thiết kế để giải quyết bài toán quản lý dữ liệu ở quy mô lớn với tính nhất quán cao. Điểm mạnh của Iceberg là tách biệt hoàn toàn giữa dữ liệu (data files) và metadata (table metadata).
Cơ chế hoạt động: Manifest Files và Snapshots
Khi bạn ghi dữ liệu vào bảng Iceberg, hệ thống không cập nhật trực tiếp file Parquet. Thay vào đó, nó tạo ra một file Parquet mới và cập nhật file `manifest` (liệt kê các file Parquet). Sau đó, tạo một `snapshot` mới trỏ đến manifest list đó.
Điều này tạo ra "Snapshot Isolation": Các query đang chạy vẫn đọc snapshot cũ, trong khi writer đang tạo snapshot mới. Không có conflict, không có lock blocking.
Thực hành: Cấu trúc metadata của Iceberg
Chúng ta sẽ tạo một bảng Iceberg giả lập bằng Spark SQL (chạy trên local hoặc container đã setup ở Phần 1) để quan sát cấu trúc file thực tế được tạo ra.
Giả sử bạn đã có Spark shell chạy. Tạo bảng Iceberg và ghi dữ liệu vào.
spark-sql -e "
CREATE TABLE demo_iceberg (
id INT,
name STRING,
created_at TIMESTAMP
)
USING iceberg
LOCATION '/var/data/lakehouse/iceberg/demo_iceberg';
INSERT INTO demo_iceberg VALUES (1, 'Alice', current_timestamp());
INSERT INTO demo_iceberg VALUES (2, 'Bob', current_timestamp());
"
Kết quả mong đợi: Spark hoàn thành lệnh INSERT. Dữ liệu đã được ghi vào storage.
Khám phá cấu trúc file được tạo ra để hiểu cách Iceberg quản lý snapshot.
find /var/data/lakehouse/iceberg/demo_iceberg -type f | sort
Kết quả mong đợi: Bạn sẽ thấy các file có tên như `v1-00000-000.parquet` (data), `snap--v1.avro` (manifest list), và `metadata/v.json` (table metadata chính).
File `metadata/v1.json` chứa thông tin quan trọng: `current-snapshot-id`, `schema`, `partition-spec`. Đây là "chìa khóa" để truy vấn bảng.
Verify Snapshot Isolation
Thực hiện cập nhật dữ liệu (Upsert) để tạo snapshot mới, sau đó kiểm tra xem snapshot cũ có còn tồn tại không.
spark-sql -e "
UPDATE demo_iceberg SET name = 'Alice Updated' WHERE id = 1;
"
find /var/data/lakehouse/iceberg/demo_iceberg/metadata -name "snap-*.avro" | wc -l
Kết quả mong đợi: Số lượng file snapshot tăng lên (từ 1 lên 2). Snapshot cũ vẫn nằm yên trong thư mục metadata, đảm bảo các reader đang chạy trước đó vẫn đọc được dữ liệu cũ.
Tổng quan về Delta Lake: ACID Transactions và Time Travel
Delta Lake là open source engine (ban đầu do Databricks phát triển) cung cấp lớp transaction ACID cho Data Lake. Delta lưu trữ metadata dưới dạng log file (_delta_log) ở cùng thư mục với dữ liệu, khác với Iceberg tách biệt metadata.
Cơ chế hoạt động: Delta Log và Versioning
Mọi thay đổi (Write, Update, Delete) trên bảng Delta đều được ghi vào một file JSON trong thư mục `_delta_log`. Mỗi lần commit tạo ra một phiên bản (version) mới. File `_commit_.json` mô tả các thay đổi của phiên bản đó.
Time Travel trong Delta dựa vào version number. Bạn có thể query dữ liệu ở bất kỳ version nào trong quá khứ bằng cú pháp `VERSION AS OF`.
Thực hành: Tạo bảng Delta và thực hiện Time Travel
Chạy Spark để tạo bảng Delta và ghi dữ liệu ban đầu.
spark-sql -e "
CREATE TABLE demo_delta (
id INT,
name STRING,
status STRING
)
USING delta
LOCATION '/var/data/lakehouse/delta/demo_delta';
INSERT INTO demo_delta VALUES (1, 'Alice', 'active');
INSERT INTO demo_delta VALUES (2, 'Bob', 'active');
"
Kết quả mong đợi: Bảng được tạo. Dữ liệu được ghi vào.
Kiểm tra cấu trúc thư mục để thấy `_delta_log` nằm cùng cấp với data files.
ls -la /var/data/lakehouse/delta/demo_delta/
Kết quả mong đợi: Bạn thấy thư mục `_delta_log` và các file `.parquet` nằm cùng một thư mục gốc. Trong `_delta_log` có file `00000000000000000000.json` (version 0).
Thực hiện Update và Time Travel
Thực hiện cập nhật trạng thái và rollback lại version cũ để chứng minh Time Travel.
spark-sql -e "
UPDATE demo_delta SET status = 'inactive' WHERE id = 1;
SELECT * FROM demo_delta;
"
Kết quả mong đợi: Dòng Alice hiện có status là 'inactive'. Version log tăng lên version 1.
Query lại bảng ở version 0 để thấy dữ liệu gốc (Time Travel).
spark-sql -e "
SELECT * FROM demo_delta VERSION AS OF 0;
"
Kết quả mong đợi: Dòng Alice hiển thị lại status là 'active' như ban đầu. Điều này chứng minh Delta Log lưu giữ lịch sử đầy đủ cho phép rollback.
Verify Delta Log
Xem trực tiếp nội dung của file log để hiểu cấu trúc ACID transaction.
cat /var/data/lakehouse/delta/demo_delta/_delta_log/00000000000000000001.json | python3 -m json.tool
Kết quả mong đợi: Bạn thấy các trường `txn`, `add` (file mới), `remove` (file cũ) và `metaData`. Đây là bản ghi transaction đảm bảo tính nguyên tử (Atomicity).
Điểm mạnh/yếu và trường hợp sử dụng
Dựa trên cấu trúc đã phân tích ở trên, dưới đây là bảng so sánh kỹ thuật để bạn quyết định chọn Iceberg hay Delta cho hệ thống của mình.
Apache Iceberg
- Điểm mạnh:
- Vendor Neutral: Không phụ thuộc vào nhà cung cấp (AWS, Azure, Databricks). Hỗ trợ tốt bởi Trino, Presto, Flink, Spark, Hive.
- Hidden Partitioning: Cho phép query toàn bộ bảng mà không cần biết partition key, tăng tính linh hoạt.
- Schema Evolution: Xử lý tốt việc thêm cột, thay đổi loại dữ liệu mà không làm vỡ pipeline cũ.
- Điểm yếu:
- Time Travel dựa trên snapshot ID, phức tạp hơn so với version number đơn giản của Delta.
- Community mới hơn Delta một chút, tài liệu cũ có thể ít hơn.
- Trường hợp sử dụng:
- Môi trường Multi-Engine (cần truy vấn bằng cả Spark và Trino/Presto).
- Cloud Agnostic (muốn di chuyển giữa AWS, GCP, Azure dễ dàng).
- Big Data Analytics với yêu cầu schema evolution cao.
Delta Lake
- Điểm mạnh:
- Spark Native: Tích hợp sâu nhất vào Spark, tối ưu hiệu năng cho Spark Workload.
- Optimize & Vacuum: Có các command chuyên biệt (`OPTIMIZE`, `ZORDER`, `VACUUM`) để nén và dọn dẹp file rất mạnh mẽ.
- Time Travel: Cú pháp đơn giản (`VERSION AS OF`, `TIMESTAMP AS OF`) dễ sử dụng.
- Điểm yếu:
- Độ phụ thuộc vào Spark cao hơn. Truy vấn bằng engine khác (như Trino) cần connector và có thể bị giới hạn tính năng.
- Cấu trúc log file có thể trở nên cồng kềnh nếu có quá nhiều transaction nhỏ (micro-batch).
- Trường hợp sử dụng:
- Hệ thống chủ yếu sử dụng Spark làm engine xử lý duy nhất.
- Cần tính năng Upsert (Merge) và Data Skipping (Z-Order) mạnh mẽ.
- Môi trường Databricks hoặc Spark on Kubernetes thuần túy.
Quyết định cuối cùng
Nếu stack công nghệ của bạn là Spark-only và cần hiệu năng tối đa với các tính năng tối ưu hóa file: Chọn Delta Lake.
Nếu stack công nghệ của bạn là Multi-engine (Spark + Trino + Flink) hoặc cần Cloud Agnostic tuyệt đối: Chọn Apache Iceberg.
Trong các phần tiếp theo của series, chúng ta sẽ lần lượt xây dựng pipeline cho cả hai công nghệ này trên Kubernetes để bạn trải nghiệm thực tế sự khác biệt.
Điều hướng series:
Mục lục: Series: Series: Xây dựng nền tảng Data Lakehouse hiện đại với Apache Iceberg, Delta Lake và Spark trên Kubernetes
« Phần 1: Khởi đầu: Thiết lập môi trường Kubernetes và Spark cho Data Lakehouse
Phần 3: Xây dựng bảng Iceberg đầu tiên và thao tác cơ bản với Spark »