Đang tải nội dung…
Đang tải nội dung…
Copy dữ liệu nền trong lúc hệ thống vẫn ghi mà không mất update
Kafka Streams, CDC & Event Processing · Phần 2Bối cảnh Fortex là hư cấu. Bằng chứng trong bài đến từ PostgreSQL logical decoding trong Docker Lab, không phải dashboard production. Lab minh họa ranh giới thứ tự; nó không chạy một Debezium cluster đầy đủ.
Sau vụ payment, Hà giao Ben dựng lại search index cho catalog. Kế hoạch của Ben rất gọn: đọc toàn bộ bảng products, sau đó bật CDC để nhận những thay đổi mới.
Tom nghe hết rồi hỏi: “Mới tính từ lúc nào?”
Trong lúc câu SELECT dài đang quét bảng, merchant đổi giá sản phẩm 7 từ 100 thành 110. Nếu connector bắt đầu đọc WAL sau update đó, snapshot giữ 100 và stream không bao giờ thấy 110. Search index cũ vĩnh viễn dù cả hai bước đều “thành công”.
CDC bootstrap phải ghép hai dòng thời gian:
Gọi vị trí bắt đầu log là L0. Điều kiện cần là:
Toán tử \oplus nghĩa là áp thay đổi theo identity và thứ tự phù hợp, không phải nối mù hai danh sách. Nếu snapshot thuộc thời điểm trước L0 nhưng một update xảy ra trước khi log bắt đầu, update nằm trong khoảng trống và biến mất.
Debezium PostgreSQL connector tạo consistent snapshot ở lần khởi động đầu rồi tiếp tục đọc từ đúng log position đã ghi nhận. Với incremental snapshot, connector còn buffer các READ row và bỏ snapshot row nếu một stream event mới hơn đã thay thế nó.
Lab dùng PostgreSQL 17.6 với wal_level=logical và output plugin test_decoding. Hai lịch chạy cùng bắt đầu từ price=100.
Lịch sai: lấy baseline 100, update thành 110, rồi mới tạo replication slot. Slot không thể quay lại lấy thay đổi trước thời điểm nó tồn tại; projection vẫn 100.
Lịch đúng: tạo log boundary trước, lấy snapshot 100, update thành 110, rồi drain logical log từ boundary. Projection hội tụ về 110.
| Lịch chạy | Snapshot | WAL thấy 110 | Projection cuối |
|---|---|---|---|
| Slot tạo sau update | 100 | Không | 100 |
| Slot tạo trước snapshot/update | 100 | Có | 110 |
6cdf03cd-d54e-417c-a3dc-59f4d9d4462fĐây là bằng chứng về ordering boundary, không phải benchmark Debezium. Full connector còn quản lý schema, offsets, snapshot modes và nhiều trường hợp cạnh khác.
Ben nhìn kết quả rồi hỏi: “Nếu một row vừa nằm trong snapshot vừa xuất hiện trong stream thì có thành duplicate không?”
Tom gật đầu. Không mất update chưa có nghĩa là không lặp. Sink nên upsert theo primary key và dùng source position hoặc domain version để từ chối event cũ hơn. Với append-only sink, cần giữ identity của change hoặc thiết kế materialization riêng.
Một rule thực dụng:
apply(change) only when change.version > projection.version
Nếu source không có domain version, connector position như LSN có thể sắp thứ tự thay đổi trong cùng nguồn. Nhưng đừng so sánh LSN giữa hai database độc lập như một chiếc đồng hồ toàn cục.
PostgreSQL giữ WAL cần thiết cho replication slot chưa consume. Tài liệu logical decoding giải thích slot giữ trạng thái để changes không bị xóa trước khi consumer đọc. Khi connector chết lâu, lượng WAL giữ lại có thể lấp đầy disk.
Vì vậy production cần theo dõi:
restart_lsn, confirmed_flush_lsn và khoảng cách tới WAL hiện tại;pg_wal và tốc độ tăng;Drop slot để giải phóng disk cũng đồng nghĩa bỏ mất recovery position. Khi ấy phải chấp nhận resnapshot hoặc một quy trình khôi phục khác.
Với bảng nhỏ và có cửa sổ bảo trì, khóa ghi ngắn rồi copy có thể đơn giản hơn. Với projection có thể xây lại thường xuyên, batch theo updated_at cộng reconciliation đôi khi đủ, miễn chấp nhận độ trễ và xử lý timestamp trùng. CDC đáng giá khi hệ thống cần thay đổi liên tục, low latency và audit/replay rõ ràng; nó đổi một truy vấn batch đơn giản lấy vận hành connector, schema evolution và WAL retention.
Cuối buổi, Ben sửa sơ đồ bootstrap. Cậu không còn vẽ snapshot rồi CDC thành hai hộp rời nhau. Giữa chúng có một đường đậm mang tên L0.
Tom thêm một ghi chú nhỏ: “Mọi migration online đều phải trả lời: thay đổi trong lúc copy đi đâu?”
Ngày hôm sau, search index nhận đủ update nhưng trạng thái order lại chạy ngược từ PAID về CREATED. Log không mất gì cả. Hệ thống chỉ tin nhầm chiếc đồng hồ.
PostgreSQL 17.6 logical decoding · digest pinned · isolated Docker
Tải source Docker Lab (.zip)COMMIT 570ccb6b3b38b8121074bb56c07f3db638bf0961
Đọc kỹ README trước khi chạy. Các lệnh thực nghiệm chuẩn trong Docker Lab:
make setupmake seedmake reproducemake verifymake cleanupMôi trường Docker độc lập, giới hạn tài nguyên và tự động dọn dẹp sau khi chạy. Số liệu trong bài viết là kết quả lần chạy chính thức; mỗi lần chạy mới đều lưu lịch sử riêng.