Đồng bộ dữ liệu giữa hai ứng dụng nghe đơn giản, chép từ bên này sang bên kia, nhưng là loại luồng dễ hỏng âm thầm nhất: bản ghi bị nhân đôi, sửa bên này bị bên kia ghi đè, hai luồng đá qua đá lại thành vòng lặp vô tận. Phần lớn những sự cố đó đến từ ba quyết định chưa được đưa ra rõ ràng trước khi dựng: bên nào là nguồn chính, ghép hai bản ghi bằng gì, và khi hai bên cùng sửa thì ai thắng.
Bài này đi qua ba quyết định đó bằng ví dụ danh sách khách hàng giữa phần mềm bán hàng và công cụ gửi email chăm sóc khách, cùng các biện pháp chống trùng, chống vòng lặp mà người dùng công cụ kéo thả tự làm được.
Một chiều hay hai chiều: câu hỏi đầu tiên
Đồng bộ một chiều nghĩa là dữ liệu chỉ chảy từ ứng dụng A sang B; mọi thay đổi ở B không được gửi ngược về A. Ví dụ: khách mới tạo trong phần mềm bán hàng tự được thêm vào danh sách email, nhưng ai sửa trong công cụ email thì phần mềm bán hàng không biết. Đồng bộ hai chiều nghĩa là sửa ở đâu cũng được, bên kia tự cập nhật theo.
Hai chiều nghe hấp dẫn hơn, nhưng phức tạp hơn nhiều lần: phải chống vòng lặp, phải xử lý xung đột, phải xử lý xoá. Lời khuyên cho người mới là bắt đầu bằng một chiều, chọn một ứng dụng làm nguồn chính, và dặn cả đội chỉ sửa ở nguồn chính. Phần lớn nhu cầu của cửa hàng nhỏ được đáp ứng như vậy.
Chỉ cân nhắc hai chiều khi thật sự có hai nhóm người làm việc ở hai ứng dụng khác nhau và cả hai đều phải sửa dữ liệu, ví dụ nhân viên bán hàng cập nhật số điện thoại trong phần mềm bán hàng, còn người làm chăm sóc khách cập nhật trạng thái huỷ nhận thư trong công cụ email. Ngay cả lúc đó, thường có thể chia theo trường: mỗi trường có một nguồn chính riêng, đồng bộ hai chiều nhưng không trường nào bị sửa ở hai nơi.
Ghép bản ghi: khoá nào để biết hai dòng là một khách
Mã nội bộ của ứng dụng nguồn. Mỗi bản ghi trong ứng dụng có một mã duy nhất do ứng dụng tự sinh. Lưu mã này vào một trường riêng ở ứng dụng đích, thường gọi là mã ngoài hay trường tham chiếu. Đây là khoá ghép đáng tin nhất vì không bao giờ trùng và không đổi khi khách sửa thông tin.
Email hoặc số điện thoại. Tiện vì có sẵn ở cả hai bên, nhưng không ổn định: khách đổi email, gõ thừa khoảng trắng, viết hoa thường lẫn lộn, số điện thoại có hoặc không có mã quốc gia. Nếu buộc phải dùng, chuẩn hoá trước khi so: viết thường, bỏ khoảng trắng, đưa số điện thoại về một dạng.
Tên khách. Không dùng làm khoá. Hai khách cùng tên là chuyện bình thường, và một khách có thể được gõ tên theo nhiều cách.
Khoá ghép kép. Khi không có mã chung, có thể ghép bằng hai trường, ví dụ email và số điện thoại đều khớp. Chặt hơn nhưng sẽ bỏ sót khi một trong hai thay đổi; ghi những ca không khớp ra bảng riêng để người xem.
Ghi lại khoá ngược. Sau khi tạo bản ghi ở ứng dụng đích, lưu mã của bản ghi đích ngược về nguồn nếu nguồn có trường cho phép. Có khoá hai chiều thì về sau sửa, xoá, đối chiếu đều nhanh hơn.
Chống tạo trùng: tìm trước, tạo sau
Lỗi phổ biến nhất của luồng đồng bộ là mỗi lần chạy lại tạo thêm một bản ghi mới cho cùng một khách. Mẫu sau chặn được phần lớn trường hợp.
- Khi có sự kiện khách mới hoặc khách được sửa ở nguồn, lấy khoá ghép của bản ghi đó.
- Gọi API của ứng dụng đích tìm bản ghi có khoá ghép tương ứng. Nhiều công cụ kéo thả có sẵn hành động tìm kèm tuỳ chọn tạo nếu không thấy.
- Nếu tìm thấy, cập nhật bản ghi đó với những trường đã thay đổi. Nếu không thấy, tạo mới và lưu khoá ghép vào trường tham chiếu.
- Nếu tìm thấy nhiều hơn một bản ghi, đừng chọn đại; dừng và ghi ra bảng cần xem xét, vì đó là dấu hiệu dữ liệu đích đã có trùng từ trước.
- Với API có hỗ trợ thao tác tạo hoặc cập nhật trong một lần gọi theo khoá, thường gọi là upsert, dùng nó thay cho hai bước tìm và tạo, vì giảm được khe hở khi hai sự kiện tới gần như cùng lúc.
Trước khi bật đồng bộ cho dữ liệu thật, dọn trùng ở cả hai bên một lần bằng tay hoặc bằng chức năng gộp của ứng dụng. Đồng bộ trên dữ liệu đã trùng chỉ nhân bản sự lộn xộn.
Hai bên cùng sửa: các luật giải quyết xung đột
Xung đột xảy ra khi cùng một trường được sửa ở hai nơi trước khi đồng bộ kịp chạy. Cần chọn trước một luật, viết ra, và để luồng làm theo.
| Luật | Cách làm | Phù hợp khi | Rủi ro |
|---|---|---|---|
| Nguồn chính luôn thắng | Giá trị ở ứng dụng nguồn chính ghi đè bên kia | Đội đã thống nhất chỉ sửa ở một nơi | Người sửa ở bên phụ mất công mà không biết |
| Sửa sau cùng thắng | So thời điểm cập nhật hai bên, lấy bản mới hơn | Hai nhóm sửa ngang nhau, ít khi trùng thời điểm | Đồng hồ hai hệ thống lệch, múi giờ khác nhau dẫn tới chọn sai |
| Chia theo trường | Mỗi trường có một nguồn chính riêng | Mỗi nhóm phụ trách một loại thông tin | Phải ghi rõ bảng phân công trường, người mới dễ quên |
| Không tự quyết | Ghi xung đột ra bảng để người xem và chọn | Dữ liệu nhạy cảm như công nợ, hạn mức | Tốn người, bảng chờ dễ bị bỏ quên |
Luật nào cũng có giá của nó. Điều tệ nhất là không chọn luật nào, để luồng tự chạy theo thứ tự sự kiện tới, kết quả thay đổi mỗi lần mà không ai giải thích được.
Ví dụ giả định: danh sách khách giữa phần mềm bán hàng và công cụ email
Ví dụ giả định: một cửa hàng mỹ phẩm thiên nhiên ở Huế bán cả tại quầy lẫn online, có khoảng ba nghìn khách trong phần mềm bán hàng và muốn gửi thư chăm sóc định kỳ qua một công cụ email. Chủ cửa hàng quyết định phần mềm bán hàng là nguồn chính cho tên, email và số điện thoại; công cụ email là nguồn chính cho trạng thái đồng ý nhận thư, vì khách bấm huỷ nhận ngay trong thư.
Luồng thứ nhất chạy một chiều: khi khách được tạo hoặc sửa trong phần mềm bán hàng, luồng tìm trong công cụ email theo mã khách đã lưu ở một trường tuỳ chỉnh; thấy thì cập nhật tên và email, không thấy thì tạo mới, nhưng chỉ khi khách đã tích ô đồng ý nhận thư lúc mua hàng. Luồng thứ hai cũng một chiều theo hướng ngược lại, chỉ chép đúng một trường: khi khách huỷ nhận thư, luồng ghi trạng thái đó vào phần mềm bán hàng để nhân viên quầy biết không mời khách đăng ký lại.
Hai luồng chạm vào hai nhóm trường khác nhau, nên không có xung đột và không có vòng lặp. Mỗi chủ nhật, một luồng đối chiếu xuất danh sách mã khách hai bên, đếm và liệt kê chênh lệch vào bảng cho chủ cửa hàng xem. Thiết kế này đơn giản hơn nhiều so với đồng bộ hai chiều mọi trường, mà vẫn đáp ứng đủ nhu cầu.
Vòng lặp đồng bộ và cách cắt
Vòng lặp xảy ra với đồng bộ hai chiều dựng bằng hai luồng: luồng một nghe sửa ở A rồi ghi sang B, luồng hai nghe sửa ở B rồi ghi sang A. Khi luồng một ghi sang B, B coi đó là một lần sửa và kích hoạt luồng hai, luồng hai ghi lại sang A, A lại kích hoạt luồng một. Hai luồng cứ thế chạy mãi, tiêu hết lượt chạy của gói công cụ trong một buổi và có thể bị API chặn vì gọi quá nhiều.
Có ba cách cắt phổ biến. Cách thứ nhất: so giá trị trước khi ghi, nếu bên đích đã có đúng giá trị đó thì không ghi gì, vòng lặp tự tắt sau một lượt. Cách thứ hai: đánh dấu nguồn sửa, ví dụ dùng một tài khoản riêng cho luồng, rồi luồng bỏ qua mọi sự kiện do chính tài khoản đó tạo ra. Cách thứ ba: dùng một trường đánh dấu thời điểm đồng bộ gần nhất, sự kiện nào xảy ra trong vài giây sau lần đồng bộ thì bỏ qua.
Dù dùng cách nào, đặt một chốt an toàn: báo vào nhóm chat khi số lần chạy của luồng trong một giờ vượt mức bình thường nhiều lần. Ví dụ giả định: luồng thường chạy khoảng hai mươi lần một giờ, đặt báo khi vượt hai trăm. Phát hiện vòng lặp sau mười phút đỡ hơn nhiều so với phát hiện khi nhận hoá đơn cuối tháng.
Xoá bản ghi, dữ liệu cá nhân và việc đối chiếu định kỳ
Xoá là phần hay bị bỏ quên. Khi khách yêu cầu xoá thông tin ở phần mềm bán hàng, bản ghi bên công cụ email có bị xoá theo không? Nếu không, bạn vẫn gửi thư cho một người đã yêu cầu ngừng, vừa gây phiền vừa có thể trái quy định về bảo vệ dữ liệu cá nhân. Nhiều ứng dụng không gửi sự kiện khi bản ghi bị xoá, nên phải có cách khác: đánh dấu trạng thái ngừng liên hệ thay vì xoá thẳng, và đồng bộ trạng thái đó.
Chỉ đồng bộ những trường mà ứng dụng đích cần. Công cụ email cần tên gọi, email và trạng thái đồng ý nhận thư; nó không cần địa chỉ nhà, ngày sinh hay lịch sử công nợ. Mỗi trường được chép thêm là một nơi nữa phải bảo vệ, phải xoá khi khách yêu cầu.
Cuối cùng, đồng bộ nào chạy lâu cũng lệch dần vì webhook lỡ, lỗi tạm thời, người sửa tay. Đặt một việc đối chiếu định kỳ, mỗi tuần hoặc mỗi tháng: đếm số bản ghi hai bên, lấy danh sách khoá ghép có ở bên này mà không có bên kia, ghi ra bảng để xử lý. Có thể nhờ chatbot AI so hai danh sách khoá đã xuất ra, nhưng chỉ đưa cột mã, không đưa thông tin cá nhân.
Câu hỏi thường gặp
Có nên đồng bộ hai chiều ngay từ đầu không?
Không nên. Bắt đầu bằng một chiều với một nguồn chính, chạy ổn vài tuần rồi mới cân nhắc mở thêm chiều ngược, và ưu tiên chia theo trường để không trường nào bị sửa ở hai nơi.
Đồng bộ tạo ra hàng trăm bản ghi trùng thì xử lý thế nào?
Tắt luồng ngay. Dùng chức năng gộp bản ghi của ứng dụng đích hoặc xuất danh sách ra bảng tính để lọc trùng theo khoá ghép. Sửa luồng theo mẫu tìm trước tạo sau, thử trên dữ liệu giả, rồi mới bật lại.
Hai ứng dụng có múi giờ khác nhau ảnh hưởng gì?
Ảnh hưởng tới luật sửa sau cùng thắng và tới các bộ lọc theo ngày. Đưa mọi thời điểm về cùng một múi giờ trước khi so sánh, thường là theo chuẩn UTC, rồi đổi sang giờ Việt Nam khi hiển thị.
Khoá ghép nên lưu ở đâu bên ứng dụng đích?
Trong một trường tuỳ chỉnh riêng, đặt tên rõ như mã khách bên phần mềm bán hàng, và dặn đội không sửa tay trường này. Nếu ứng dụng đích không cho trường tuỳ chỉnh, dùng email đã chuẩn hoá làm khoá và chấp nhận rủi ro bỏ sót.
Đồng bộ có làm tăng chi phí công cụ kết nối không?
Có, vì mỗi lần sửa ở nguồn là một lần chạy, và vòng lặp có thể làm số lần chạy tăng vọt. Ước lượng số lần sửa mỗi ngày, cộng thêm bước tìm trước khi ghi, rồi so với hạn mức gói đang dùng.
Khách yêu cầu xoá dữ liệu thì luồng đồng bộ cần làm gì?
Xoá hoặc ẩn danh bản ghi ở mọi ứng dụng đã nhận bản sao, không chỉ ở nguồn. Lập danh sách các nơi dữ liệu khách đi qua, kể cả lịch sử chạy của công cụ kết nối, để không bỏ sót.