# Giải quyết bài toán hàng đợi phân tán sau 15 năm

Khi còn phụ trách cơ sở hạ tầng tại Reddit, hệ thống quan trọng nhất mà tôi duy trì là Postgres, nhưng thứ đứng ngay sau đó chính là RabbitMQ – trình trung tin nhắn (message broker) của chúng tôi. RabbitMQ là yếu tố thiết yếu cho hoạt động của Reddit — mọi thứ đều được đưa vào một hàng đợi phân tán trước khi đến cơ sở dữ liệu. Ví dụ, khi bạn upvote một bài đăng, hành động đó sẽ được ghi vào hàng đợi và bộ đệm (cache), sau đó trả về thành công cho người dùng. Sau đó, một trình chạy hàng đợi sẽ lấy mục đó và cố gắng ghi vào cơ sở dữ liệu cũng như tạo một mục công việc mới để tính toán lại tất cả các bảng xếp hạng mà lượt upvote đó ảnh hưởng.

Chúng tôi sử dụng kiến trúc hàng đợi công việc này vì nó đơn giản, có khả năng mở rộng và có nhiều tính năng mạnh mẽ:

**Khả năng mở rộng ngang.** Hàng đợi công việc cho phép chúng tôi chạy nhiều tác vụ song song, tận dụng tài nguyên của nhiều máy chủ. Chúng cũng khá đơn giản để mở rộng—chỉ cần thêm nhiều worker hơn.

**Kiểm soát luồng.** Với hàng đợi công việc, chúng tôi có thể tùy chỉnh tốc độ mà worker tiêu thụ các tác vụ từ các hàng đợi khác nhau. Ví dụ, đối với các tác vụ tốn nhiều tài nguyên, chúng tôi có thể giới hạn số lượng các tác vụ đó có thể chạy đồng thời trên một worker duy nhất. Nếu một tác vụ truy cập API bị giới hạn tốc độ, chúng tôi có thể giới hạn số tác vụ được thực thi mỗi giây để tránh quá tải API.

**Lên lịch.** Hàng đợi công việc cho phép chúng tôi xác định khi nào hoặc tần suất một tác vụ chạy. Ví dụ, chúng tôi có thể chạy tác vụ theo lịch cron, hoặc lên lịch thực thi một số tác vụ trong tương lai.

Hệ thống này hoạt động tốt, nhưng nó có thể bị hỏo theo nhiều cách phức tạp. Nếu cơ sở dữ liệu cho bình chọn bị xuống, mục đó phải được đưa trở lại hàng đợi. Nếu bộ đệm bảng xếp hạng bị xuống, bảng xếp hạng không thể được tính toán lại. Nếu trình xử lý hàng đợi bị treo sau khi đã lấy mục nhưng trước khi hành động trên nó, dữ liệu sẽ bị mất. Và nếu chính hàng đợi bị xuống, như nó thường xuyên xảy ra, chúng tôi có thể chỉ mất bình chọn, bình luận, hoặc bài đăng (bạn đã bao giờ nghĩ “Tôi biết mình đã bình chọn cho cái đó nhưng nó biến mất!” khi sử dụng Reddit chưa? Đó là lý do).

Điều chúng tôi thực sự cần để tạo hệ thống hàng đợi phân tán mạnh mẽ chính là những hàng đợi bền vững (durable queues) lưu trữ trạng thái của các tác vụ trong hàng đợi vào một kho lưu trữ bền vững như Postgres. Với hàng đợi bền vững, chúng tôi có thể tiếp tục các công việc thất bại từ bước hoàn thành cuối cùng và chúng tôi sẽ không mất dữ liệu khi có sự cố treo chương trình.

Hàng đợi bền vững là thứ hiếm khi tôi còn ở Reddit, nhưng ngày càng trở nên phổ biến hơn. Về cơ bản, chúng hoạt động bằng cách kết hợp hàng đợi công việc với các luồng công việc bền vững, giúp bạn điều phối đáng tin cậy các luồng công việc gồm nhiều tác vụ song song. Về kiến trúc, hàng đợi bền vững tương tự như các hàng đợi thông thường, nhưng sử dụng một kho lưu trữ bền vững (thường là cơ sở dữ liệu quan hệ) làm cả trình trung tin nhắn và backend:

Trừu tượng cốt lõi trong hàng đợi bền vững là một luồng công việc gồm nhiều tác vụ. Ví dụ, bạn có thể gửi một tác vụ xử lý tài liệu chia tài liệu thành các trang, xử lý mỗi trang song song trong các tác vụ riêng biệt, sau đó xử lý hậu kỳ và trả về kết quả:

Hàng đợi bền vững hoạt động bằng cách lưu điểm kiểm tra (checkpoint) các luồng công việc trong kho lưu trữ bền vững của chúng. Khi một khách hàng gửi một tác vụ, tác vụ và đầu vào của nó được ghi lại. Sau đó, bất cứ khi nào tác vụ đó gọi một tác vụ khác, tác vụ con và đầu vào của nó được ghi lại như một con của tác vụ gọi. Do đó, hệ thống hàng đợi có một bản ghi bền vững hoàn chỉnh về tất cả các tác vụ và mối quan hệ của chúng.

Các luồng công việc này đặc biệt liên quan khi phục hồi từ các sự cố. Nếu một worker không bền vững bị gián đoạn khi thực thi tác vụ, hệ thống hàng đợi sẽ khởi động lại nó từ đầu tốt nhất, hoặc mất tác vụ trong trường hợp xấu nhất. Điều này không lý tưởng cho các luồng công việc chạy lâu hoặc các tác vụ có dữ liệu quan trọng. Thay vào đó, khi một hệ thống hàng đợi bền vững phục hồi một luồng công việc, nó tra cứu các điểm kiểm tra của nó để phục hồi từ bước hoàn thành cuối cùng, tránh gửi lại bất kỳ công việc nào đã hoàn thành.

**Hàng đợi bền vững và khả năng quan sát (Observability)**

Lợi ích khác của hàng đợi bền vững là khả năng quan sát tích hợp. Vì chúng lưu trữ các bản ghi chi tiết về mọi luồng công việc và tác vụ từng được gửi, hàng đợi bền vững giúp dễ dàng giám sát những gì các hàng đợi và luồng công việc đang làm tại bất kỳ thời điểm nào. Ví dụ, tra cứu nội dung hiện tại của một hàng đợi (hoặc bất kỳ nội dung nào trong quá khứ) chỉ đơn giản là một truy vấn SQL. Tương tự, tra cứu trạng thái hiện tại của một luồng công việc là một truy vấn SQL khác.

**Đánh đổi về Hàng đợi bền vững**

Vậy khi nào chúng ta nên sử dụng hàng đợi bền vững? Như thường lệ, câu trả lời nằm ở việc đánh đổi. Đối với hàng đợi bền vững, sự đánh đổi chính xoay quanh hiệu suất trình trung tin nhắn. Hầu hết các hàng đợi công việc phân tán sử dụng một kho lưu trữ khóa-giá trị trong bộ nhớ như Redis để trung gian hóa tin nhắn và lưu trữ đầu ra tác vụ. Tuy nhiên, hàng đợi bền vững cần sử dụng một kho lưu trữ bền vững, thường là cơ sở dữ liệu quan hệ như Postgres, làm cả trình trung tin nhắn và backend. Latter cung cấp các đảm bảo mạnh mẽ hơn, nhưng former có thông lượng cao hơn. Do đó, bạn nên ưu tiên hàng đợi bền vững khi xử lý khối lượng thấp hơn các tác vụ quan trọng đối với kinh doanh và hàng đợi công việc phân tán khi xử lý khối lượng rất lớn các tác vụ nhỏ.

Trong thế hệ hệ thống phân tán hiện đại, việc đảm bảo tính toàn vẹn dữ liệu và khả năng phục hồi là yếu tố sống còn. Những kinh nghiệm từ Reddit cho thấy rằng dù hàng đợi truyền thống mang lại hiệu suất cao, nhưng chúng không thể đảm bảo dữ liệu không bị mất trong các sự cố. Giải pháp hàng đợi bền vững kết hợp sức mạnh của kiến trúc hàng đợi với khả năng chịu lỗi của cơ sở dữ liệu quan hệ, tạo ra một nền tảng đáng tin cậy cho các ứng dụng yêu cầu độ chính xác cao.

Khi các doanh nghiệp ngày càng phụ thuộc vào các luồng công việc phức tạp và AI, nhu cầu về hàng đợi bền vững sẽ chỉ tiếp tục tăng. Thay vì đối mặt với những tình huống “Tôi biết tôi đã bình chọn cho cái đó nhưng nó biến mất!”, người dùng có thể tin tưởng vào hệ thống của bạn, biết rằng mọi tác vụ quan trọng đều được thực thi và ghi lại một cách đáng tin cậy.

Đối với các nhà phát triển đang xây dựng các hệ thống phân tán, câu hỏi không còn là “có nên sử dụng hàng đợi bền vững không?” mà là “khi nào nên sử dụng chúng?”. Với sự đánh đổi giữa hiệu suất và độ tin cậy, câu trả lời phụ thuộc vào yêu cầu cụ thể của từng ứng dụng – nhưng đối với bất kỳ hệ thống nào xử lý dữ liệu quan trọng, hàng đợi bền vững không còn là lựa chọn, mà là yêu cầu bắt buộc.