Lập lịch không dùng crontab cho Rails app trên Heroku
Introduction to Heroku Đối với những developer lập trình Ruby on Rails, Heroku hẳn không phải là 1 cái tên xa lạ. Heroku là 1 nền tảng platform as a service (PaaS) cho phép developer có thể build, run, vận hành app trên môi trường Cloud. Thực ra Heroku không chỉ hỗ trợ mỗi ngôn ngữ Ruby mà còn ...
Introduction to Heroku
Đối với những developer lập trình Ruby on Rails, Heroku hẳn không phải là 1 cái tên xa lạ. Heroku là 1 nền tảng platform as a service (PaaS) cho phép developer có thể build, run, vận hành app trên môi trường Cloud. Thực ra Heroku không chỉ hỗ trợ mỗi ngôn ngữ Ruby mà còn support nhiều ngôn ngữ khác như Python, Java, PHP, NodeJS nhưng 1 lý do đơn giản mà hầu như Ruby dev nào cũng biết Heroku vì nó được giới thiệu nằm chình ình ngay đầu quyển Rails tutorial - sách nhập môn cho Ruby on Rails dev. Ngoài ra heroku là 1 nơi rất tiện để triển khai các project nhỏ khi chỉ cần dùng account free cũng có thể deploy 1 Rails app cá nhân, support 2 dynos (khái niệm do heroku đưa ra - có thể hiểu như 2 server instance), đủ để chạy 1 web app service + worker. Account Heroku free support sub-domain luôn, tiện cho việc set url các loại webhook, phù hợp để triển khai mấy cái tool nhỏ, 1 vài con BOT hay crawler.
Heroku sample mini-project
Một format mini Rails project nhỏ khá phổ biến có thể chứa các phần sau:
- 1 web app server
- 1 worker - sidekiq, resque ...
- batch để lập lịch chạy mấy cái task định kì
Nhìn qua thì cũng thấy bình thường chẳng có gì nhưng bắt tay vào làm thì lại phát sinh 1 số vấn đề
Account free của heroku chỉ cho xài max là 2 dyno thôi nhưng cái đống trên có vẻ tốn 3 dyno rồi. Nếu xài tới cái dyno thứ 3 là mất tiền - không vui tí nào với dân xài chùa. Để xử lí cho việc này mình có tìm 1 số solution như sau:
-
Chấp nhận hy sinh bớt 1 trong 3 thứ, lý do là thực ra cái tool mình nghịch mình không có ý định lúc nào cũng phải chạy full cả 3 thứ. Việc quyết định xài 2 dyno free này cho mục đích gì thì sẽ được quyết định trong file Procfile - chuyên dùng để định nghĩa command được thực thi trên các dynos khi server được deploy. Chi tiết xem tại https://devcenter.heroku.com/articles/procfile#deploying-to-heroku
-
Sau 1 hồi điều tra thì Heroku có support one-off dyno. Dyno này được dùng khi muốn chạy 1 số lệnh trên heroku dynos remote từ local như rake task hay rails runner chẳng hạn. Dynos chỉ được sinh ra khi bạn có yêu cầu chạy remote từ local thôi nên khi lệnh bạn muốn thực hiện remote hoàn thành xong, dynos này cũng tự động tắt luôn chứ không keep như dyno web hay dyno worker được chỉ định trong Procfile. Tác dụng của one-off dyno này khá phù hợp với nhu cầu của mình do mình có ý định lên 1 cái lịch 5 phút chạy lại 1 cái rake task nho nhỏ. Mình sẽ lập lịch bằng crontab và cứ 5 phút sẽ chạy lệnh để tự động bật cái one-off dyno lên, chạy rake task, xong việc thì one-off dyno tự ngắt. Mặc dù cách làm này chưa ổn vì vẫn dính dáng đến máy local nhưng ít ra cái one-off dyno không charge thêm phí cho cái account free của mình. Heroku chỉ tính thêm số h mà one-off dynobị bật lên để thực thi tác vụ vào 1000h free mỗi tháng / tất cả các dynos của account free.
Đến đây thì vấn đề ban đầu của mình tương đối là giải quyết được rồi nhưng mình cảm thấy không thoả mãn với việc vẫn bị dính dáng đến máy local nên thử vọc vạch thêm tí nữa. Trong lúc mình thử không chạy web server nữa mà chỉ chạy 1 dynos batch với 1 dynos worker thôi thì phát hiện ra 1 vấn đề quan trọng - Heroku không cho mình xài crontab
Concept
Việc lập lịch để thực hiện 1 công việc chỉ định vào 1 thời điểm được xác định từ trước hay lặp đi lặp lại công việc định kì là 1 trong những xử lí phổ biến và thường thì chúng ta nghĩ ngay đến giải pháp đầu tiên là xài crontab. Tuy nhiên Heroku cho rằng crontab không phù hợp với môi trường horizontal scalable như Heroku. Thay vào đó họ đề xuất 1 giải pháp mà họ cho rằng mạnh mẽ và cơ động hơn là clock process. 1 clock process sẽ chạy 1 singleton process để đánh thức các công việc cần thực hiện sau 1 khoảng thời gian nhất định. Khi kết hợp với background worker, nó sẽ tạo nên 1 phương thức rõ ràng và có thể mở rộng (extendable) cho việc lập lịch.
Theo quan điểm trên, việc lập lịch 1 job và thực thi job đó tại thời điểm đã định là 2 công việc độc lập. Việc tác riêng phần thực thi job khỏi việc lập lịch giúp làm rõ ràng nhiệm vụ của mỗi phần và giúp hệ thống dễ cấu trúc hơn. Ý tưởng của việc này là sử dụng job scheduler chỉ để đẩy các background work vào queue chứ không thực hiện chúng. 1 background worker sẽ nhận công việc xử lí những job được đẩy vào queue bởi scheduler
Heroku Scheduler
Để xử lí cho vấn đề clock process này, Heroku cung cấp add-on Heroku Scheduler. Đây là 1 công cụ miến phí (yeah) để support cho công việc lập lịch trên Heroku dyno. Chi tiết về add-on này xem tại https://devcenter.heroku.com/articles/scheduler
Cách cài đặt và sử dụng add-on này khá đơn giản, có thể tham khảo tại article trên. Tuy nhiên dưới đây là 1 số chú ý.
- Mặc dù Scheduler là 1 free add-on nhưng về bản chất nó sẽ xài 1 one-off dyno, tức là vẫn count vào 1000h xài free trên tổng số dyno hàng tháng của bạn. (Cái này vẫn chấp nhận được)
- Bạn có thể thiết lập job cần chạy thông qua 1 màn hình dashboard GUI đẹp đẽ, tuy nhiên nó chỉ cho bạn cấu hình chạy job theo các format frequency định sẵn: daily, hourly, every 10 minutes. Đây là 1 tập lựa chọn quá nghèo nàn và trên thực tế mình còn đang muốn lập job chạy từng phút