Vai trò của Catalog trong kiến trúc Lakehouse
Catalog đóng vai trò là lớp metadata trung tâm, tách biệt logic định danh bảng (table name) khỏi địa chỉ vật lý (file path) trên Storage. Việc này cho phép nhiều engine (Spark, Presto, Trino) truy cập cùng một bảng mà không cần biết vị trí lưu trữ cụ thể.
Trong kiến trúc Lakehouse, Catalog giải quyết bài toán "Namespace management" và "Schema evolution". Nếu không có Catalog, bạn phải quản lý thủ công các đường dẫn như s3://bucket/iceberg/table/data/, dẫn đến khó khăn khi thực hiện các thao tác như Time Travel hay Branching.
Chúng ta sẽ so sánh 3 phương án: Hive/Glue (phổ biến, ổn định), Nessie (Git-based, hỗ trợ branching tốt nhất cho Data Versioning), và Hudi (tích hợp sẵn trong Hudi nhưng ít linh hoạt khi dùng chung với Iceberg/Delta).
Cấu hình Hive Metastore và AWS Glue Catalog cho Iceberg/Delta
Đây là phương án tiêu chuẩn (Standard) mà hầu hết các doanh nghiệp đang sử dụng. Hive Metastore lưu metadata trong database (MySQL/PostgreSQL), còn AWS Glue Catalog lưu trên cloud. Iceberg và Delta đều hỗ trợ native các catalog này.
Triển khai Hive Metastore trên Kubernetes
Bước 1: Khởi động container Hive Metastore sử dụng MySQL làm backend. Đây là bước cần thiết để Spark có thể truy vấn metadata qua JDBC thay vì chỉ đọc file path.
Tại sao: Apache Spark cần một điểm kết nối duy nhất để hỏi "Bảng X nằm ở đâu?" thay vì phải quét toàn bộ S3/GCS.
Kết quả mong đợi: Dịch vụ Metastore chạy trên port 9083 và có thể kết nối từ pod Spark.
kubectl run hive-metastore --image=apache/hive:3.1.2 --restart=Never -- \
-e HIVE_METASTORE_PORT=9083 \
-e HIVE_METASTORE_CONF="javax.jdo.option.ConnectionURL=jdbc:mysql://mysql-service:3306/metastore?createDatabaseIfNotExist=true" \
-e HIVE_METASTORE_CONF="javax.jdo.option.ConnectionDriverName=com.mysql.jdbc.Driver" \
-e HIVE_METASTORE_CONF="javax.jdo.option.ConnectionUserName=admin" \
-e HIVE_METASTORE_CONF="javax.jdo.option.ConnectionPassword=secret" \
--expose
Cấu hình Spark để kết nối với Hive Catalog
Bước 2: Sửa file cấu hình Spark (spark-defaults.conf) để chỉ định Catalog là Hive thay vì Default (In-Memory).
Tại sao: Mặc định Spark dùng Hadoop Catalog (path-based). Để dùng Hive/Glue, phải force Spark load module Hive.
Kết quả mong đợi: Khi chạy Spark, bạn có thể dùng lệnh SHOW TABLES để liệt kê bảng từ Hive Metastore.
File cấu hình: /etc/spark/conf/spark-defaults.conf
spark.sql.catalogImplementation=hive
spark.sql.catalog.hive=hive
spark.hadoop.hive.metastore.uris=thrift://hive-metastore.default.svc.cluster.local:9083
spark.sql.extensions=org.apache.spark.sql.delta.DeltaSparkSessionExtension
Triển khai với AWS Glue Catalog
Bước 3: Nếu môi trường là AWS, thay vì tự host Hive, ta dùng Glue Catalog. Cấu hình Spark sẽ đổi sang sử dụng Glue.
Tại sao: Glue Catalog là managed service, không cần lo về HA hay backup database metadata.
Kết quả mong đợi: Spark tự động sync schema với AWS Glue Console.
File cấu hình: /etc/spark/conf/spark-defaults.conf (phiên bản AWS)
spark.sql.catalogImplementation=glue
spark.glue.catalog.id=your-glue-catalog-id
spark.sql.extensions=org.apache.spark.sql.delta.DeltaSparkSessionExtension
Triển khai Nessie cho Branching và Time Travel
Nessie là catalog dựa trên Git (Git-like), hỗ trợ tính năng Branching và Tagging mạnh mẽ, cho phép các team làm việc song song trên cùng một dataset mà không ghi đè lên nhau (Merge Conflict resolution).
Chạy Nessie Server trên Kubernetes
Bước 1: Deploy Nessie server. Nessie cần một backend storage (H2, Postgres, S3) để lưu metadata. Ở đây ta dùng H2 (in-memory) để demo nhanh, production nên dùng Postgres.
Tại sao: Nessie hoạt động như một REST API server độc lập, Spark sẽ gọi API này để quản lý metadata thay vì gọi Hive.
Kết quả mong đợi: Nessie chạy trên port 19120 và có thể truy cập qua browser hoặc API client.
kubectl run nessie --image=projectnessie/nessie:0.83.0 --restart=Never -- \
-e NESSIE_BACKEND=h2 \
-p 19120 \
--expose
Cấu hình Spark để kết nối Nessie Catalog
Bước 2: Thêm Nessie Catalog vào Spark và cấu hình URI kết nối.
Tại sao: Spark cần biết đường dẫn REST API của Nessie để thực hiện các lệnh DDL (CREATE TABLE, DROP TABLE) và thao tác branching.
Kết quả mong đợi: Bạn có thể tạo branch mới trong Spark SQL và làm việc song song.
File cấu hình: /etc/spark/conf/spark-defaults.conf (phiên bản Nessie)
spark.sql.catalog.nessie=org.apache.iceberg.rest.CatalogRestClient
spark.sql.catalog.nessie.uri=http://nessie.default.svc.cluster.local:19120
spark.sql.catalog.nessie.catalog-type=ICEBERG
spark.sql.catalog.nessie.warehouse=s3://your-bucket/path/
spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtension
Kết nối Spark với Catalog trung tâm thay vì Path-based
Trước khi có Catalog, bạn thường viết: spark.read.format("iceberg").load("s3://bucket/table"). Bây giờ, với Catalog, cú pháp chuẩn là spark.read.table("catalog_name.database_name.table_name").
Thực hành: Tạo bảng qua Catalog
Bước 1: Tạo database và bảng thông qua Catalog (Hive hoặc Nessie) thay vì tạo thư mục vật lý thủ công.
Tại sao: Catalog tự động tạo thư mục metadata và data trên storage, đồng thời ghi nhận thông tin vào Metastore/Nessie.
Kết quả mong đợi: Bảng xuất hiện trong danh sách SHOW TABLES của Spark và có thể truy cập bằng tên logic.
spark.sql("CREATE DATABASE IF NOT EXISTS nessie_catalog.production")
spark.sql("CREATE TABLE IF NOT EXISTS nessie_catalog.production.sales USING iceberg AS SELECT * FROM raw_sales")
Thực hành: Branching với Nessie
Bước 2: Tạo một nhánh (branch) mới từ branch master để thực hiện thử nghiệm schema hoặc data mà không ảnh hưởng production.
Tại sao: Đây là tính năng độc quyền của Nessie (và Git-based catalogs), giúp áp dụng mô hình CI/CD cho dữ liệu (Data Versioning).
Kết quả mong đợi: Bạn có thể đọc/ghi vào branch "dev" trong khi "main" vẫn hoạt động độc lập.
spark.sql("CALL system.create_branch('dev', 'main')")
spark.sql("USE nessie_catalog.production")
spark.sql("INSERT INTO sales SELECT * FROM raw_sales WHERE date > '2023-01-01'")
spark.sql("CALL system.merge_branch('dev', 'main')")
Verify kết quả tích hợp Catalog
Bước cuối: Kiểm tra toàn bộ hệ thống để đảm bảo Catalog hoạt động đúng, metadata đồng bộ và các engine khác có thể truy cập.
Kiểm tra danh sách bảng và Schema
Thực hiện lệnh để liệt kê tất cả bảng trong Catalog và kiểm tra schema.
Tại sao: Xác nhận Catalog đã lưu trữ metadata đúng và Spark có thể đọc được.
Kết quả mong đợi: Danh sách bảng hiện ra, không báo lỗi "Table not found".
spark.sql("SHOW TABLES IN nessie_catalog.production").show()
spark.sql("DESCRIBE TABLE nessie_catalog.production.sales").show()
Kiểm tra tính năng Time Travel
Thực hiện truy vấn dữ liệu theo timestamp hoặc snapshot ID để xác nhận lịch sử phiên bản.
Tại sao: Catalog lưu giữ lịch sử snapshots, đây là tính năng cốt lõi của Lakehouse.
Kết quả mong đợi: Dữ liệu trả về là dữ liệu ở thời điểm trong quá khứ, không phải dữ liệu mới nhất.
spark.sql("SELECT * FROM nessie_catalog.production.sales AS OF TIMESTAMP '2023-10-01 00:00:00'").show()
Kiểm tra Branching (chỉ dành cho Nessie)
Chuyển đổi context sang branch khác và so sánh dữ liệu.
Tại sao: Xác nhận Nessie đang quản lý nhiều nhánh độc lập.
Kết quả mong đợi: Dữ liệu trên branch "dev" khác với branch "main".
spark.sql("USE nessie_catalog.production")
spark.sql("SET spark.sql.catalog.nessie.branch=dev")
spark.sql("SELECT count(*) FROM sales").show()
spark.sql("SET spark.sql.catalog.nessie.branch=main")
spark.sql("SELECT count(*) FROM sales").show()
Đ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 5: Xử lý dữ liệu thực tế: ETL pipeline với Spark trên Kubernetes
Phần 7: Quản lý tài nguyên và tối ưu hiệu năng trên Kubernetes »