- Pipeline delta index search/ads giảm worst-case freshness lag từ 10 phút xuống 30 giây (giảm 50%) sau khi chuyển sang Spark Structured Streaming micro-batch với trigger 30 giây.
- Bottleneck thực sự không phải processing cost mà là scheduling delay và orchestration overhead.
- Hai lần thất bại (record-level streaming + success file markers) trước khi tìm ra 4 pattern cốt lõi.
- Áp dụng cho pipeline xử lý hàng triệu documents, delta index hàng chục GB.
TL;DR

Một pipeline delta index cho hệ thống search/ads xử lý hàng triệu documents đã cắt giảm worst-case freshness lag từ 10 phút xuống còn 30 giây - tức giảm 50% - sau khi chuyển từ batch scheduling sang Spark Structured Streaming micro-batch mode. Điều quan trọng: cải thiện này không đến từ việc xử lý nhanh hơn, mà từ việc loại bỏ hoàn toàn scheduling delays và orchestration overhead.
Đây là bài lessons learned thực tế từ Parveen Saini (Staff Software Engineer II) chia sẻ trên InfoQ ngày 4/5/2026 - hai lần thất bại, bốn pattern cốt lõi, và một insight đơn giản mà thường bị bỏ qua.
Kẻ sát thủ thầm lặng: scheduling delay, không phải processing cost
Pipeline ban đầu chạy dạng scheduled batch: delta index (khoảng 1/10 kích thước full index, thường là hàng chục GB) được rebuild định kỳ để cập nhật ads, campaigns và signals giữa các lần full rebuild (tốn 2-3 giờ xây dựng + tối đa 5 giờ tổng cộng bao gồm validation & deployment).
Vấn đề không nằm ở tốc độ xử lý. Vấn đề là:
- Data mới đến ngay sau một scheduled run thường phải đợi gần hết interval tiếp theo mới được xử lý
- Failure yêu cầu re-execute toàn bộ window, không chỉ phần bị lỗi
- Burst of updates làm batch duration tăng, scheduled runs bị skip hoặc delay - freshness lag tích lũy
Data partition mới đến mỗi 5-7 phút nhưng pipeline không thể phản ứng kịp thời vì progress bị gate bởi cả processing time lẫn fixed scheduling boundaries.
Hai lần thất bại trước khi tìm ra hướng đúng
Lần 1 - Record-level streaming: Nhìn có vẻ "đúng nhất" trên giấy. Thực tế? Indexing logic hoạt động ở cấp độ nhóm/product, không phải individual record. Một thay đổi vào 1 ad đòi hỏi recompute toàn bộ grouped representation. Record-level streaming tạo ra partial-update states - một số ads được update nhưng grouped index chưa consistent - dẫn đến search results tạm thời sai. Không chấp nhận được.
Lần 2 - Success files & completion markers: Cách cũ từ batch pipeline. Trong continuous streaming, model này vỡ vụn vì S3 eventual consistency: completion markers xuất hiện muộn hoặc không nhất quán, listing partitions bị incomplete, job poll liên tục cho markers đã tồn tại nhưng chưa visible, dẫn đến duplicate hoặc premature processing.
Bài học cốt lõi: "Completion is inferred, not guaranteed."
Bốn pattern đã cứu pipeline này
1. Rate-based deterministic triggers: Thay completion markers bằng time-driven execution - trigger mỗi 30 giây. Không cần filesystem notifications, không cần completion markers. Mỗi trigger chỉ làm một việc: list visible partitions, identify latest, compare với watermark, process nếu mới hơn. Đơn giản và deterministic.
2. External logical watermark: Không dùng Spark native checkpointing vì nó được thiết kế cho sequential replay. Thay bằng một external watermark đơn giản - file metadata trong object storage ghi atomic - đại diện cho partition đã xử lý gần nhất dựa trên timestamp trong partition path. Single upstream job gán timestamp nên ordering nhất quán không cần clock sync.
3. Freshness-first: nhảy thẳng đến partition mới nhất: Nếu P0 là watermark và P1, P2, P3 đều visible - xử lý P3, bỏ qua P1 và P2. An toàn vì delta window dùng overlapping sliding window 5 giờ: các partitions bị skip tự nhiên được covered trong run tiếp theo. Edge case còn lại thì full rebuild (chạy mỗi vài giờ) xử lý nốt.
4. Planned restarts mỗi 24 giờ qua watchdog: JVM long-lived process tích lũy heap memory và GC pressure theo thời gian. Thay vì cố giữ job chạy mãi, team chủ động restart định kỳ: release memory, reset execution state, pickup code mới không cần can thiệp thủ công. External watchdog monitor liveness và enforce restart cadence - biến failures thành routine thay vì urgent.
Khi nào không nên dùng pattern này
Pattern này không phù hợp với hệ thống cần strict per-record processing guarantees, ordered replay of all historical events, exact catch-up semantics sau downtime, hoặc sub-second latency (high-frequency trading, complex event pattern matching).
Với những use case đó, cần Apache Flink hoặc Kafka Streams (single-digit millisecond latency, per-record semantics). Một lựa chọn mới là Databricks Real-Time Mode (public preview từ tháng 8/2025) - đạt dưới 200ms trên Spark API mà không cần duy trì Flink stack riêng, loại bỏ nguy cơ "logic drift" giữa training và inference.
Ai nên áp dụng ngay
Pattern này lý tưởng cho:
- Search & ads indexing - freshness quan trọng nhưng không cần per-record immediacy
- ML feature generation - recommendation, fraud detection với window 10-60 giây
- ETL & data sync từ object storage - S3-style với eventual consistency
- Overlapping window patterns - hệ thống có thể skip intermediate states một cách an toàn
Một quy tắc nhận biết đơn giản: nếu team đang chạy batch với freshness lag do scheduling overhead nhiều hơn processing time, đây là pattern đáng thử đầu tiên.
Đọc thêm
Bài gốc của Parveen Saini: From Batch to Micro-Batch Streaming: Lessons Learned the Hard Way in a Delta Index Pipeline - InfoQ, 4/5/2026. Muốn đi xa hơn micro-batch: Databricks Real-Time Mode giải thích cách Spark đạt sub-200ms latency không cần engine thứ hai.
