← Tất cả bài viếtHậu trường build

Stateless qua Redis — lần con bot suýt loạn trí giữa nhiều người

2026-06-08

Có một đêm tôi tưởng mình vừa làm con bot thông minh hơn, hoá ra suýt làm nó mất trí nhớ.

Bot CSKH của tôi chạy ngon trên một server. Khi lượng tin nhắn tăng, tôi làm điều ai cũng nghĩ tới: thêm một server thứ hai, đặt một cái cân tải (load balancer) phía trước chia việc cho hai bên. Nhiều tay làm thì nhanh hơn, đúng không?

Sai — theo một kiểu rất tinh vi. Bot bắt đầu cư xử như người lú lẫn.

Triệu chứng: bot quên mình vừa nói gì

Khách nhắn ba câu liên tiếp. Câu một rơi vào server A, câu hai vào server B, câu ba lại về A. Vấn đề là trí nhớ hội thoại của bot nằm trong bộ nhớ của từng server. Server B không hề biết server A vừa nói gì với khách hai giây trước.

Thế là bot trả lời câu hai như thể cuộc trò chuyện chưa từng bắt đầu — hỏi lại đúng thứ khách vừa nói, chào lại từ đầu, quên mất khách đang hỏi giá liệu trình nào.

Tệ hơn: Facebook đôi khi gửi lại cùng một tin nhắn (đó là cơ chế bình thường của họ để chống mất tin). Trước đây một server tự nhớ "tin này xử lý rồi, bỏ qua". Giờ tin gửi lại rơi vào server khác — server đó chưa thấy bao giờ — nên cả hai cùng trả lời. Khách nhận hai tin y hệt nhau. Trông như bot bị giật.

Không có dòng lỗi nào đỏ lên. Code vẫn "chạy". Nó chỉ sai một cách rất con người: hai cái đầu, không chia sẻ ký ức.

Gốc rễ: bộ nhớ trong tiến trình là một lời nói dối tiện lợi

Khi mới build, tôi để mọi "trạng thái" sống trong RAM của tiến trình — lịch sử hội thoại, danh sách tin đã xử lý. Với một server thì hoàn hảo: nhanh, đơn giản, không cần gì thêm.

Nhưng RAM của một tiến trình là riêng của nó. Khoảnh khắc bạn có hai tiến trình, mỗi cái ôm một nửa sự thật. Và cái "nhanh, đơn giản" ban đầu trở thành lý do con bot không thể lớn lên — không thể thêm server, mà mỗi lần tôi deploy bản mới (khởi động lại tiến trình) thì toàn bộ trí nhớ bốc hơi.

Đây chính là ranh giới giữa một prototype và một dịch vụ.

Lời giải: đẩy trí nhớ ra ngoài, vào một chỗ dùng chung

Cách sửa không phải làm bot khôn hơn — mà làm mỗi server "không ôm gì cả" (đó là nghĩa của stateless). Mọi ký ức được chuyển ra một kho chung mà cả hai server cùng đọc cùng ghi. Tôi dùng Redis — một kho dữ liệu trong bộ nhớ, cực nhanh, sống tách khỏi tiến trình bot.

Hai thứ cốt lõi được đưa sang Redis:

  • Lịch sử hội thoại → một danh sách trong Redis, gắn khoá theo từng khách (trang nào + người nào), chỉ giữ ~12 lượt gần nhất và tự hết hạn sau 7 ngày. Giờ server nào nhận tin cũng đọc được đúng mạch chuyện.
  • Chống trùng tin → một thao tác Redis nguyên tử: "ghi mã tin này, nếu chưa từng có thì báo mới, nếu có rồi thì báo cũ". Vì nó nguyên tử và dùng chung, chỉ một server giành được quyền xử lý, server kia tự lùi. Hết cảnh trả lời hai lần.

Sau khi làm xong, hai server trở thành hai bản sao thay thế được cho nhau. Khách nhắn vào đâu cũng như nhau. Tôi deploy bản mới giữa giờ cao điểm, trí nhớ vẫn còn nguyên vì nó không nằm trong tiến trình vừa bị khởi động lại nữa.

Hai cú vấp tôi muốn kể thật

Build-in-public thì phải kể cả chỗ trượt chân:

1. Cái load balancer không hề cân tải. Tôi cấu hình cho nó tự dò server qua DNS, tưởng vậy là chia đều. Thực tế nó cứ bám lấy một server cho cả loạt request, chỉ đổi khi server kia chết. Tức là tôi đã có hai server nhưng chỉ một cái làm việc. Phải khai báo cố định cả hai địa chỉ thì nó mới chia đều từng request thật.

2. Hai server cùng "dọn nhà" lúc khởi động. Mỗi lần bật, bot chạy bước thiết lập cơ sở dữ liệu (tạo bảng, gieo dữ liệu mẫu). Với hai server bật gần như cùng lúc, cả hai cùng chạy bước đó — suýt tạo trùng dữ liệu khách mẫu. Tôi thêm một cái "khoá" trên Redis: ai giành được khoá mới chạy phần thiết lập, người kia đứng nhìn. Một việc nhạy cảm chỉ được làm bởi đúng một người.

Bài học mang đi

Stateless nghe như từ lóng dân kỹ thuật khoe mẽ. Thực ra nó là một câu hỏi rất đời: "Nếu mai có người thứ hai cùng làm việc này, hai người có tranh nhau cuốn sổ ghi nhớ không?" Nếu có, bạn chưa sẵn sàng để lớn.

Mẹo tư duy tôi rút ra: bộ nhớ trong tiến trình là một sự tiện lợi bạn chỉ được phép xài khi còn đúng một server. Ngay khi nghĩ đến chuyện chịu tải hơn — hay đơn giản là deploy mà không mất dữ liệu — hãy hỏi "trạng thái này nên sống ở đâu cho cả tập thể cùng thấy?". Trả lời được câu đó là bạn đã bước từ viết một script sang vận hành một dịch vụ.

👉 Muốn tự tay đi qua đúng những vấp ngã như thế này trên con bot đầu tiên của mình? Bắt đầu với mini-course miễn phí: Bot AI đầu tiên của bạn — làm xong trong một buổi tối, và bạn sẽ hiểu vì sao "cho nó chạy được" mới chỉ là nửa chặng đường.

Nhận bài thực chiến qua email

Mỗi tuần một bài về cách đưa AI vào doanh nghiệp thật. Miễn phí, huỷ bất cứ lúc nào.