Sidekiq Best Practices
Bài đăng này đã không được cập nhật trong 6 năm
Don't place logic in your Worker
Đầu tiên là 10 dòng code của bạn và bạn nghĩ
Code ở đây là chuẩn rồi
6 tháng sau, sau một hồi fix bugs, bạn chợt nhận ra check thiếu điều kiện
OK, thêm mấy dòng vào đây rồi assign lại cho QA
Bạn thêm vào Worker thêm 20 dòng. Chưa đầy 10 ngày sau, team bạn thêm vào 10 dòng... Câu chuyện vẫn tiếp tục, và Worker của bạn phình quá to.
Một điểm mà bạn luôn phải thừa nhận là
Logic sẽ luôn luôn phình lên
Vậy nên, trước khi viết một dòng code logic nào, hãy luôn luôn nghĩ về nơi sẽ đặt những logic đó:
- Interactor
- Service Object
- Model
Don't make your worker too big
Sikdekiq được làm ra để chạy small tasks
, nó không được sinh ra để chạy những long running workers
Vậy làm cách nào để biết Worker của mình đang quá lớn?
Dấu hiện nhận biết đầu tiên là vòng lặp LOOP
.
Tưởng tượng bạn đang code cho Shopee
, bạn có đơn đặt hàng Order
, một Order
sẽ sinh ra hóa đơn Invoice
tương ứng,
một Invoice
sẽ hết hạn sau 5 ngày. Bạn và team quyết định đưa việc expire
một Invoice
vào trong Worker:
class InvoiceExpirerWorker
include Sidekiq::Worker
def perform
expired_invoices = Invoice.expirable
expired_invoices.each do |invoice|
invoice.expire!
end
end
end
Nhìn đoạn code có vẻ đang đơn giản, không hề có vấn đề gì quá lớn
Nhưng giả sử, trong model Invoice
bạn sẽ phải xử lý cả đống việc trong việc expire một invoice:
- Cancel order
- Đưa hàng đã order về kho
- gửi email cho khách hàng
- update các record liên quan
Ví dụ một ngày bạn có 10,000 invoices bị expire, mỗi invoice cần 20 giây để hoàn thành, tính sơ sơ bạn cần mất:
10,000 * 20 = 200,000s
Vậy là Worker phải mất gần 200,000s để chạy xong.
Too long!
Vậy phải làm gì?
Lời khuyên được đưa ra ở đây là sử dụng một Master Worker
, nhiệm vụ của master worker này là sinh ra các worker nhỏ hơn.
Giả sử bạn có 10,000 invoices cần expire thì nó sinh ra 10,000 worker nhỏ để expire từng invoice.
class BatchInvoiceExpirerWorker
include Sidekiq::Worker
def perform
expired_invoices = Invoice.expirable
expired_invoices.each do |invoice|
InvoiceExpirerWorker.perform_async invoice.id
end
end
end
class InvoiceExpirerWorker
include Sidekiq::Worker
def perform invoice_id
invoice = Invoice.find invoice_id
invoice.expire!
end
end
Thay vì bạn có 1 worker chạy 200,000s thì bạn sẽ có 10,000 worker nhỏ hơn chạy với 20s.
Trade off
Một chú ý mà chúng ta phải quan tâm khi tách ra quá nhiều worker là có thể DB connection pool
sẽ bị "cạn kiệt",
nên hãy chú ý trong các trường hợp mà nó có thể xảy ra.
Bạn có thể tham khảo: https://github.com/mperham/sidekiq/issues/1047
All rights reserved