FDE PulseViệc làm FDE đang mở 314Mới đăng 7 ngày qua 10Chủ đề nổi bật: Đào tạo kỹ năng FDE tại Đông Nam Á

Tờ báo của nghề Forward Deployed Engineer

Bách khoa

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.

Đồ hoạThư mục Parquet thô và bảng có table format
Thư mục Parquet trên S3Bảng Delta, Iceberg hoặc Hudi
Job ghi đè chết giữa chừngReader thấy file thiếu hoặc lẫn cũ với mới, số liệu saiChưa commit thì reader vẫn thấy nguyên phiên bản cũ
Hai writer cùng ghi một keyTimestamp muộn hơn thắng, ứng dụng phải tự khóaMọi thay đổi đi qua transaction log
Xem lại bảng hôm quaChỉ khôi phục được từng file nhờ VersioningTime travel về một version hoặc snapshot
Upsert hoặc xóa từng dòngPhải tự viết lại cả fileHudi có COPY_ON_WRITE và MERGE_ON_READ

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.
Chia sẻLinkedInFacebookX

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.

6 nguồn
Đọc tiếp trên lộ trình · Chặng 2: Kỹ thuật rộngChốt grain trước khi vẽ bảng: dựng star schema bên cạnh database giao dịch của kháchCâu hỏi "doanh thu theo tỉnh, theo nhóm hàng, theo tháng" nghe đơn giản, nhưng database bán hàng của khách thường không được thiết kế để trả lời nó, và FDE là người phải dựng cầu nối.