Blog

Một chat nặng có thể làm sập API của chúng tôi như thế nào

Ngày 28 tháng 9, khoảng 17 phút sau một lần phát hành, cả hai máy chủ API production của chúng tôi cùng hết bộ nhớ vào cùng một thời điểm, khởi động lại, và trong khoảng năm phút, load balancer không có máy chủ khỏe mạnh nào để gửi lưu lượng tới. Cách sửa đầu tiên của chúng tôi là cấp cho API gấp đôi bộ nhớ. Nguyên nhân thực sự xuất hiện sau đó trong ngày: mở một chat cụ thể khiến API phải tải 53 MB tin nhắn trong một request duy nhất. Lịch sử chat giờ được tải theo từng trang, giới hạn cả theo kích thước lẫn số lượng, và chat đó mở ra với một trang đầu tiên 5 MB thay vì sập.

Chuyện gì đã xảy ra

API của chúng tôi chạy như một container trên mỗi trong hai máy chủ phía sau một load balancer. Mỗi container có giới hạn bộ nhớ 384 MiB. Khi nghỉ, nó dùng 130–140 MiB, nên giới hạn này trông như có nhiều khoảng trống thừa.

Cả hai container cùng chạm giới hạn và sập với thông báo “JavaScript heap out of memory”. Heap là bộ nhớ mà một tiến trình Node.js dùng cho các đối tượng nó đang xử lý. Vì cả hai máy chủ cùng hỏng, không còn nơi nào để định tuyến request. Đó là loại sự cố tồi tệ nhất với một hệ thống hai máy chủ: dự phòng chỉ hữu ích khi các lỗi không xảy ra cùng lúc.

Cách sửa đầu tiên: thêm không gian

Thời điểm xảy ra gợi ý đến đợt deploy. Sau một lần phát hành, các lượt agent bị deploy làm gián đoạn được tiếp tục và lưu lại transcript của chúng, và đợt công việc dồn dập đó trông giống nguồn gây tăng đột biến bộ nhớ.

Vì vậy thay đổi đầu tiên nâng giới hạn bộ nhớ của API từ 384 MiB lên 768 MiB. Chúng tôi áp dụng trực tiếp trên cả hai máy chủ ngay trong ngày, và commit vào cấu hình deploy để lần deploy tiếp theo không lặng lẽ đặt lại về 384. Một bản sửa làm bằng tay trên máy chủ mà lần deploy kế tiếp hoàn tác là cách phổ biến để cùng một sự cố quay trở lại.

Thêm bộ nhớ là một biên an toàn hợp lý. Nhưng nó không phải là lời giải thích.

Nguyên nhân thực sự: một request, 71 MB

Sau đó trong ngày, chúng tôi tái hiện lỗi sập trên một bản sao của một chat, với giới hạn bộ nhớ cũ của production. Bản sao đó làm sập API một mình, không có đợt deploy nào liên quan.

Khi bạn mở một dự án, ứng dụng web yêu cầu API lịch sử của chat. Endpoint trả về 30 tin nhắn mới nhất, nguyên vẹn. Với đa số chat, 30 tin nhắn là nhỏ. Với chat này, 30 tin nhắn mới nhất là 53 MB trong cơ sở dữ liệu và 71 MB phản hồi. Một số câu trả lời của agent có kích thước 4–10 MB mỗi cái, chủ yếu là transcript của các sub-agent: các agent trợ lý mà agent chính khởi động để làm song song một phần của công việc, và toàn bộ phần việc của chúng được lưu cùng câu trả lời.

Xây dựng phản hồi đó tốn nhiều heap hơn mức API có. Và mỗi lần mở hoặc làm mới dự án đó lại gửi request. Một chat duy nhất có thể làm sập bất kỳ máy chủ nào nó rơi vào, và vì trang có thể được tải lại, nó có thể lan sang cả hai.

“30 tin nhắn” chưa bao giờ là một giới hạn thực sự về kích thước. Nó chỉ là giới hạn về số lượng, mà tình cờ nhỏ với các chat bình thường.

Cách sửa: trang được giới hạn theo kích thước lẫn số lượng

Giờ một trang lịch sử gồm tối đa 30 tin nhắn hoặc khoảng 4 MB tin nhắn, tùy điều kiện nào đến trước.

Cơ chế hoạt động qua hai bước:

  1. Hỏi kích thước trước. API yêu cầu cơ sở dữ liệu trả về ID và kích thước đã lưu của các tin nhắn mới nhất, không kèm nội dung. Truy vấn đó rẻ bất kể tin nhắn lớn đến đâu.
  2. Chỉ tải những gì vừa vặn. Đi từ mới nhất đến cũ nhất, nó thêm các tin nhắn cho đến khi tin nhắn tiếp theo sẽ đẩy trang vượt quá 4 MB. Chỉ những dòng đó mới được tải đầy đủ.

Hai quy tắc giữ cho cách này an toàn:

  • Tin nhắn mới nhất luôn nằm trên trang, bất kể kích thước. Một trang không trả về gì sẽ khiến chat bị kẹt, không có cách nào cuộn lên để xem lại.
  • Không cắt bỏ gì. Không tin nhắn nào bị cắt ngắn hay bỏ đi. Phần còn lại của chat được tải qua điều khiển “Cuộn lên để xem tin nhắn cũ hơn” mà các tin nhắn cũ đã dùng, từng trang một.

Chúng tôi tìm thấy thêm một chỗ có cùng dạng vấn đề. Khi bạn dừng một lượt, API kiểm tra vài câu trả lời cuối để xem có cái nào rỗng và cần được dọn dẹp không. Trước đây nó làm vậy bằng cách tải toàn bộ tám tin nhắn. Với một chat có những câu trả lời vài megabyte, đó là cùng một vấn đề ở dạng nhỏ hơn. Giờ nó chỉ hỏi cơ sở dữ liệu một câu có/không — tin nhắn này có nội dung nào không? — mà không tải chính nội dung đó.

Chúng tôi kiểm chứng như thế nào

Trên bản sao của chat, chạy với giới hạn 384 MiB cũ:

  • Trang đầu tiên là 5 MB. Trước đây, cùng request đó làm sập API.
  • Năm tab trình duyệt cùng mở chat cho thấy mức bộ nhớ đỉnh là 154 MB.
  • Cuộn lại toàn bộ chat trả về đủ 190 tin nhắn, mỗi tin đúng một lần, theo đúng thứ tự.

Logic chọn trang cũng có unit test, bao gồm một test dựng từ kích thước thực của các tin nhắn mới nhất trong chat đó. Trang đầu tiên trả về một tin nhắn, báo rằng còn nữa, và trang kế tiếp bắt đầu từ tin nhắn ngay sau nó.

Chúng tôi rút ra điều gì

  • Số lượng không phải là giới hạn kích thước. Bất kỳ endpoint nào trả về “N mục mới nhất” của một thứ có thể lớn vô hạn đều cần một ngân sách byte. Dữ liệu cuối cùng sẽ tìm ra khoảng hở đó.
  • Nghi phạm hiển nhiên không phải lúc nào cũng là nguyên nhân. Đợt deploy là một tác nhân thật sự gây thêm công việc, và thêm bộ nhớ là đáng làm. Nhưng sự cố tái hiện được mà không cần deploy, và nếu không tái hiện được, chúng tôi đã dừng lại ở việc tăng bộ nhớ.
  • Hai máy chủ cùng hỏng khi nhận cùng một request. Dự phòng bảo vệ khỏi việc một máy chủ chết. Nó không bảo vệ khỏi một request giết chết bất kỳ máy chủ nào nó chạm tới, đặc biệt là một request mà người dùng sẽ tự nhiên thử lại bằng cách làm mới trang.
  • Sửa trong cấu hình, không chỉ trên máy. Việc tăng bộ nhớ được đưa vào cấu hình deploy cùng ngày, nên lần deploy tiếp theo vẫn giữ nguyên nó.

Đây là câu chuyện thứ hai gần đây về bộ nhớ liên quan đến đầu ra của agent. Câu chuyện đầu tiên là một nhãn trạng thái xếp hàng 690 MB chuỗi. Nếu bạn dùng sub-agent trên Mythex, bạn có thể đọc về cách chúng hoạt động trong tài liệu.

Mythex · Pricing · Xây dựng mọi thứ với AI · Mẫu website và ứng dụng · So sánh Mythex · Thuật ngữ xây dựng ứng dụng bằng AI · Công cụ miễn phí để xây dựng ứng dụng bằng AI · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو 简体中文 繁體中文(香港) 繁體中文(台灣)