Chẩn đoán lỗi phổ biến trong Apache Hudi
NullPointerException thường xảy ra khi cấu hình không khớp giữa Spark và Hudi hoặc khi tham số bảng bị thiếu.
Nguyên nhân thường là do thiếu tham số hoodie.table.name hoặc hoodie.datasource.write.precombine.field trong job Spark.
Kiểm tra lại toàn bộ tham số trong code Spark trước khi chạy để đảm bảo tính nhất quán.
val spark = SparkSession.builder
.appName("HudiWriteJob")
.master("local[*]")
.config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
.getOrCreate()
import org.apache.spark.sql.SaveMode
val df = spark.read.parquet("/path/to/source/data")
df.write
.format("hudi")
.option("hoodie.table.name", "my_table")
.option("hoodie.datasource.write.precombine.field", "ts")
.option("hoodie.datasource.write.recordkey.field", "id")
.option("hoodie.datasource.write.partitionpath.field", "date")
.mode(SaveMode.Overwrite)
.save("/path/to/hudi/table")
Kết quả mong đợi: Job chạy thành công, không xuất hiện lỗi NullPointerException trong log stderr.
Xử lý lỗi Lock Timeout và File Locking
Lock timeout xảy ra khi hai job cùng cố gắng ghi vào cùng một partition hoặc bảng mà không có cơ chế giải quyết xung đột.
Hudi sử dụng cơ chế file locking để đảm bảo tính toàn vẹn dữ liệu. Nếu lock không được giải phóng, job mới sẽ bị treo.
Để xử lý, bạn cần xác định tiến trình đang giữ lock và buộc giải phóng nó hoặc tăng thời gian chờ.
ls -l /path/to/hudi/table/.hoodie/lock
Kết quả mong đợi: Xuất hiện file lock nếu có job đang chạy. Nếu file tồn tại quá lâu mà không có job, cần kill tiến trình Spark giữ lock.
ps aux | grep "hudi" | grep -v grep
Sử dụng lệnh trên để tìm PID của tiến trình Spark đang giữ lock.
kill -9
rm /path/to/hudi/table/.hoodie/lock
Thao tác này sẽ kill tiến trình và xóa file lock để job mới có thể khởi động.
Kết quả mong đợi: File lock biến mất và job Spark mới có thể chạy mà không bị treo.
Phân tích log Spark và Hudi
Log là nguồn thông tin chính để tìm nguyên nhân gốc rễ của sự cố trong môi trường phân tán.
Log Spark chứa thông tin về lỗi của Executor, trong khi log Hudi chứa chi tiết về các thao tác ghi và quản lý metadata.
Cấu trúc log thường nằm trong thư mục spark-logs hoặc stdout/stderr của driver và executor.
find /var/log/spark -name "*.log" -exec grep -l "ERROR" {} \;
Lệnh này tìm tất cả các file log chứa từ khóa ERROR trong thư mục log của Spark.
Kết quả mong đợi: Danh sách các file log có chứa lỗi cần phân tích.
grep -A 20 "Lock timeout" /var/log/spark/executor-*.log
Đọc 20 dòng sau khi tìm thấy từ khóa "Lock timeout" để xem stack trace chi tiết.
Kết quả mong đợi: Hiển thị nguyên nhân cụ thể gây ra timeout và tên file bị lock.
Tìm kiếm lỗi trong log Hudi
Log Hudi thường được ghi vào stdout của Spark Driver hoặc file log riêng nếu cấu hình logging.
Để tìm lỗi liên quan đến Hudi, hãy lọc các dòng chứa "HudiException" hoặc "HoodieException".
grep -B 5 -A 10 "HudiException" /path/to/spark-driver.log
Hiển thị 5 dòng trước và 10 dòng sau lỗi để hiểu ngữ cảnh xảy ra sự cố.
Kết quả mong đợi: Xem được stack trace đầy đủ của lỗi Hudi, bao gồm cả thông số cấu hình gây lỗi.
Xử lý dữ liệu bị lỗi (Bad Data)
Dữ liệu bị lỗi (Bad Data) xảy ra khi định dạng không đúng, thiếu trường bắt buộc, hoặc giá trị không phù hợp với schema.
Spark có thể ném lỗi và dừng toàn bộ job nếu gặp dữ liệu lỗi, trừ khi cấu hình bỏ qua.
Hudi hỗ trợ cơ chế ghi dữ liệu lỗi vào một partition riêng để không làm hỏng toàn bộ bảng.
df.write
.format("hudi")
.option("hoodie.datasource.write.error.output.path", "/path/to/hudi/table/.hoodie/hoodie_errors")
.option("hoodie.datasource.write.error.output.partition.path", "date")
.mode(SaveMode.Append)
.save("/path/to/hudi/table")
Cấu hình này sẽ chuyển các record lỗi vào thư mục hoodie_errors thay vì dừng job.
Kết quả mong đợi: Job hoàn thành, dữ liệu hợp lệ được ghi vào bảng chính, dữ liệu lỗi nằm trong partition errors.
Khảo sát dữ liệu lỗi
Sau khi job chạy xong, cần kiểm tra thư mục lỗi để xác định nguyên nhân và sửa dữ liệu nguồn.
Dữ liệu trong thư mục lỗi thường được lưu dưới dạng Parquet hoặc JSON tùy cấu hình.
hadoop fs -ls /path/to/hudi/table/.hoodie/hoodie_errors/*
Lệnh này liệt kê các file chứa dữ liệu lỗi trong thư mục errors.
Kết quả mong đợi: Danh sách các file chứa record lỗi cần xử lý.
spark.read.parquet("/path/to/hudi/table/.hoodie/hoodie_errors/*").show(10)
Đọc và hiển thị 10 dòng đầu tiên của dữ liệu lỗi để phân tích.
Kết quả mong đợi: Xem được nội dung cụ thể của các record bị lỗi và lý do bị loại bỏ.
Tối ưu hóa bộ nhớ (Memory tuning) cho Executor Spark
Thiếu bộ nhớ (OOM - Out Of Memory) là nguyên nhân phổ biến gây sập Executor trong các job Hudi lớn.
Hudi yêu cầu bộ nhớ lớn cho các thao tác Compaction và Clustering để xử lý nhiều file nhỏ.
Cần điều chỉnh các tham số spark.executor.memory, spark.memory.fraction và spark.memory.storageFraction.
export SPARK_EXECUTOR_MEMORY=8g
export SPARK_DRIVER_MEMORY=4g
export SPARK_MEMORY_FRACTION=0.6
export SPARK_MEMORY_STORAGE_FRACTION=0.2
Các biến môi trường này cấu hình bộ nhớ cho Executor và Driver, giảm phần dành cho storage để ưu tiên tính toán.
Kết quả mong đợi: Executor có đủ bộ nhớ để xử lý các tác vụ Hudi nặng mà không bị OOM.
Cấu hình tối ưu trong Spark Submit
Khi chạy job qua spark-submit, cần truyền các tham số bộ nhớ trực tiếp trong lệnh.
Đối với các job Hudi, khuyến nghị tăng bộ nhớ Executor lên mức 8GB - 16GB tùy quy mô dữ liệu.
spark-submit \
--master yarn \
--deploy-mode cluster \
--driver-memory 4g \
--executor-memory 8g \
--num-executors 10 \
--conf "spark.memory.fraction=0.6" \
--conf "spark.memory.storageFraction=0.2" \
--conf "spark.executor.cores=4" \
--class com.example.HudiWriteJob \
/path/to/hudi-job.jar
Lệnh này khởi tạo job với cấu hình bộ nhớ tối ưu cho Hudi trên môi trường YARN.
Kết quả mong đợi: Job chạy ổn định, không gặp lỗi OOM và tốc độ xử lý được cải thiện.
Điều chỉnh bộ nhớ cho Compaction
Compaction là quá trình gộp nhiều file nhỏ thành file lớn, tiêu tốn nhiều bộ nhớ nhất.
Cần tăng bộ nhớ dành riêng cho Compaction thông qua tham số hoodie.compaction.max.memory.
df.write
.format("hudi")
.option("hoodie.compaction.max.memory", "4096")
.option("hoodie.compaction.async.enabled", "true")
.mode(SaveMode.Append)
.save("/path/to/hudi/table")
Thiết lập giới hạn bộ nhớ 4GB cho quá trình compaction để tránh OOM trong giai đoạn này.
Kết quả mong đợi: Compaction chạy thành công mà không làm sập Executor.
Khôi phục dữ liệu khi gặp sự cố nghiêm trọng
Khi gặp sự cố nghiêm trọng làm hỏng metadata hoặc mất dữ liệu, cần khôi phục từ snapshot hoặc backup.
Hudi lưu trữ lịch sử các phiên bản dữ liệu, cho phép rollback về trạng thái trước đó.
Trước khi thực hiện khôi phục, cần backup toàn bộ thư mục bảng Hudi hiện tại.
cp -r /path/to/hudi/table /path/to/hudi/table_backup_$(date +%Y%m%d_%H%M%S)
Lệnh này tạo bản sao lưu của toàn bộ bảng trước khi thực hiện bất kỳ thao tác sửa chữa nào.
Kết quả mong đợi: Thư mục backup được tạo thành công với timestamp hiện tại.
Khôi phục từ Snapshot
Dùng công cụ hudi-tools hoặc Spark để đọc snapshot và ghi lại vào bảng mới.
Cách an toàn nhất là tạo một bảng mới từ snapshot thay vì ghi đè trực tiếp lên bảng bị lỗi.
val spark = SparkSession.builder
.appName("HudiRestoreJob")
.getOrCreate()
val snapshotDf = spark.read
.format("hudi")
.option("hoodie.table.type", "COPY_ON_WRITE")
.load("/path/to/hudi/table")
snapshotDf.write
.format("hudi")
.mode(SaveMode.Overwrite)
.save("/path/to/hudi/table_restored")
Đọc toàn bộ dữ liệu từ bảng cũ và ghi vào bảng mới để loại bỏ các metadata bị lỗi.
Kết quả mong đợi: Bảng mới được tạo thành công với dữ liệu sạch và metadata hợp lệ.
Xóa metadata bị lỗi và tái khởi tạo
Nếu metadata bị hỏng nghiêm trọng, có thể cần xóa thư mục .hoodie và tái khởi tạo bảng.
Thao tác này chỉ thực hiện khi đã backup dữ liệu Parquet/Hudi file gốc.
rm -rf /path/to/hudi/table/.hoodie
Xóa thư mục metadata để loại bỏ các file lock hoặc commit bị lỗi.
Kết quả mong đợi: Thư mục .hoodie biến mất, bảng trở về trạng thái chưa được quản lý bởi Hudi.
hadoop fs -ls /path/to/hudi/table
Kiểm tra lại xem chỉ còn các file data (parquet/avro) và các partition folder.
Kết quả mong đợi: Chỉ còn lại dữ liệu thô, không có file metadata.
spark.read.parquet("/path/to/hudi/table/*/*")
.write
.format("hudi")
.option("hoodie.table.name", "my_table")
.option("hoodie.datasource.write.precombine.field", "ts")
.option("hoodie.datasource.write.recordkey.field", "id")
.mode(SaveMode.Append)
.save("/path/to/hudi/table")
Chạy lại job ghi để tái tạo metadata và biến dữ liệu thô thành bảng Hudi hợp lệ.
Kết quả mong đợi: Bảng Hudi được khôi phục hoàn toàn, có thể truy vấn bình thường.
Điều hướng series:
Mục lục: Series: Triển khai Database Lakehouse với Apache Hudi và Ubuntu 24.04
« Phần 7: Tích hợp Hudi với các công cụ truy vấn và ETL phổ biến