Bảy vấn đề kỹ thuật khiến đồng bộ hóa thời gian thực gần như không thể
Đồng bộ hóa dữ liệu thời gian thực giữa các hệ thống doanh nghiệp là một thách thức kỹ thuật khổng lồ thường bị đánh giá thấp. Bài viết này phân tích bảy vấn đề cốt lõi từ giới hạn API, xung đột dữ liệu hai chiều cho đến sự thay đổi cấu trúc dữ liệu, giải thích tại sao hầu hết các dự án nội bộ đều thất bại.

Tôi đã dành 18 tháng để cố gắng khiến hai cơ sở dữ liệu đồng nhất với nhau. Không phải "cuối cùng sẽ đồng bộ", không phải trong vòng 15 phút. Mà là đồng bộ thời gian thực, hai chiều, và không mất dữ liệu.
Phiên bản đầu tiên bị sập sau 10.000 bản ghi. Phiên bản thứ hai xử lý được khối lượng nhưng lại làm hỏng các trường dữ liệu khi cả hai hệ thống ghi vào cùng một bản ghi trong cùng một giây. Phiên bản thứ ba giải quyết được xung đột nhưng lại bị lỗi mỗi khi Salesforce thêm một trường tùy chỉnh. Tôi đã vứt bỏ cả ba và bắt đầu lại từ đầu.
Đó là vào năm 2022. Khi đó tôi có bằng kinh doanh, sáu tháng tự học lập trình, và một vấn đề không ngừng ám ảnh tôi: Tại sao việc giữ cho một CRM và một cơ sở dữ liệu đồng bộ hóa theo thời gian thực lại khó đến thế?
Ba năm và một đợt Y Combinator sau đó, Stacksync đã đồng bộ hóa hàng triệu bản ghi trên hơn 200 hệ thống doanh nghiệp với độ trễ dưới một giây. Tôi muốn giải thích tại sao vấn đề này lại khó đến vậy, bởi vì hầu hết các đội ngũ kỹ thuật đều đánh giá thấp nó cho đến khi họ đã sáu tháng lún sâu vào một dự án thất bại.
1. Polling là lời nói dối bạn tự nói với mình
Cách tiếp cận đầu tiên mà mọi đội ngũ đều thử là polling. Bạn thiết lập một cron job kiểm tra các thay đổi từ Salesforce mỗi năm phút. Có thể là mỗi một phút nếu bạn tham vọng. Bạn kéo dữ liệu thay đổi (delta), ghi vào Postgres và coi như xong.
Nó hoạt động trong bản demo. Nó sụp đổ trong môi trường sản xuất (production).
Polling có một giới hạn. Bạn không thể poll nhanh hơn mức cho phép của API, và các API doanh nghiệp không được xây dựng để đọc tần suất cao. Salesforce áp đặt giới hạn cuộc gọi API hàng ngày phụ thuộc vào cấp phép của bạn. Một công ty quy mô vừa với 100 người dùng nhận được khoảng 100.000 cuộc gọi API mỗi ngày. Nghe có vẻ nhiều cho đến khi bạn nhận ra rằng một truy vấn SOQL duy nhất kiểm tra các bản ghi đã cập nhật trên năm đối tượng sẽ tiêu tốn năm cuộc gọi. Chạy truy vấn đó mỗi phút và bạn sẽ tiêu tốn 7.200 cuộc gọi vào cuối ngày chỉ cho một công việc đồng bộ. Thêm hệ thống thứ hai, thêm nhiều đối tượng hơn, thêm bất kỳ sự phức tạp nào, và bạn sẽ chạm trần trước giờ ăn trưa.
Vấn đề sâu hơn của polling là về thời gian. Giữa các lần poll, thế giới thay đổi. Một nhân viên bán hàng cập nhật một cơ hội (opportunity) lúc 10:01:14. Lần poll của bạn chạy lúc 10:01:00 và tiếp theo là 10:02:00. Trong 46 giây, cơ sở dữ liệu của bạn sai. Nếu bất kỳ hệ thống nào xuôi dòng đọc bản ghi đó trong 46 giây đó, cổng khách hàng sẽ hiển thị giá lỗi thời, công cụ nội bộ kích hoạt quy trình sai, hoặc một báo cáo được gửi đi với số liệu của ngày hôm qua.
Bốn mươi sáu giây nghe có vẻ nhỏ nhặt. Hãy nhân con số đó với mọi bản ghi, mọi đối tượng, mọi hệ thống được kết nối, và bạn sẽ có một hạ tầng dữ liệu không bao giờ hoàn toàn nhất quán. Bạn chỉ hy vọng rằng cửa sổ không nhất quán đó đủ nhỏ để không ai nhận ra.
Chúng tôi đã nhận ra.
2. Change Data Capture nghe có vẻ đơn giản cho đến khi bạn xây dựng nó
Giải pháp thay thế cho polling là kiến trúc hướng sự kiện (event-driven). Thay vì hỏi "cái gì đã thay đổi?" theo thời gian, bạn lắng nghe các sự kiện thay đổi khi chúng xảy ra. Salesforce cung cấp Change Data Capture (CDC). HubSpot có webhooks. NetSuite có các trình kích hoạt SuiteScript.
Mỗi triển khai là khác nhau. Mỗi cái có các đảm bảo giao hàng, định dạng sự kiện, logic thử lại (retry logic) và chế độ lỗi riêng.
Salesforce CDC xuất bản các sự kiện lên một kênh API truyền trực tuyến với cửa sổ lưu trữ 72 giờ. Nếu trình lắng nghe của bạn bị sập lâu hơn thế, các sự kiện sẽ biến mất. Không có phát lại. Bạn cần phát hiện khoảng trống, quay lại poll toàn bộ hoặc tăng dần để tái tạo cửa sổ bị thiếu, sau đó tiếp tục truyền trực tuyến mà không trùng lặp bản ghi hoặc bỏ lỡ điểm chuyển tiếp. Logic phục hồi này một mình đã khiến đội ngũ của chúng tôi mất nhiều tuần để làm đúng, và nó vẫn cần tinh chỉnh sau khi các trường hợp góc (edge cases) xuất hiện trong môi trường sản xuất.
Webhooks của HubSpot kích hoạt khi thay đổi thuộc tính nhưng chúng được đóng gói (batch). Bạn nhận được một tải trọng chứa nhiều thay đổi trên nhiều bản ghi, và thứ tự trong lô đó không được đảm bảo. Nếu bản ghi A được cập nhật trước bản ghi B, bạn có thể nhận B trước. Đối với các bản ghi độc lập, thứ tự không quan trọng. Nhưng đối với các bản ghi liên quan mà bản cập nhật cha phải đến trước tham chiếu con, việc giao hàng sai thứ tự sẽ làm hỏng các quan hệ khóa ngoại của bạn.
Các trình kích hoạt SuiteScript của NetSuite kích hoạt đồng bộ bên trong giao dịch. Nếu logic đồng bộ của bạn quá chậm, thao tác lưu của người dùng trong NetSuite bị treo. Bạn đang trực tiếp trên đường dẫn quan trọng của sản phẩm của một bên khác. Hết giờ, và người dùng sẽ thấy một lỗi họ không thể chẩn đoán.
M every connector đòi hỏi triển khai CDC riêng, ngữ nghĩa thử lại riêng và đường dẫn phục hồi lỗi riêng. Không có tiêu chuẩn chung. Chúng tôi xây dựng và duy trì một đường ống nhập khẩu riêng cho từng trong số hơn 200 hệ thống mà Stacksync kết nối.
3. Đồng bộ hai chiều là bài toán đồng thuận trong hệ thống phân tán
Đồng bộ một chiều là một đường ống. Dữ liệu chảy từ A đến B. Nếu B nhận được một bản ghi mà nó đã có, bạn ghi đè lên nó. Nếu B nhận được một bản ghi mới, bạn chèn nó. Logic vừa vặn trong một trang mã giả.
Đồng bộ hai chiều là một bài toán hệ thống phân tán. Cả A và B đều có thể ghi vào cùng một bản ghi cùng một lúc. Không hệ thống nào biết hệ thống kia cũng đang thực hiện thay đổi. Không có đồng hồ chung, không có nhật ký giao dịch chung, không có điều phối viên ngồi giữa chúng để thực thi thứ tự.
Đây là một biến thể của cùng một vấn đề mà các cơ sở dữ liệu đã giải quyết hàng thập kỷ trước với các giao thức đồng thuận phân tán như Paxos và Raft. Sự khác biệt là bạn không kiểm soát điểm cuối nào. Salesforce sẽ không triển khai giao thức đồng thuận của bạn. Postgres cũng vậy. Bạn đang đồng bộ hóa hai hệ thống có chủ quyền không biết đến sự tồn tại của nhau.
Giải pháp ngây thơ là "ghi cuối cùng thắng" (last write wins). Dấu thời gian nào muộn hơn sẽ được ưu tiên. Điều này thất bại vì ba lý do.
Thứ nhất, đồng hồ bị trôi (drift). Đồng hồ máy chủ của Salesforce và đồng hồ cơ sở dữ liệu của bạn không được đồng bộ hóa hoàn hảo. NTP giảm độ trôi xuống mili-giây, nhưng mili-giây lại quan trọng khi hai lần ghi xảy ra trong cùng một giây.
Thứ hai, độ chi tiết (granularity) thay đổi. Dấu thời gian của Salesforce ghi lại các thay đổi ở cấp bản ghi. Nếu một nhân viên cập nhật số điện thoại lúc 10:01:00 và hệ thống của bạn cập nhật email lúc 10:01:00, "ghi cuối cùng thắng" ở cấp bản ghi sẽ loại bỏ một trong những thay đổi đó. Bạn cần phát hiện xung đột ở cấp trường (field-level), nghĩa là theo dõi dấu thời gian của từng trường riêng lẻ trên cả hai hệ thống và hợp nhất chúng độc lập. Mô hình dữ liệu cho việc này một mình đã rất đáng kể.
Thứ ba, ý định quan trọng. Một số trường nên luôn tuân theo CRM vì đội ngũ bán hàng sở hữu chúng. Các trường khác nên luôn tuân theo cơ sở dữ liệu vì một đường ống tự động sở hữu chúng. Chiến lược giải quyết xung đột tổng thể coi tất cả các trường như nhau, điều này sai đối với mọi trường hợp sử dụng thực tế. Bạn cần các chính sách giải quyết xung đột theo từng trường, từng đối tượng, từng hướng mà khách hàng có thể cấu hình mà không cần viết mã.
Chúng tôi đã dành bốn tháng cho động cơ giải quyết xung đột của mình trước khi có bất kỳ khách hàng nào sử dụng nó. Đây là phần khó nhất của hệ thống và ít được nhìn thấy nhất.
4. Schema là một mục tiêu di động
Các hệ thống doanh nghiệp không có schema cố định. Quản trị viên Salesforce thêm các trường tùy chỉnh hàng tuần. Người dùng HubSpot tạo các thuộc tính tùy chỉnh cho mọi chiến dịch mới. Tư vấn NetSuite xây dựng các loại bản ghi tùy chỉnh cho quy trình làm việc cụ thể theo ngành.
Khi một trường mới xuất hiện trong hệ thống nguồn, lớp đồng bộ của bạn có ba lựa chọn: bỏ qua, thất bại, hoặc thích nghi. Bỏ qua có nghĩa là mất dữ liệu. Thất bại có nghĩa là thời gian chết (downtime). Thích nghi có nghĩa là hệ thống của bạn phải phát hiện thay đổi schema, xác định loại và ràng buộc của nó, tạo cột tương ứng trong cơ sở dữ liệu đích, điền lại các giá trị lịch sử cho trường đó, và tiếp tục đồng bộ, tất cả mà không làm gián đoạn việc đồng bộ đang diễn ra của mọi trường khác.
Đây là sự tiến hóa schema, và nó phải xảy ra tự động vì bạn không thể yêu cầu khách hàng điều chỉnh schema cơ sở dữ liệu của họ thủ công mỗi khi một quản trị viên Salesforce thêm một trường danh sách chọn.
Các trường hợp góc là nơi mọi thứ trở nên tàn khốc. Điều gì xảy ra khi một trường được đổi tên? Lớp đồng bộ của bạn ánh xạ nó theo tên API, cái mà trong Salesforce là bất biến, nhưng trong HubSpot nó có thể thay đổi. Điều gì xảy ra khi loại trường thay đổi từ văn bản sang số? Cột Postgres của bạn là VARCHAR và nguồn hiện đang gửi số nguyên. Điều gì xảy ra khi một trường bị xóa? Bạn có xóa cột và mất dữ liệu lịch sử, hay đánh dấu nó là lỗi thời và giữ lại?
Mọi thay đổi schema là một sự kiện di chuyển phải thực thi trực tiếp, không có khóa, trên một bảng đang chủ động nhận ghi từ cả hai hướng. Chúng tôi xử lý hàng nghìn trường hợp như vậy mỗi tuần trên cơ sở khách hàng của mình.
5. Giới hạn tốc độ được thiết kế để đối đầu với bạn
Các nhà cung cấp Enterprise SaaS đặt giới hạn tốc độ API để bảo vệ hạ tầng của họ khỏi các tích hợp hoạt động sai. Nó hợp lý từ góc độ của họ và đối đầu từ góc độ của bạn.
Salesforce thực thi cả giới hạn yêu cầu hàng ngày và đồng thời. HubSpot giới hạn tốc độ ở 100 yêu cầu mỗi 10 giây cho ứng dụng OAuth. Mô hình đồng thời của NetSuite giới hạn bạn ở một vài kết nối đồng thời tùy thuộc vào giấy phép của khách hàng.
Động cơ đồng bộ của bạn cần là người tiêu dùng các API này hiệu quả nhất có thể. Mọi cuộc gọi không cần thiết là một cuộc gọi khách hàng của bạn không thể sử dụng cho việc khác. Mọi đợt bùng nổ kích hoạt giới hạn tốc độ sẽ tạo ra một danh sách chờ làm giảm độ trễ SLA của bạn.
Chúng tôi đã triển khai quản lý tốc độ thích nghi giám sát hạn ngạch còn lại theo thời gian thực và điều chỉnh thông lượng động. Khi hạn ngạch thấp, động cơ chuyển sang kích thước lô lớn hơn với ít cuộc gọi hơn. Khi hạn ngạch dồi dào, nó chạy các lô nhỏ hơn, thường xuyên hơn để có độ trễ thấp hơn. Hệ thống tối ưu hóa liên tục cho sự đánh đổi giữa tính mới và tiêu thụ hạn ngạch, và điểm tối ưu thay đổi trong ngày khi các tích hợp khác của khách hàng cạnh tranh cho cùng một pool.
Điều này vô hình với khách hàng. Họ thấy đồng bộ dưới một giây. Bên dưới, động cơ đang chơi một trò chơi phân bổ tài nguyên thời gian thực với các ràng buộc mà nó không kiểm soát.
6. Sắp xếp thứ tự trong các hệ thống phân tán
Khi bạn đồng bộ một Tài khoản Salesforce và các Liên hệ con của nó sang Postgres, Tài khoản phải tồn tại trước khi các Liên hệ tham chiếu nó. Nếu Liên hệ đến trước và Tài khoản chưa tồn tại trong Postgres, việc chèn khóa ngoại sẽ thất bại.
Trong một cơ sở dữ liệu hệ thống đơn, điều này được giải quyết bằng các giao dịch. Bạn bọc cả hai lần chèn trong một giao dịch và chúng hoặc cùng thành công hoặc cùng hoàn tác. Trên các hệ thống, không có ranh giới giao dịch. Các sự kiện đến khi chúng đến. Sự kiện tạo Tài khoản có thể bị chậm bởi độ trễ mạng, thứ tự kênh CDC, hoặc một chu kỳ thử lại, trong khi sự kiện tạo Liên hệ đến ngay lập tức.
Bạn cần một đồ thị phụ thuộc hiểu các mối quan hệ giữa các đối tượng, giữ các bản ghi con cho đến khi cha của chúng tồn tại, và xử lý chúng theo đúng thứ tự tô pô. Đồ thị này phải tính đến các phụ thuộc vòng tròn (Đối tượng A tham chiếu Đối tượng B cái mà tham chiếu Đối tượng A), tự tham chiếu (một Tài khoản có Tài khoản cha), và các tra cứu đa hình (một trường có thể tham chiếu các loại đối tượng khác nhau tùy thuộc vào bản ghi).
Chúng tôi xử lý các đồ thị phụ thuộc này theo thời gian thực khi các sự kiện đến, sắp xếp lại chúng trên bay mà không cần đệm lâu hơn cần thiết. Làm sai điều này có nghĩa là mất dữ liệu (bỏ các sự kiện không thể sắp xếp) hoặc hỏng dữ liệu (chèn các bản ghi với các tham chiếu treo lơ lửng).
7. Hiệu ứng cộng hưởng
Mỗi vấn đề này là khó khi đứng một mình. Cùng nhau, chúng nhân lên.
Một thay đổi schema kích hoạt một sự di chuyển tạm thời tăng các cuộc gọi API, điều này đánh trần giới hạn tốc độ, tạo ra một danh sách chờ, làm chậm các sự kiện, phá vỡ đảm bảo thứ tự, gây ra một bản ghi con đến trước cha của nó, kích hoạt một sự giữ phụ thuộc, kéo dài độ trễ, nghĩa là cửa sổ giải quyết xung đột mở rộng, nghĩa là nhiều xung đột cần được giải quyết, nghĩa là nhiều ghi hơn, tiêu thụ nhiều hạn ngạch API hơn.
Một sự nhiễu loạn lan truyền qua mọi lớp. Hệ thống phải hấp thụ các sự cố này mà không mất dữ liệu, không có đỉnh độ trễ nhìn thấy bởi khách hàng, và không có sự can thiệp của con người.
Đó là lý do tại sao hầu hết các dự án đồng bộ nội bộ thất bại. Đội ngũ xây dựng một cái gì đó hoạt động cho trường hợp sử dụng đầu tiên, trong tháng đầu tiên, với 10.000 bản ghi đầu tiên. Sau đó schema thay đổi. Sau đó khối lượng tăng. Sau đó hệ thống thứ hai được thêm vào. Sau đó một trường hợp góc tạo ra một sự cố, và việc gỡ lỗi bắt buộc bạn phải truy xuất các sự kiện qua ba hệ thống, hai kênh CDC, một nhật ký giải quyết xung đột, và một đồ thị phụ thuộc.
Hầu hết các đội ngũ bỏ cuộc và chuyển sang ETL theo lô. Họ chấp nhận độ trễ 15 phút vì giải pháp thay thế là xây dựng một động cơ hệ thống phân tán mà rất ít đội ngũ kỹ thuật có thời gian, ngân sách, hoặc chuyên môn để duy trì.
Bài học rút ra
Đồng bộ hóa các hệ thống doanh nghiệp theo thời gian thực là một bài toán hệ thống phân tán mặc một bộ đồ SaaS. Nó trông giống như một đường ống dữ liệu đơn giản cho đến khi bạn bóc lớp đầu tiên và tìm thấy các thuật toán đồng thuận, tiến hóa schema, giới hạn tốc độ đối đầu, và sắp xếp nhân quả đều được nén vào một hệ thống duy nhất phải chạy 24/7 mà không mất một bản ghi nào.
Chúng tôi đã xây dựng Stacksync trong ba năm. Vấn đề chưa giảm đi. Mọi kết nối mới, mọi trường hợp góc mới, mọi cột mốc quy mô mới đều tiết lộ một lớp phức tạp khác vô hình ở cấp độ trước đó.
Nếu đội ngũ của bạn đang cân nhắc xây dựng cái này nội bộ, lời khuyên chân thành của tôi là hãy suy nghĩ kỹ về tổng chi phí. Không phải chi phí của phiên bản đầu tiên, cái mà sẽ hoạt động. Mà là chi phí của phiên bản thứ 40, cái là phiên bản thực sự sống sót trong môi trường sản xuất ở quy mô lớn.
Kỹ thuật là có thể. Tôi biết vì chúng tôi đã làm nó. Câu hỏi là liệu kỹ thuật đó có phải là cách sử dụng tốt nhất thời gian của đội ngũ bạn không khi công việc thực sự của họ là xây dựng sản phẩm của bạn, không phải duy trì đường ống bên dưới nó.
Sự phân biệt đó có xu hướng làm rõ quyết định rất nhanh.
Bài viết liên quan

Phần mềm
Anthropic ra mắt Claude Opus 4.7: Nâng cấp mạnh mẽ cho lập trình nhưng vẫn thua Mythos Preview
16 tháng 4, 2026

Công nghệ
Qwen3.6-35B-A3B: Quyền năng Lập trình Agentic, Nay Đã Mở Cửa Cho Tất Cả
16 tháng 4, 2026

Công nghệ
Spotify thắng kiện 322 triệu USD từ nhóm pirate Anna's Archive nhưng đối mặt với bài toán thu hồi
16 tháng 4, 2026
