Databricks 2 — Delta Lake chuyên sâu

13 thg 7, 2026 2 lượt xem
#data-engineering
#databricks
#acid
#delta-lake
#time-travel

Delta Lake là gì và tại sao nó quan trọng

Delta Lake là table format mở (mã nguồn mở, chuẩn giao thức công khai) làm nền cho kiến trúc lakehouse của Databricks. Bài Spark 7 — Delta Lake & Lakehouse đã giới thiệu Delta ở góc độ engine Spark và medallion; bài này đi sâu hơn về cách nó hoạt động bên trong và vận hành trong production — thứ bạn thực sự cần khi bảng giao dịch ngân hàng đã lên tới hàng tỷ dòng.

Về bản chất, một bảng Delta chỉ là một thư mục Parquet cộng thêm một transaction log. Nó không phải database server, không có process riêng — chỉ là file trên object storage (S3, ADLS, GCS). Toàn bộ "phép màu" ACID nằm ở cách đọc và ghi cái log đó. Hiểu transaction log là hiểu Delta.

/lakehouse/txn_bronze/
├── _delta_log/
│   ├── 00000000000000000000.json        ← commit 0 (CREATE)
│   ├── 00000000000000000001.json        ← commit 1 (INSERT)
│   ├── 00000000000000000002.json        ← commit 2 (MERGE)
│   ├── ...
│   ├── 00000000000000000010.checkpoint.parquet
│   └── _last_checkpoint
├── part-00000-....snappy.parquet
├── part-00001-....snappy.parquet
└── ...

Transaction log (_delta_log) — trái tim của Delta

Mỗi lần ghi thành công tạo ra một file commit JSON đánh số tuần tự tăng dần: ...0000.json, ...0001.json,... Mỗi commit là một tập action mô tả thay đổi ở mức metadata, không phải dữ liệu:

  • add — thêm một file Parquet (kèm path, size, số dòng, thống kê min/max/null-count từng cột).
  • remove — đánh dấu một file không còn thuộc bảng (tombstone), file vẫn nằm trên storage.
  • metaData — schema, partition columns, cấu hình bảng.
  • protocol — phiên bản reader/writer tối thiểu.
  • commitInfo — audit: user, thời điểm, loại thao tác (MERGE/DELETE/OPTIMIZE...).

Trạng thái bảng tại version N = áp lần lượt tất cả action từ commit 0 đến N. Danh sách file hiện hành = tập add trừ đi tập remove. Vì các file dữ liệu bất biến, một reader chỉ cần đọc danh sách file của một version cụ thể là có ngay snapshot nhất quán — dù có ai đang ghi song song.

ACID trên object storage

Object storage vốn không có transaction, không có khoá bảng. Delta đạt ACID nhờ hai cơ chế:

  1. Atomic commit — một thao tác chỉ "thấy được" khi file N.json xuất hiện nguyên vẹn. Việc tạo file này là thao tác đặt tên nguyên tử (create-if-not-absent). Nếu quá trình ghi chết giữa chừng, các file Parquet mồ côi vẫn trên storage nhưng không được reference trong log nào → coi như không tồn tại. Không có "nửa vời".
  2. Optimistic concurrency control (OCC) — nhiều writer cùng nhắm version tiếp theo N. Chỉ một tạo được N.json; các writer thua sẽ đọc lại log, kiểm tra xem thay đổi của mình có xung đột (đụng cùng file) không, nếu không thì thử commit lại ở N+1. Đây là lý do append song song rất khoẻ, còn nhiều MERGE cùng vùng dữ liệu thì dễ conflict. Khi gặp ConcurrentModificationException, cách xử lý thực chiến là: tách writer theo partition/khoá để không đụng cùng file, hoặc gom các MERGE về một luồng ghi tuần tự cho từng bảng nóng.

Checkpoint — không phải replay từ đầu

Nếu bảng có 500.000 commit, đọc lại 500.000 JSON là thảm hoạ. Cứ mỗi 10 commit (mặc định), Delta ghi một checkpoint Parquet gộp toàn bộ trạng thái tính đến điểm đó. File _last_checkpoint trỏ tới checkpoint mới nhất. Reader chỉ cần: đọc checkpoint gần nhất + vài JSON commit sau nó → dựng snapshot rất nhanh. Hệ quả vận hành: log cũ được dọn theo delta.logRetentionDuration (mặc định 30 ngày), độc lập với retention dữ liệu của VACUUM — hai mốc này giới hạn khả năng time travel theo hai chiều khác nhau (log quá cũ thì mất commitInfo, file dữ liệu bị VACUUM thì mất nội dung).

Time travel — đọc quá khứ của bảng

Vì mọi version đều ghi lại trong log và file cũ chưa bị xoá, ta query bảng ở bất kỳ thời điểm nào bằng VERSION AS OF hoặc TIMESTAMP AS OF.

-- Delta SQL trên Databricks (minh hoạ, KHÔNG chạy trong sandbox)
SELECT * FROM txn_silver VERSION AS OF 42;
SELECT * FROM txn_silver TIMESTAMP AS OF '2026-06-30 09:00:00';

-- So sánh số dư tổng giữa hai version để đối soát
SELECT (SELECT SUM(amount) FROM txn_silver VERSION AS OF 42)
     - (SELECT SUM(amount) FROM txn_silver VERSION AS OF 41) AS delta_amount;

Ứng dụng thực tế: audit (bảng lúc 9h sáng khác gì lúc 17h?), khôi phục sau khi lỡ chạy MERGE sai (RESTORE TABLE ... TO VERSION AS OF 41), so sánh để debug pipeline. DESCRIBE HISTORY txn_silver liệt kê từng version: ai ghi, lúc nào, thao tác gì, số dòng bị ảnh hưởng — nhật ký kiểm toán sẵn có, cực quý cho ngân hàng.

Schema enforcement & evolution

Enforcement (bắt buộc mặc định): khi ghi, Delta so schema DataFrame với schema bảng. Ghi thừa cột lạ, sai kiểu (một job đẩy amount là string trong khi bảng là decimal) → fail ngay lúc ghi, không âm thầm làm hỏng dữ liệu. Đây là khác biệt căn bản so với Parquet thô.

Evolution (có kiểm soát): khi thực sự cần thêm cột, bật mergeSchema:

# PySpark — minh hoạ, không chạy trong sandbox
(df.write.format("delta")
   .mode("append")
   .option("mergeSchema", "true")
   .saveAsTable("txn_bronze"))

MERGE cũng hỗ trợ withSchemaEvolution(). Nguyên tắc vận hành: enforcement là mặc định, evolution là quyết định có chủ đích — không để pipeline tự ý phình schema.

MERGE / UPDATE / DELETE — upsert, CDC và xoá theo yêu cầu

Đây là năng lực mà Parquet thô không có. MERGE là dao đa năng cho upsert và áp CDC (Change Data Capture) từ hệ nguồn:

-- Delta MERGE (minh hoạ) — áp CDC từ bảng staging vào bảng silver
MERGE INTO txn_silver t
USING txn_staging s
  ON t.txn_id = s.txn_id
WHEN MATCHED AND s.op = 'D' THEN DELETE
WHEN MATCHED AND s.op = 'U' THEN UPDATE SET *
WHEN NOT MATCHED AND s.op <> 'D' THEN INSERT *;

DELETE theo điều kiện phục vụ xoá dữ liệu theo yêu cầu — cực kỳ quan trọng cho GDPR / Nghị định 13/2023 (NĐ13) của Việt Nam về bảo vệ dữ liệu cá nhân:

-- Xoá dữ liệu khách hàng theo yêu cầu (minh hoạ)
DELETE FROM txn_silver WHERE customer_id = 'CIF00918273';

Lưu ý: DELETE chỉ ghi tombstone và viết lại các file liên quan — dữ liệu vật lý vẫn còn trong file cũ cho tới khi VACUUM dọn (xem dưới). Với yêu cầu xoá tuân thủ pháp lý, phải chạy VACUUM để thực sự loại bỏ khỏi storage.

Change Data Feed — đọc thay đổi mức row

Bật Change Data Feed (CDF) để Delta ghi lại từng dòng thay đổi (insert/update/delete) giữa các version, thay vì buộc pipeline hạ nguồn phải diff toàn bảng:

ALTER TABLE txn_silver SET TBLPROPERTIES (delta.enableChangeDataFeed = true);

-- Đọc mọi thay đổi từ version 41 đến nay
SELECT * FROM table_changes('txn_silver', 41);
-- cột thêm: _change_type (insert/update_preimage/update_postimage/delete), _commit_version, _commit_timestamp

CDF là nền cho incremental pipeline: bảng gold chỉ xử lý phần thay đổi, giảm mạnh chi phí so với full-refresh. Đây là mắt xích tự nhiên nối với các model incremental trong dbt. Không có CDF, bảng gold buộc phải full-refresh hoặc tự dựng cơ chế watermark thủ công; với CDF, Delta trả sẵn đúng những dòng đã đổi kèm loại thay đổi. Lưu ý CDF chỉ ghi lại từ sau khi bật, và bản thân dữ liệu CDF cũng tuân theo retention của VACUUM.

Tối ưu vận hành: OPTIMIZE, Z-Order, liquid clustering, VACUUM

Ghi streaming/CDC liên tục sinh ra small files problem — hàng nghìn file nhỏ vài KB khiến đọc chậm (metadata overhead) và tốn tiền list/GET trên object storage (xem tiering & storage cost).

LệnhLàm gìKhi nào
OPTIMIZENén nhiều file nhỏ thành file lớn (~1GB)Định kỳ sau nhiều lần ghi nhỏ
OPTIMIZE ... ZORDER BYĐồng vị (co-locate) dữ liệu theo cột lọcBảng lớn, hay lọc theo cột nhất định
Liquid clusteringThay Z-Order/partition, tự động, không cần chọn partition keyBảng mới, muốn cluster linh hoạt
VACUUMXoá file cũ ngoài retention (mặc định 7 ngày)Dọn storage, hoàn tất xoá GDPR
-- Nén file + đồng vị theo cột hay lọc (minh hoạ)
OPTIMIZE txn_silver ZORDER BY (customer_id, txn_date);

-- Liquid clustering (cách mới, thay cho partition + Z-Order)
ALTER TABLE txn_silver CLUSTER BY (customer_id, txn_date);

-- Dọn file cũ hơn 168 giờ; hạ retention sẽ CẢNH BÁO an toàn
VACUUM txn_silver RETAIN 168 HOURS;

Data skipping: nhờ thống kê min/max lưu trong add action, Delta biết file nào không thể chứa dòng thoả điều kiện WHEREbỏ qua không đọc. ZORDER/liquid clustering làm cho các giá trị gần nhau nằm chung file → khoảng min/max hẹp → skip được nhiều file hơn. Đây là lý do đồng vị dữ liệu tăng tốc query mạnh mà không cần index truyền thống.

Lưu ý VACUUM & time travel: VACUUM xoá file vật lý mà time travel dựa vào. Sau khi VACUUM với retention 7 ngày, bạn không còn time travel về trước mốc đó. Đặt retention khớp nhu cầu audit là quyết định vận hành quan trọng.

Constraint — CHECK và NOT NULL

Delta cho phép ràng buộc chất lượng ngay trên bảng, ghi vi phạm sẽ bị chặn:

ALTER TABLE txn_silver ADD CONSTRAINT amount_valid CHECK (amount <> 0);
ALTER TABLE txn_silver ALTER COLUMN customer_id SET NOT NULL;

Đây là lớp phòng thủ chất lượng dữ liệu ngay tại storage, bổ sung cho kiểm thử ở tầng transform.

Delta vs Iceberg vs Hudi, và UniForm

Ba table format mở phổ biến, cùng giải bài toán ACID trên lake nhưng khác chi tiết:

  • Delta Lake — gốc từ Databricks, log JSON + checkpoint; mạnh nhất trong hệ sinh thái Databricks/Spark.
  • Apache Iceberg — chuẩn mở "trung lập engine" nhất, được nhiều engine (Spark, Trino, Flink, Snowflake) hỗ trợ tốt; metadata dạng manifest/snapshot. Xem Trino truy vấn lakehouse.
  • Apache Hudi — nổi bật cho ingest/upsert streaming độ trễ thấp (copy-on-write & merge-on-read).

UniForm (Universal Format) của Delta tạo đồng thời metadata Iceberg (và Hudi) bên cạnh log Delta, để engine bên ngoài đọc bảng Delta như thể là Iceberg — không cần copy dữ liệu. Giảm khoá nhà cung cấp và cho phép Trino/Snowflake đọc cùng một bảng.

Use case thực tế

Bối cảnh: NCB xây bảng giao dịch lakehouse txn_silver trên ADLS, ingest CDC từ core banking, ~1,8 tỷ dòng, tăng ~4 triệu dòng/ngày qua micro-batch mỗi 5 phút.

  • ACID + streaming: mỗi micro-batch là một commit nguyên tử. Report đối soát cuối ngày chạy song song luôn thấy snapshot nhất quán, không bao giờ đọc trúng batch dở.
  • Small files: 288 micro-batch/ngày × nhiều partition sinh ~15.000 file nhỏ/ngày. Job OPTIMIZE ZORDER BY (customer_id, txn_date) chạy 1h sáng gộp còn ~600 file; thời gian quét bảng cho report tra cứu theo CIF giảm từ ~90s xuống ~11s nhờ data skipping.
  • Đối soát/audit bằng time travel: phòng Kế toán phát hiện chênh 2,3 triệu VND ở báo cáo cuối ngày. Dùng DESCRIBE HISTORY xác định version lúc 17:00, TIMESTAMP AS OF để dựng lại số dư tại thời điểm chốt sổ, khoanh vùng đúng batch bị double-count ở 16:42. Không cần khôi phục backup.
  • Xoá theo NĐ13: khách hàng CIF00918273 yêu cầu xoá. DELETE FROM txn_silver WHERE customer_id = 'CIF00918273' (ghi tombstone), sau đó VACUUM RETAIN 0 HOURS (đã bật spark.databricks.delta.retentionDurationCheck.enabled=false có kiểm soát) để loại file vật lý → hoàn tất xoá tuân thủ, lưu bằng chứng qua commitInfo.
  • CDF cho gold: bảng gold tổng hợp theo ngày chỉ đọc table_changes('txn_silver', last_version) thay vì quét lại 1,8 tỷ dòng — chi phí compute giảm hơn 90%.

Ghi nhớ

  • Bảng Delta = thư mục Parquet + transaction log _delta_log (chuỗi commit JSON + checkpoint Parquet). Hiểu log là hiểu Delta.
  • ACID trên object storage nhờ atomic commit (create-if-not-absent) + OCC; file mồ côi không được reference coi như không tồn tại.
  • Time travel (VERSION/TIMESTAMP AS OF) + DESCRIBE HISTORY cho audit, khôi phục, đối soát — nhật ký kiểm toán có sẵn.
  • Schema enforcement là mặc định, evolution (mergeSchema) là quyết định có chủ đích.
  • MERGE/UPDATE/DELETE phục vụ upsert, CDC và xoá theo NĐ13/GDPR; DELETE chỉ tombstone — phải VACUUM mới xoá vật lý.
  • Tối ưu vận hành: OPTIMIZE (nén small files), Z-Order/liquid clustering (đồng vị để data skipping), VACUUM (dọn ngoài retention — nhưng phá time travel trước mốc).
  • Change Data Feed cho phép pipeline hạ nguồn đọc thay đổi mức row → incremental, tiết kiệm mạnh.
  • Constraint CHECK/NOT NULL chặn dữ liệu bẩn tại storage.
  • Delta/Iceberg/Hudi cùng giải ACID-on-lake; UniForm cho đọc bảng Delta như Iceberg, giảm khoá nhà cung cấp.

Bài viết liên quan

CSV/JSON/Parquet/Avro, lưu trữ theo hàng vs cột, nén, và OLTP vs OLAP.

13 thg 7, 2026 8

Data Engineering là gì, vai trò trong vòng đời dữ liệu, và bức tranh hệ sinh thái công cụ.

13 thg 7, 2026 5

Vì sao xử lý phân tán, mô hình Spark (RDD/DataFrame), lazy evaluation, shuffle và tối ưu.

13 thg 7, 2026 5

Đảm bảo chất lượng & minh bạch dữ liệu bằng dbt: data tests dựng sẵn (unique, not_null, relationships, accepted_values), singular test, unit test, và tài liệu tự sinh kèm lineage graph.

13 thg 7, 2026 5