Bot bắt đầu đưa ra cùng một câu trả lời hai hoặc ba lần bất cứ khi nào người dùng nhấn nút gửi liên tục. Sự trùng lặp này chỉ xuất hiện đối với những người gõ đủ nhanh để gửi đi nhiều tin nhắn trước khi AI bắt đầu suy nghĩ, và nó đã ẩn mình trong môi trường production một thời gian dài vì mô hình này rất hiếm khi xảy ra. Một khóa cơ sở dữ liệu (database lock) bị giải phóng quá sớm – chỉ vài mili giây sau khi được thiết lập – đã khiến cuộc hội thoại không được bảo vệ, cho phép nhiều tiến trình cùng trả lời một prompt giống nhau.

Tại sao khóa (lock) lại thất bại

Mã nguồn thực hiện lấy khóa trong một lần gọi cơ sở dữ liệu duy nhất, sau đó ngay lập tức chuyển quyền điều khiển lại cho trình xử lý yêu cầu (request handler). Thời gian tồn tại của khóa chỉ tính bằng mili giây, ngắn hơn nhiều so với thời gian mô hình AI cần để tạo ra phản hồi. Vào thời điểm mô hình bắt đầu làm việc, khóa đã biến mất, vì vậy không có gì ngăn cản yêu cầu thứ hai chiếm lấy bản ghi cuộc hội thoại đó và đưa ra một câu trả lời khác.

Hai triệu chứng đã xuất hiện:

  • Các câu trả lời giống hệt nhau được gửi liên tiếp.
  • Các câu trả lời được diễn đạt lại một chút xuất hiện cho cùng một câu hỏi, vì mỗi tiến trình tự xây dựng prompt riêng từ cùng một đầu vào của người dùng.

Vì hầu hết người dùng đều tạm dừng giữa các tin nhắn, lỗi này đã không bị phát hiện. Chỉ những người gõ phím nhanh mới kích hoạt được tình trạng tranh chấp (race condition), và các trường hợp này rất hiếm.

Cách khắc phục nửa vời không hiệu quả

Phản ứng đầu tiên là thêm một khoảng trễ ngắn sau khi tin nhắn đến, với hy vọng "debounce" các đầu vào nhanh. Điều đó có hiệu quả khi hai tin nhắn đến liên tiếp nhau, nhưng nó sẽ thất bại nếu tin nhắn thứ ba xuất hiện trong khi AI vẫn đang tạo văn bản.

Một vấn đề thứ hai nảy sinh khi các bộ hẹn giờ (timers) và dữ liệu hội thoại nằm trong cùng một kho lưu trữ (storage bucket). Khi bot hoàn tất việc xử lý một yêu cầu, nó ghi đè lên bản ghi bộ hẹn giờ, về cơ bản là tự xóa bỏ quá trình đếm ngược của chính nó. Hệ thống mất dấu những tin nhắn nào đã được trả lời, tạo điều kiện cho sự trùng lặp tiếp diễn.

Xây dựng cơ chế bảo vệ đáng tin cậy: bộ đếm phiên bản, bộ hẹn giờ biệt lập và một lease

Nhóm đã thiết kế lại luồng xử lý dựa trên ba trụ cột:

  • Bộ đếm phiên bản (Version counter) – mỗi tin nhắn đến sẽ tăng một bộ đếm được lưu cùng với cuộc hội thoại. Bộ đếm này cho hệ thống biết đã có bao nhiêu tin nhắn đến kể từ câu trả lời cuối cùng, giúp dễ dàng phát hiện đầu vào mới trong khi phản hồi đang được tạo.
  • Cửa sổ debounce chuyên dụng – các bộ hẹn giờ hiện nằm trong một khu vực lưu trữ riêng biệt, tách biệt khỏi dữ liệu hội thoại. Việc giới hạn cứng thời gian debounce giúp ngăn người dùng làm treo bot vô thời hạn.
  • Hợp đồng thuê phiên (Session lease) – khóa ban đầu được thay thế bằng một lease có chứa dấu thời gian hết hạn rõ ràng. Lease được chiếm giữ bằng thao tác compare-and-swap (CAS): tiến trình đọc giá trị lease hiện tại, chỉ ghi giá trị mới nếu giá trị cũ khớp, và từ đó giành được quyền độc quyền đối với cuộc hội thoại. Nếu tiến trình bị lỗi (crash), lease sẽ tự động hết hạn, giải phóng cuộc hội thoại cho trình xử lý tiếp theo.

Cách thức hoạt động của pipeline mới

  1. Tin nhắn đến – hệ thống tăng bộ đếm phiên bản và (thiết lập lại) bộ hẹn giờ debounce. Nó phản hồi lại client ngay lập tức mà không cần khởi động AI.
  2. Hết thời gian chờ (Timer expiration) – trình xử lý bộ hẹn giờ cố gắng giành lấy lease. Nếu CAS thành công, trình xử lý sẽ tiếp tục; nếu không, nó sẽ lùi lại vì biết rằng một tiến trình khác đã sở hữu cuộc hội thoại.
  3. Kiểm tra đầu vào mới – trình xử lý so sánh bộ đếm phiên bản hiện tại với giá trị mà nó đã ghi lại khi bộ hẹn giờ bắt đầu. Nếu bộ đếm đã tăng lên, nó sẽ gộp các tin nhắn đang chờ thành một prompt duy nhất.
  4. Tạo phản hồi – mô hình AI chạy một lần duy nhất, tạo ra một câu trả lời bao quát tất cả các đầu vào gần đây của người dùng.
  5. Kiểm tra tính hợp lệ cuối cùng – ngay trước khi phản hồi được gửi đi, trình xử lý đọc lại bộ đếm phiên bản một lần nữa. Nếu có tin nhắn mới hơn đến trong quá trình tạo, phản hồi sẽ bị hủy bỏ và tiến trình sẽ khởi động lại bộ hẹn giờ, đảm bảo không có câu trả lời lỗi thời nào đến tay người dùng.

Cách tiếp cận này loại bỏ các phản hồi trùng lặp, giới hạn thời gian một cuộc hội thoại có thể bị đình trệ và tự động phục hồi sau các lỗi tiến trình vì lease sẽ tự hết hạn.

Bài học rút ra

Một chiếc khóa biến mất trước khi phần quan trọng (critical section) bắt đầu thì sẽ không mang lại bất kỳ sự bảo vệ nào. Bằng cách thay thế một khóa cơ sở dữ liệu thoáng qua bằng một lease có thời hạn rõ ràng và tách biệt các bộ hẹn giờ khỏi dữ liệu hội thoại, bot hiện đã đảm bảo được một phản hồi duy nhất và cập nhật nhất ngay cả khi người dùng gõ với tốc độ ánh sáng. Sự cố này nhấn mạnh một bài học vượt thời gian: các biện pháp bảo vệ tính đồng thời (concurrency safeguards) phải tồn tại lâu hơn công việc mà chúng bảo vệ, nếu không chúng sẽ trở thành những rào cản vô hình để các lỗi lọt qua.