A relational database, like mysql, provides transactions to wrap several operations in one unit, make them all pass or all fail. All isolation levels except READ UNCOMMITTED don't allow read data changes until they are committed in other transaction. If you don't realize it, you probably introduce some unexpected errors.
Before
It's common to generate a background job to send emails, tweets or post to facebook wall, like
classNotification<:baseafter_create :asyns_send_notification>
defasync_send_notification
NotificationWorker.async_send_notification({:notification_id =>id})endendclassNotificationWorker<:basedefsend_notification>
# send notification to user's friends by emailendend
It looks fine, every time it creates a notification, generates an asynchronous worker, assigns notification_id to the worker, in the worker it finds the notification by id, then sends notification by email.
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.
main processworker process
BEGIN
INSERT INTO notifications(message, user_id) values('notification message', 1)
# 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.
classNotification<:baseafter_commit :asyns_send_notification>:create
defasync_send_notification
NotificationWorker.async_send_notification({:notification_id =>id})endend
Now the transactions order becomes
main processworker process
BEGIN
INSERT INTO notifications(message, user_id) values('notification message', 1)
# return id 10 for newly-created notification
COMMIT
SELECT * FROM notifications WHERE id = 10
Worker process won't receive NotFoundErrors any more.
For those callbacks that no need to execute in one transaction, you should always use after_commit to avoid unexpected errors.
after_commit is introduced from rails 3, if you use rails 2, please check out after_commit gem instead.