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
 
  
 
