Async trong Ruby: Phát Hiện "Đỉnh Cao" cho Tương Lai AI và Hiệu Suất Vượt Trội!
Lê Lân
1
Async Ruby và Cuộc Cách Mạng Đồng Thời của LLM: Tương Lai Song Hành
Mở Đầu
Sau hơn một thập kỷ làm việc chuyên sâu với Python trong hệ sinh thái async, tác giả bất ngờ khi trở lại thế giới Ruby và nhận thấy sự chênh lệch đáng kể về cách tiếp cận xử lý đồng thời. Không như Python – vốn đã hoàn toàn chuyển sang async/await, Ruby vẫn chủ yếu dựa trên mô hình đa luồng (thread-based) qua các thư viện như Sidekiq, GoodJob hay SolidQueue.
Tuy nhiên, khi xây dựng các ứng dụng tương tác với mô hình ngôn ngữ lớn (LLM) như RubyLLM và Chat with Work, tác giả nhận ra rằng nhu cầu xử lý bất đồng bộ (async) trong Ruby thực sự là một cuộc cách mạng chưa được khai phá, đồng thời còn mang lại lợi thế vượt trội so với Python.
Bài viết sẽ trình bày chi tiết về:
Những hạn chế của mô hình đa luồng khi xử lý LLM streaming
Cách Ruby triển khai async fiber vượt trội như thế nào
Tại sao async Ruby lại là bước tiến tất yếu cho công nghệ AI và LLM hiện đại
Cách triển khai async Ruby trong các ứng dụng thực tế
1. Tại Sao Mô Hình Đa Luồng Truyền Thống Không Phù Hợp Với LLM
1.1. Vấn Đề Đóng Băng Tài Nguyên (Slot Starvation)
Trong các queue công việc đa luồng, một job streaming kết nối LLM có thể giữ nguyên một worker (slot) trong hàng chục giây, trong khi thread đó phần lớn thời gian chỉ chờ dữ liệu token đến dần. Ví dụ:
classStreamAIResponseJob < ApplicationJob
defperform(chat, message)
chat.ask(message) do |chunk|
broadcast_chunk(chunk) # Thread bị chiếm dụng trong suốt quá trình streaming
end
end
end
Kết quả là với cấu hình 25 worker, chỉ có 25 kết nối streaming cùng lúc mà thôi, người thứ 26 phải chờ đợi dù server hoàn toàn rảnh.
1.2. Phép Nhân Tài Nguyên (Resource Multiplication)
Mỗi thread đòi hỏi:
Một kết nối database riêng
Bộ nhớ stack dành riêng
Chi phí quản lý thread từ hệ điều hành
Khi quy mô tăng lên hàng nghìn kết nối streaming, tài nguyên bị tắc nghẽn trầm trọng dù phần lớn thread gần như nhàn rỗi.
1.3. Chi Phí Hiệu Năng Cao
Các phép thử thực tế cho thấy:
Hoạt động
Thời gian trung bình
Tạo thread mới
~80μs
Chuyển ngữ cảnh thread
~1.3μs
Thông lượng tối đa
~5,000 yêu cầu/giây
Với hàng nghìn kết nối, độ trễ vi mô này tăng dần thành tắc nghẽn lớn.
1.4. Giới Hạn Khả Năng Mở Rộng
Khi tạo đến 10,000 thread, hệ điều hành bị quá tải, scheduler không kịp xử lý, gây tụt hiệu suất nghiêm trọng. Trong khi đó, các ứng dụng AI hiện đại cần khả năng quản lý hàng nghìn kết nối đồng thời.
Điểm quan trọng: LLM streaming không giống các tác vụ background job truyền thống. Đây là sân chơi riêng cho async concurrency.
2. Đồng Thời Trong Ruby: Threads vs Fibers
2.1. Khái Niệm Về Quá Trình, Luồng Và Fiber
Process (Quá trình): Giống như một văn phòng riêng biệt, cách ly hoàn toàn với bộ nhớ riêng.
Thread (Luồng): Là công nhân chia sẻ một văn phòng, có quyền truy cập bộ nhớ và tài nguyên chung nhưng phải tránh xung đột.
Fiber: Là các nhiệm vụ nhỏ được xử lý trong một luồng bởi một công nhân, chuyển đổi một cách có kiểm soát (cooperative).
2.2. Phương Thức Lên Lịch
Loại
Ai quyết định chuyển task?
Đặc điểm
Thread
Hệ điều hành (preemptive)
Ngắt bất ngờ, cấu trúc kernel, overhead cao
Fiber
Chuyển đổi tự nguyện (cooperative)
Chỉ chuyển khi yield, đơn giản và hiệu quả
Ví dụ với thread:
threads = 10.times.map do |i|
Thread.new do
expensive_calculation(i)
fetch_from_api(i) # Có thể block riêng lẻ
process_result(i)
end
end
Với fiber trong async Ruby:
Asyncdo
fibers = 10.times.map do |i|
Asyncdo
expensive_calculation(i)
fetch_from_api(i) # Tự yield để cho fiber khác chạy
process_result(i)
end
end
end
2.3. Ruby’s GVL và Ưu Thế Fiber
Ruby sử dụng GVL (Global VM Lock), giới hạn chỉ một thread thực thi Ruby code cùng lúc, điều này làm thread kém hiệu quả cho việc xử lý CPU. Tuy nhiên, khi chờ I/O, GVL được giải phóng, nên thread có thể song song một cách gián tiếp.
Điều này cho thấy:
Thread không giúp ích nhiều trong tính toán CPU-bound
Thread chỉ giúp khi chờ I/O (network, database)
Tuy nhiên, overhead thread vẫn lớn, do đó fiber với chuyển đổi nhẹ lại rất phù hợp.
3. Ưu Thế Của Fiber và I/O Multiplexing Trong Ruby Async
Nhờ epoll/kqueue/io_uring, máy chủ có thể theo dõi hàng ngàn file descriptor duy nhất bằng một lời gọi hệ thống duy nhất, tránh chi phí tốn kém của một thread riêng cho từng kết nối.
3.2. So Sánh Hiệu Năng Fiber vs Thread (Ruby 3.4)
Chỉ số
Fiber
Thread
Tạo mới
~3μs
~80μs
Chuyển đổi ngữ cảnh
~0.1μs
~1.3μs
Thông lượng tối đa
~80,000 yêu cầu/giây
~5,000 yêu cầu/giây
3.3. Lợi Ích Đầy Đủ
Dùng ít tài nguyên hệ thống hơn
Lên lịch hiệu quả nhờ userspace quản lý
Hoạt động nhịp nhàng với GVL
Khả năng mở rộng vượt trội hàng chục nghìn kết nối
Chia sẻ tài nguyên như connection pool dễ dàng
Chú ý: Fiber không phụ thuộc vào hệ điều hành như thread, cho phép quản lý khối lượng kết nối lớn mà không ảnh hưởng hiệu suất.
4. Ruby Async Giải Quyết Tất Cả Thách Thức Của LLM
Vấn đề
Giải pháp Async Ruby
Slot Starvation
Tạo fiber on-demand, không giới hạn slot cố định
Tài nguyên share
Dùng chung kết nối DB, memory pool
Hiệu năng
Tạo & chuyển fiber nhanh, ít overhead
Khả năng mở rộng
Hàng chục nghìn fiber single thread
5. Hệ Sinh Thái Async Ruby và Triển Khai Thực Tế
5.1. Nền Tảng: Gem async
require'async'
require'net/http'
Asyncdo
responses = 1000.times.map do |i|
Asyncdo
uri = URI("https://api.openai.com/v1/chat/completions")
Không cần async/await, callback phức tạp, chỉ cần đặt code trong Async block.
5.2. RubyLLM: Async Tự Động Và Không Đổi Mã Nguồn
Mặc dù RubyLLM không sử dụng async trực tiếp, khi gọi trong môi trường Async thì vì nó dựa trên Net::HTTP – vốn tự động yield tại IO, tự nhiên trở nên async.
Không cần thay đổi code công việc hay channel, hiệu suất và khả năng chịu tải tự động tăng lên.
6. Khi Nào Dùng Thread, Khi Nào Dùng Async?
Tình huống
Khuyến nghị
CPU-intensive (tính toán nặng)
Thread hoặc process riêng biệt
Cần cách ly hoàn toàn
Thread hoặc process riêng biệt
C extension không an toàn fiber
Thread
Tác vụ IO-bound (web request, API...)
Async Fiber
Ứng dụng WebSocket, streaming
Async Fiber
Ứng dụng LLM, AI streaming
Async Fiber
Kết Luận
Sau nhiều năm chứng kiến sự phát triển nhanh chóng của async trong Python, có thể thấy Ruby đang âm thầm xây dựng một hệ sinh thái async vừa mạnh vừa dễ dùng. Công nghệ fiber và event loop của Ruby cung cấp giải pháp xử lý đồng thời vượt trội: không cần thay đổi cú pháp, không phải tái cấu trúc toàn bộ hệ thống, mà hiệu suất và khả năng mở rộng được cải thiện đáng kể.
Đặc biệt với các ứng dụng LLM – nơi yêu cầu hàng nghìn kết nối streaming đồng thời với độ trễ cực thấp – async Ruby là lựa chọn lý tưởng, giúp giảm chi phí, cải thiện trải nghiệm người dùng và đơn giản hoá vận hành.
Nếu bạn là người phát triển AI hoặc ứng dụng web hiện đại trên Ruby, async không chỉ là công nghệ nên thử – nó là lợi thế cạnh tranh cần thiết.