Ever encountered a situation like this in production?
ActiveRecord::Base.transaction do user_one.process_action_one user_two.process_action_two MailWorker.perform_async("UserMailer", "success_mail", user_one.id) MailWorker.perform_async("UserMailer", "success_mail", user_two.id) end
Where we have to process a group of actions within a transaction, and on success send mails to users. We use asycronous jobs to send our mails, as it can be blocking if done otherwise.
Everything seems fine and simple, but what happens if during the function calls, an error is raised and the transaction which encapsulates these lines of code is rolled back?
Since the jobs are already scheduled to run asyncronously, they will be processed regardless of the success or failure of the transaction. This can cause a problem, because the users could receive an erroneous notification if the transaction had rolled back and the intended code not executed.
We thus require, a solution which only schedules asyncronous jobs called within a transaction, to be processed once the encapsulating transaction commits.
Sidekiq Async Task solves exactly this problem. This is a ruby gem which gives your Sidekiq background workers the feature to be transaction-safe. That is, it guarentees that the asyncronous sidekiq jobs within a transaction will only be executed once the transaction commits.
- Install the gem, run the gem generators and migrate your database. (This gem adds a model in your database which keeps track of what asyncronous task to execute when).
$ gem sidekiq_async_task #Add to gemspec $ bundle install $ rails generate sidekiq_async_task:install $ rake db:migrate
SidekiqAsyncTask::TransactionSupportto your Sidekiq worker.
class MailWorker < SidekiqAsyncTask::TransactionSupport include Sidekiq::Worker sidekiq_options retry: true def perform_with_callback(*args) #Logic for sending mail end end
Define your worker logic in the
perform_with_callbackmethod instead of the generic
performmethod, in the worker class.
Schedule your asyncronous tasks using any of these interfaces.
HardWorker.perform_with_transaction_in(1.second, args) HardWorker.perform_with_transaction_at(time, args) HardWorker.perform_with_transaction_async(args) HardWorker.perform_with_transaction_future(time, args)
- Enjoy safe transactional asyncronous tasks in rails!
Everytime we ask Sidekiq to run a worker asyncronously, the gem creates an entry in the AsyncTask table in the database. When the transaction commits, the AsyncTask entry also commits. The gem defines logic in the after_commit callback of the AsyncTask model to process the job corresponding to the AsyncTask entry.
We can also create an active_admin dashboard for this model to have a UI to view all the scheduled and processed AsyncTasks in our database.