Pages

Friday, January 10, 2014

Use after_commit instead of active record callbacks to avoid unexpected errors

AR callbacks after_create/after_update/after_destroy to generate background job etc are useful, but these callbacks are still wrapped in database transaction, and you may probably get unexpected errors on production servers.
Before It’s common to generate a background job to send emails like
class Comment < ActiveRecord::Base
  after_create :asyns_send_notification
  def async_send_notification
    Notifier.send_new_comment_notification(self).deliver
  end
 handle_asynchronously :async_send_notification
end

# It looks fine that comment record passed to delayed job and then delayed job will fetch this
#comment and then post on which this comment has been made and a notification will
#send via email to the author of that post. Right?
You won’t see any issue in development, as local db can commit fast. But in production server, db traffic might be huge, worker probably finish faster than transaction commit. e.g
  1. main process
  2. worker process
  3. BEGIN
     INSERT comment in comments table
     # return id 10 for newly-created notification
     SELECT * FROM notifications WHERE id = 10
     COMMIT
    
In this case, the worker process query the newly-created notification before main process commits the transaction, it will raise NotFoundError, because transaction in worker process can’t read uncommitted notification from transaction in main process.
Refactor
So we should tell activerecord to generate notification worker after notification insertion transaction committed.
class Comment < ActiveRecord::Base
after_commit :asyns_send_notification,:on => :create  # This is the main point of the whole thing.

  def async_send_notification
    Notifier.send_new_comment_notification(self).deliver
  end
 handle_asynchronously :async_send_notification
end
Now the transactions order becomes
  1. main process
  2. worker process
  3. BEGIN
     INSERT comment into comments_table
     return id 10 for newly-created notification
     COMMIT
     SELECT * FROM notifications WHERE id = 10
    
Worker process won’t receive NotFoundErrors any more. :)
For More Details visit http://www.codebeerstartups.com

No comments:

Post a Comment