Trino 5 — Truy vấn Lakehouse (Iceberg/Delta)

13 thg 7, 2026 2 lượt xem
#sql
#lakehouse
#delta-lake
#iceberg
#trino

Trino 5 — Truy vấn Lakehouse (Iceberg/Delta)

Bốn bài trước dựng nền về Trino như một query engine phân tán không sở hữu dữ liệu, kiến trúc coordinator/worker, connectorsSQL liên hợp. Bài này zoom vào vai trò quan trọng nhất của Trino trong kiến trúc dữ liệu hiện đại: làm query engine cho LAKEHOUSE — mô hình đã được giới thiệu trong Data Engineering — Lakehouse.

Lakehouse = data lake rẻ và mở (file cột Parquet/ORC trên object storage) cộng khả năng giao dịch của warehouse (ACID, schema, time travel). Mảnh ghép làm nên phép cộng đó là table format hiện đại: Apache IcebergDelta Lake. Trino đọc/ghi cả hai qua connector chuyên dụng, biến kho file thô thành một "warehouse trên data lake" mà bạn truy vấn bằng SQL chuẩn.

Vấn đề của Hive table cũ

Thế hệ đầu của "SQL trên data lake" là Hive table: một bảng chỉ là một thư mục chứa file (Parquet/ORC), cộng metadata tối thiểu trong Hive Metastore (schema + danh sách partition = danh sách thư mục con). Đơn giản, nhưng vỡ trận khi dữ liệu lớn và cần độ tin cậy:

  • Không có ACID. Bảng = thư mục; ghi = đổ file mới vào thư mục. Reader đang quét mà writer đổ file vào → đọc phải dữ liệu nửa vời (dirty read). Không có transaction, không rollback. Sửa/xoá vài dòng gần như bất khả (phải viết lại cả partition).
  • List file chậm và tốn kém. Để biết bảng gồm những file nào, engine phải LIST thư mục trên object storage. Bảng lớn có hàng trăm nghìn file → hàng nghìn lời gọi API LIST chỉ để lập kế hoạch, chậm và tốn tiền (S3 tính phí mỗi request). "Small files problem" làm mọi thứ tệ hơn.
  • Đổi schema khó. Thêm/đổi tên/xoá cột dựa vào vị trí cột trong file (position-based) → dễ lệch, dễ hỏng dữ liệu cũ. Không có lịch sử schema.
  • Partition cứng nhắc. Partition là đường dẫn thư mục (/dt=2026-06-30/); người viết truy vấn phải biết và tự lọc đúng cột partition thì mới prune được. Muốn đổi cách chia partition = viết lại toàn bộ bảng.
  • Không time travel, không audit. Ghi đè là mất; không có "phiên bản trước".

Table format hiện đại sinh ra để chữa đúng những điểm này.

Table format hiện đại giải quyết thế nào

Ý tưởng chung: tách "bảng gồm những file nào" ra khỏi thư mục, đưa vào một lớp metadata có quản lý. Engine không LIST thư mục nữa mà đọc metadata để biết chính xác tập file thuộc một phiên bản (snapshot) của bảng. Từ đó có được atomic commit, time travel, schema/partition evolution.

Apache Iceberg

Iceberg dùng metadata phân tầng:

  • Snapshot: mỗi lần ghi tạo một snapshot mới = một phiên bản bất biến của bảng, có snapshot_id và timestamp. Commit là atomic: đổi con trỏ trong catalog sang metadata mới → reader hoặc thấy snapshot cũ, hoặc thấy snapshot mới, không bao giờ thấy nửa vời (ACID).
  • Manifest phân tầng: metadata.jsonmanifest listmanifest filedata file. Nhờ đó engine biết chính xác file nào thuộc snapshot nào mà không LIST thư mục, kèm thống kê (min/max, null count) từng cột để prune file trước khi đọc.
  • Hidden partitioning: bạn khai PARTITIONED BY (day(created_at)), Iceberg tự tính partition và tự prune khi bạn lọc WHERE created_at >= .... Người viết SQL không cần biết cột partition ẩn — khác hẳn Hive.
  • Partition evolution: đổi cách chia partition (VD từ month sang day) không cần viết lại dữ liệu cũ; dữ liệu mới theo spec mới, dữ liệu cũ vẫn đọc được.
  • Schema evolution an toàn: thêm/xoá/đổi tên cột theo ID cột (không theo vị trí) → không hỏng file cũ.
  • Time travel & ACID như trên.

Delta Lake

Delta dùng một transaction log dạng thư mục _delta_log/: mỗi commit là một file JSON được đánh số tăng dần (00000000000000000000.json, ...001.json, ...), ghi lại các thao tác add/remove file. Định kỳ log được nén thành checkpoint (Parquet) để đọc trạng thái nhanh. Bản chất cũng là "danh sách file có phiên bản + atomic commit + thống kê để prune", chỉ khác cách tổ chức metadata so với Iceberg. Delta cũng có time travel, ACID, schema evolution.

Trino đọc/ghi cả hai qua connector: catalog Iceberg (connector.name=iceberg) và catalog Delta (connector.name=delta_lake). Với người viết SQL, khác biệt gần như trong suốt — vẫn là catalog.schema.table.

Tính năng khai thác qua Trino

Đây là phần đắt giá: những gì Hive không làm được thì Trino + Iceberg/Delta làm được bằng SQL.

Time travel — truy vấn snapshot cũ

Iceberg cho phép đọc bảng ở một phiên bản trong quá khứ qua FOR VERSION AS OF (theo snapshot_id) hoặc FOR TIMESTAMP AS OF (theo thời điểm):

-- Trino/Iceberg (minh hoạ, KHÔNG chạy trên sandbox)
-- Đọc bảng như tại một snapshot cụ thể
SELECT * FROM lake.core.transactions
FOR VERSION AS OF 8213409928734652341;

-- Đọc bảng như tại một thời điểm (state cuối ngày 30/06)
SELECT count(*), sum(amount) FROM lake.core.transactions
FOR TIMESTAMP AS OF TIMESTAMP '2026-06-30 23:59:59 Asia/Ho_Chi_Minh';

Ứng dụng ngân hàng: tái lập báo cáo đúng như đã chốt cuối kỳ (dù bảng sau đó đã thay đổi), so sánh trước/sau một lần load, audit/điều tra một giá trị đã thay đổi lúc nào.

Xem lịch sử, snapshot, manifest

Iceberg lộ ra các metadata table ngay trong SQL (dùng hậu tố $):

-- Trino/Iceberg (minh hoạ) — xem toàn bộ snapshot của bảng
SELECT committed_at, snapshot_id, operation, summary
FROM lake.core."transactions$snapshots"
ORDER BY committed_at DESC;

-- Xem lịch sử (ai/khi nào tạo snapshot, có phải ancestor không)
SELECT * FROM lake.core."transactions$history";

-- Xem file/partition đang thuộc bảng và thống kê
SELECT * FROM lake.core."transactions$files";
SELECT * FROM lake.core."transactions$partitions";

Đây là "trang quản trị bảng" bằng SQL: biết bảng có bao nhiêu file, phân bố partition, snapshot nào đang là hiện tại — rất tiện để chẩn đoán small-files hay tìm snapshot cần rollback.

MERGE / UPDATE / DELETE — ACID trên lake

Điều Hive không làm nổi: sửa/xoá dòng lẻ với đảm bảo giao dịch. Trino hỗ trợ UPDATE, DELETE, và đặc biệt MERGE — nền tảng cho upsertCDC (Change Data Capture):

-- Trino/Iceberg (minh hoạ) — MERGE để upsert từ bảng staging CDC
MERGE INTO lake.core.customers AS t
USING staging.cdc.customers_delta AS s
   ON t.id = s.id
WHEN MATCHED AND s.op = 'DELETE' THEN DELETE
WHEN MATCHED AND s.op = 'UPDATE' THEN
     UPDATE SET full_name = s.full_name, segment = s.segment, updated_at = s.ts
WHEN NOT MATCHED AND s.op <> 'DELETE' THEN
     INSERT (id, full_name, segment, updated_at)
     VALUES (s.id, s.full_name, s.segment, s.ts);

-- Xoá theo điều kiện (GDPR / yêu cầu xoá dữ liệu khách)
DELETE FROM lake.core.transactions WHERE customer_id = 100237;

Iceberg/Delta triển khai xoá/sửa hiệu quả bằng merge-on-read (ghi delete file đánh dấu dòng bị xoá, gộp khi đọc) hoặc copy-on-write (viết lại data file). Nhờ đó upsert và xử lý CDC vào data lake trở nên khả thi bằng SQL — trước đây phải dùng Spark job phức tạp.

Bảo trì bảng — sống còn cho hiệu năng & chi phí

Bảng lakehouse cần bảo trì định kỳ, nếu không sẽ phình file nhỏ và giữ quá nhiều snapshot cũ (tốn dung lượng object storage). Trino cung cấp các thủ tục qua ALTER TABLE ... EXECUTE:

-- Trino/Iceberg (minh hoạ) — nén file nhỏ thành file lớn (chống small-files)
ALTER TABLE lake.core.transactions EXECUTE optimize;
-- Chỉ optimize partition gần đây cho rẻ
ALTER TABLE lake.core.transactions EXECUTE optimize
      WHERE created_at >= DATE '2026-06-01';

-- Xoá snapshot quá 7 ngày (giải phóng metadata + data file mồ côi của snapshot cũ)
ALTER TABLE lake.core.transactions EXECUTE expire_snapshots(retention_threshold => '7d');

-- Dọn file mồ côi (file không còn được snapshot nào tham chiếu)
ALTER TABLE lake.core.transactions EXECUTE remove_orphan_files(retention_threshold => '7d');

Ba việc cốt lõi cần lịch chạy đều đặn:

Thủ tụcLàm gìVì sao cần
optimizeNén nhiều file nhỏ thành ít file lớn (compaction), sort lại nếu cóÍt file → prune nhanh, đọc ít request; chống small-files
expire_snapshotsXoá snapshot cũ quá ngưỡng giữTime travel không cần vô hạn; giữ nhiều = tốn dung lượng
remove_orphan_filesXoá file không còn snapshot nào tham chiếuDọn rác vật lý sau khi commit thất bại/ghi lại

Đây là điểm chạm trực tiếp tới chi phí storage — xem thêm cách quản trị dung lượng/chi phí trên lake trong Snowflake — Quản lý chi phí (khái niệm giữ lịch sử vs chi phí lưu trữ tương đồng). Lưu ý an toàn: expire_snapshots/remove_orphan_files phá huỷ khả năng time travel về trước ngưỡng — cân nhắc kỹ retention theo yêu cầu audit của ngân hàng.

Partition & sorting

Khi tạo bảng, chọn partition theo cột lọc phổ biến (thường theo ngày) và sort order theo cột hay lọc điểm để prune ở cấp file:

-- Trino/Iceberg (minh hoạ) — tạo bảng có hidden partitioning + sort
CREATE TABLE lake.core.transactions (
    id BIGINT, account_id BIGINT, customer_id BIGINT,
    amount DECIMAL(18,2), kind VARCHAR, created_at TIMESTAMP(6)
)
WITH (
    format = 'PARQUET',
    partitioning = ARRAY['day(created_at)'],
    sorted_by = ARRAY['account_id']
);

Partition đúng giúp WHERE created_at >= ... chỉ quét vài ngày; thống kê + sort giúp bỏ qua file không chứa account_id cần tìm.

Vì sao Trino + Iceberg/Delta là "warehouse trên data lake"

Ghép lại, bạn có đúng trải nghiệm của một data warehouse nhưng trên nền lake mở:

  • Mở, không khoá nhà cung cấp (no vendor lock-in). Dữ liệu là Parquet + metadata Iceberg/Delta theo chuẩn mở, nằm trên object storage của bạn. Nhiều engine cùng đọc/ghi một bảng: Trino cho truy vấn tương tác, Spark cho batch nặng, Flink cho streaming — không ai độc quyền định dạng. Muốn đổi engine không phải migrate dữ liệu.
  • Rẻ. Storage là object storage (rẻ nhất), compute (Trino) tách rời và co giãn độc lập — tắt cụm là ngừng trả tiền compute; dữ liệu vẫn nằm nguyên. Không phải trả phí lưu trữ đắt của warehouse độc quyền.
  • Đủ tính năng warehouse: ACID, transaction, time travel, MERGE/upsert, schema evolution, audit — như đã thấy ở trên.

Đó là lý do "lakehouse" thắng thế: giữ cái rẻ và mở của lake, thêm cái tin cậy của warehouse.

So sánh nhanh Iceberg vs Delta vs Hudi

Ba table format phổ biến, cùng giải bài toán ACID/time travel trên lake nhưng khác trọng tâm:

Tiêu chíIcebergDelta LakeHudi
GốcNetflix → ApacheDatabricks → Linux FoundationUber → Apache
MetadataManifest phân tầng + catalogTransaction log _delta_log + checkpointTimeline + metadata table
Điểm mạnhChuẩn mở trung lập, partition/schema evolution, đa engine mạnhHệ sinh thái Databricks/Spark chín, đơn giảnUpsert/incremental & CDC streaming mạnh
Trino hỗ trợĐọc + ghi đầy đủ (connector iceberg)Đọc + ghi (connector delta_lake)Chủ yếu đọc
Xu hướngĐược nhiều nền tảng chọn làm chuẩn trung lậpMở dần (UniForm đọc như Iceberg)Ngách streaming/CDC

Với đội mới xây lakehouse trung lập engine và ưu tiên đọc/ghi từ Trino, Iceberg thường là lựa chọn mặc định; Delta hợp khi đã đầu tư nặng vào Databricks/Spark; Hudi khi nhu cầu upsert streaming rất cao.

Vai trò của catalog / metastore

Table format cần một catalog để biết "bảng X hiện đang trỏ tới metadata file nào" — đây là nơi commit atomic xảy ra (đổi con trỏ). Với Iceberg, Trino kết nối được nhiều loại:

  • REST catalog: chuẩn mở qua HTTP, xu hướng mới — trung lập, dễ chia sẻ giữa nhiều engine, tách catalog khỏi engine. Ví dụ triển khai: Nessie, Polaris, Gravitino, Unity Catalog (chế độ Iceberg REST).
  • Hive Metastore (HMS): cổ điển, tương thích rộng, nhiều tổ chức đã có sẵn.
  • AWS Glue: catalog quản lý trên AWS.

Delta thường dựa vào đường dẫn bảng + _delta_log, hoặc Unity Catalog. Trong một lakehouse chuẩn hoá, REST catalog đang được ưa chuộng vì cho phép một bảng, nhiều engine truy cập nhất quán mà không khoá vào một nhà cung cấp — đúng tinh thần mở của lakehouse.

SQL "▶ Chạy được" — mẫu truy vấn phân tích tổng hợp theo thời gian

Trên Trino/Iceberg, một tác vụ điển hình là tổng hợp giao dịch theo thời gian (thường prune tốt nhờ partition theo ngày). Cú pháp gộp theo mốc thời gian dùng date_truncANSI, giống nhau giữa Trino và PostgreSQL — nên câu dưới đây chạy được nguyên văn trên sandbox PostgreSQL để minh hoạ mẫu (trên Trino, transactions sẽ là bảng Iceberg lake.core.transactions):

-- ▶ Chạy được
SELECT
    date_trunc('day', t.created_at)            AS ngay,
    t.kind,
    COUNT(*)                                   AS so_giao_dich,
    ROUND(SUM(t.amount)::numeric, 2)           AS tong_tien,
    ROUND(AVG(t.amount)::numeric, 2)           AS trung_binh
FROM transactions t
GROUP BY date_trunc('day', t.created_at), t.kind
ORDER BY ngay, t.kind;

Đây đúng là dạng "warehouse query" chạy trên lake: gộp fact lớn theo mốc thời gian, ra bảng tổng hợp cho BI. Trên Iceberg, WHERE created_at >= DATE '...' sẽ được đẩy xuống thành partition pruningfile pruning nhờ thống kê metadata — quét ít file, trả kết quả nhanh.

Use case thực tế

Bối cảnh (NCB): Khối Quản lý rủi ro cần một kho dữ liệu giao dịch mở, giữ đầy đủ lịch sử và truy vết được để phục vụ điều tra AML và audit của thanh tra. Yêu cầu cụ thể: (1) lưu ~2 tỉ giao dịch/tháng rẻ nhất có thể; (2) sửa/xoá được dòng theo yêu cầu pháp lý (GDPR/xoá dữ liệu khách) nhưng có dấu vết ai sửa khi nào; (3) tái lập được báo cáo đúng như đã chốt tại cuối mỗi kỳ.

Cách làm với Trino + Iceberg:

  1. Bảng lake.core.transactionsIceberg, partition ẩn day(created_at), format Parquet, lưu trên object storage nội bộ — chi phí storage chỉ bằng một phần so với warehouse độc quyền.
  2. Load hằng ngày từ CDC bằng MERGE (upsert): mỗi lần load tạo một snapshot mới, atomic — reader đang chạy báo cáo không bị đọc dữ liệu nửa vời.
  3. Yêu cầu xoá dữ liệu một khách: DELETE FROM ... WHERE customer_id = ... — ACID, và snapshot trước vẫn tồn tại trong retention để chứng minh trạng thái trước khi xoá; thanh tra hỏi "trước khi xoá dữ liệu ra sao?" → dùng FOR TIMESTAMP AS OF để tái lập.
  4. Tái lập báo cáo cuối tháng 6: chạy lại đúng câu tổng hợp với FOR TIMESTAMP AS OF TIMESTAMP '2026-06-30 23:59:59 ...' → ra con số y hệt lúc chốt sổ, dù bảng đã có thêm dữ liệu tháng 7.
  5. Bảo trì tự động hằng đêm: optimize nén file nhỏ của partition mới; expire_snapshots(retention_threshold => '90d') giữ 90 ngày time travel theo chính sách audit; remove_orphan_files dọn rác.

Kết quả: một "warehouse trên data lake" mở — chi phí storage thấp, đọc được bằng Trino cho analyst và Spark cho batch, đầy đủ ACID/time travel/audit cho rủi ro và thanh tra, không khoá vào nhà cung cấp nào. Lớp tổng hợp/metric phía trên đóng gói thành OBT như trong OBT & Metrics hiện đại.

Ghi nhớ

  • Hive table cũ = thư mục + file: không ACID, list file chậm/tốn, đổi schema và partition khó, không time travel. Table format hiện đại chữa đúng những điểm này bằng lớp metadata có quản lý.
  • Iceberg: snapshot + manifest phân tầng (metadata.json → manifest list → manifest → data file), hidden partitioning, partition/schema evolution, time travel, ACID. Delta: transaction log _delta_log + checkpoint. Trino đọc/ghi cả hai qua connector.
  • Khai thác qua Trino: time travel (FOR VERSION AS OF / FOR TIMESTAMP AS OF), xem lịch sử/snapshot qua metadata table "tbl$snapshots", MERGE/UPDATE/DELETE cho ACID + upsert/CDC trên lake.
  • Bảo trì bắt buộc: optimize (nén small files), expire_snapshots (giữ lịch sử có ngưỡng), remove_orphan_files (dọn rác) — ảnh hưởng trực tiếp hiệu năng & chi phí storage; nhớ retention hợp yêu cầu audit.
  • Trino + Iceberg/Delta = "warehouse trên data lake": mở (đa engine, chuẩn mở), rẻ (object storage + compute tách rời), không khoá nhà cung cấp — nhưng đủ ACID/time travel/audit.
  • Iceberg thường là mặc định trung lập engine; Delta hợp hệ Databricks/Spark; Hudi mạnh upsert/CDC streaming.
  • REST catalog (Nessie/Polaris/Unity...) là xu hướng: một bảng, nhiều engine truy cập nhất quán; ngoài ra còn Hive Metastore và AWS Glue.

Bài viết liên quan

Cách index hoạt động (B-Tree), đọc EXPLAIN, seq scan vs index scan và mẫu tối ưu truy vấn.

13 thg 7, 2026 4

Khoá, ràng buộc, quan hệ và chuẩn hoá 1NF/2NF/3NF — thiết kế lược đồ đúng từ đầu.

13 thg 7, 2026 4

Kết nhiều bảng đúng cách: các loại JOIN, bẫy thường gặp và phép hợp tập.

13 thg 7, 2026 3

Truy vấn lồng, CTE (WITH), CTE đệ quy và hàm cửa sổ — vũ khí cho phân tích nâng cao.

13 thg 7, 2026 3