Lưu dữ liệu khách trên S3: vì sao một thư mục Parquet chưa phải là một bảng
S3 chỉ bảo đảm từng object riêng lẻ. Muốn cả bảng vẫn đúng khi job chết giữa đêm, bạn cần Delta Lake, Iceberg hoặc Hudi, và một bucket được khóa chặt ngay từ ngày đầu.
S3 chỉ bảo đảm từng object; transaction log mới bảo đảm được cả bảng.
Đồ hoạ: FDE Times
Tóm tắt nhanh
- S3 đảm bảo strong consistency cho từng object nhưng không có cập nhật nguyên tử xuyên nhiều key. Vì vậy một thư mục Parquet có thể bị đọc ở trạng thái ghi dở.
- Delta Lake dùng transaction log để có ACID và mỗi lần ghi tạo một version mới; Iceberg có time travel trên snapshot; Hudi mạnh về upsert và delete.
- Với bucket của khách: giữ private và Block Public Access, bật Versioning, rồi mới dùng Object Lock và Lifecycle theo nhu cầu.
Sáng thứ Hai, dashboard doanh thu của khách hiện số liệu hôm qua chỉ bằng một phần ba ngày thường. Không ai sửa code. Job chạy đêm qua chỉ ghi đè partition của ngày hôm trước trên S3, rồi bị kill giữa chừng vì hết bộ nhớ.
Đây là một tình huống giả định, nhưng ai làm data ở site khách đủ lâu cũng từng gặp một biến thể của nó. Lỗi không nằm ở S3. Lỗi nằm ở chỗ ta coi một thư mục chứa file là một cái bảng.
Ở buổi kickoff nào cũng có người nói câu “cứ đổ hết vào S3”. Người FDE cần biết khi nào một thư mục file là đủ, khi nào phải dùng table format, và cần khóa bucket sao cho dữ liệu khách không bị lộ, không bị mất. Người hiểu những điều đó dựng được pipeline mà khách dám dựa vào; người không hiểu chỉ dựng được pipeline chạy lúc demo.
Object storage hứa gì, và không hứa gì?
AWS mô tả S3 là dịch vụ object storage, thường dùng cho data lake, backup, archive và phân tích big data. S3 có strong read-after-write consistency cho PUT và DELETE ở mọi Region. Google Cloud Storage cũng đảm bảo điều tương tự cho mọi thao tác upload.
Cần đọc kỹ chữ “object”. Tài liệu S3 nói rõ: mọi cập nhật đều dựa trên key, và không có cách nào cập nhật nguyên tử nhiều key cùng lúc. Nếu hai PUT cùng ghi vào một key, request có timestamp muộn hơn sẽ thắng. S3 không khóa object giúp bạn, nên muốn có cơ chế khóa thì ứng dụng phải tự xây.
Azure Blob Storage có thêm Data Lake Storage với hệ thống file phân cấp, nên đừng mặc định giới hạn của S3 áp dụng y nguyên cho mọi cloud; ví dụ dưới đây bám vào S3, nơi giới hạn được ghi rõ nhất.
Lần theo một job ghi đè bị chết giữa chừng
Hãy hình dung thư mục s3://acme-lake/orders/order_date=2026-10-08/ đang có ba file: part-0, part-1 và part-2. Job ghi đè làm theo cách thô sơ: xóa ba file cũ rồi ghi ba file mới. Mỗi thao tác là một request riêng lên một key riêng.
Giả sử job chết ngay sau khi ghi xong part-0 mới. Reader liệt kê thư mục, chỉ thấy một file, và báo doanh thu bằng một phần ba thực tế.
Nếu đảo thứ tự, ghi file mới trước rồi mới xóa file cũ, thì khi chết giữa chừng reader sẽ thấy cả file cũ lẫn file mới, và doanh thu bị đếm hai lần.
Thứ tự nào cũng không an toàn, vì không có transaction nào bao trùm nhiều key.
Delta Lake giải bài toán này bằng một transaction log dạng file, đặt cạnh các file Parquet, để có giao dịch ACID.
# Ghi đè đúng một ngày, nguyên tử ở cấp bảng
(df_moi.write.format("delta")
.mode("overwrite")
.option("replaceWhere", "order_date = '2026-10-08'")
.save("s3://acme-lake/orders"))
# Đọc lại phiên bản trước khi job chạy để đối chiếu
truoc = (spark.read.format("delta")
.option("versionAsOf", 41)
.load("s3://acme-lake/orders"))
Nếu job chết trước bước commit, các file mới chỉ nằm đó mà không bảng nào trỏ tới. Dashboard vẫn thấy nguyên phiên bản cũ. Mỗi lần ghi vào bảng Delta đều tạo ra một version mới, nên khi khách hỏi “hôm qua bảng trông thế nào”, bạn có câu trả lời mà không cần đi lục backup.
Delta, Iceberg hay Hudi: chọn theo engine của khách
Cả ba format đều giải cùng một bài toán, nhưng mỗi format mạnh ở một chỗ khác nhau. Câu hỏi đầu tiên nên đặt ra không phải là “format nào tốt nhất”, mà là “khách đang đọc dữ liệu bằng engine gì”.
| Format | Điểm mạnh | Gợi ý dùng khi |
|---|---|---|
| Delta Lake | Transaction log mở trên Parquet, mỗi lần ghi tạo một version | Khách đã chạy Databricks |
| Apache Iceberg | Dành cho bảng phân tích rất lớn; time travel cho phép chạy lại truy vấn trên đúng một snapshot | Khách trên AWS muốn dùng S3 Tables, truy vấn bằng Athena, Redshift hoặc Spark |
| Apache Hudi | Hai loại bảng COPY_ON_WRITE và MERGE_ON_READ, mạnh về upsert và delete | Nguồn dữ liệu cập nhật hoặc xóa từng dòng liên tục |
Với Iceberg, AWS đã có hẳn một loại table bucket, gọi là S3 Tables, được xây riêng để lưu bảng ở định dạng này. Nếu khách thuộc hệ AWS, đó là con đường ít ma sát nhất.
Tính năng time travel của Iceberg cũng giải quyết một yêu cầu rất hay gặp: kiểm toán viên muốn chạy lại báo cáo tuần trước và phải ra đúng con số cũ.
Bucket của khách: khóa trước, tối ưu sau
Bucket S3 và các object trong đó mặc định là private. Hãy giữ nguyên như vậy và luôn bật Block Public Access. Nếu cần chia sẻ dữ liệu cho một đội khác, hãy cấp quyền theo danh tính thay vì mở bucket ra cho tiện.
Tiếp theo là ba công cụ bảo vệ. Versioning giữ lại bản cũ của từng object. Object Lock (WORM) ngăn object bị xóa hoặc ghi đè trong một khoảng thời gian cố định hoặc vô thời hạn. Lifecycle tự động chuyển hoặc xóa dữ liệu cũ để tiết kiệm chi phí.
Cần phân biệt hai tầng bảo vệ. Versioning của S3 cứu được từng file. Time travel của table format cứu được trạng thái của cả bảng. Khi nhận dữ liệu thô có yêu cầu tuân thủ, hãy cân nhắc dùng Object Lock cho vùng raw.
Lifecycle rule thì phải được thiết kế cùng chính sách dọn version của table format, nếu không bạn có thể vô tình xóa đúng file mà một version cũ vẫn đang cần.
Năm bước khi nhận một data lake mới
Bước đầu tiên là vẽ sơ đồ ai ghi, ai đọc, và có bao nhiêu job cùng ghi vào một chỗ. Chỉ riêng sơ đồ này đã đủ cho bạn biết có rủi ro last-writer-wins hay không. Bước thứ hai là tách hai vùng: vùng raw chỉ append file thô, còn vùng bảng dùng table format.
Bước thứ ba là chọn format theo engine của khách, như trong bảng ở trên. Bước thứ tư là cấu hình bucket: private, Block Public Access, Versioning, rồi đến Object Lock và Lifecycle khi có lý do cụ thể. Bước cuối cùng hay bị bỏ qua nhất: cố tình kill một job đang ghi, rồi kiểm tra xem dashboard có còn đúng không.
Những lỗi khiến khách mất niềm tin
Lỗi phổ biến nhất là coi một thư mục Parquet là một bảng, rồi cho hai job cùng ghi đè vào đó.
Lỗi thứ ba là mở public bucket “chỉ trong một buổi chiều” để chia sẻ file cho đối tác.
Có một lỗi tinh vi hơn: chọn format theo xu hướng thay vì theo công cụ khách đang trả tiền và vận hành mỗi ngày. Một bảng Hudi đẹp đến đâu cũng vô nghĩa nếu đội analytics của khách chỉ quen Athena và không ai bảo trì được nó.
Khi đọc JD cho vị trí FDE, hãy để ý các từ như “lakehouse”, “Iceberg”, “Delta” hay “CDC”. Trong CV, đừng chỉ viết “dùng S3”. Hãy ghi cụ thể bạn đã thiết kế bảng nào, chọn format nào, và đã diễn tập tình huống lỗi ra sao.
Đến một ngày nào đó, job đêm của khách chắc chắn sẽ chết giữa chừng. Điều cần quyết định từ hôm nay là sáng hôm sau dashboard hiện số liệu của hôm kia, hay hiện một phần ba sự thật.