• after_create and after_commit


    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

    class Notification < ActiveRecord::Base
      after_create :asyns_send_notification
    
      def async_send_notification
        NotificationWorker.async_send_notification({:notification_id => id})
      end
    end
    
    class NotificationWorker < Workling::Base
      def send_notification(params)
        notification = Notification.find(params[:notification_id])
        user = notification.user
        # send notification to user's friends by email
      end
    end

    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.

    class Notification < ActiveRecord::Base
      after_commit :asyns_send_notification, :on => :create
    
      def async_send_notification
        NotificationWorker.async_send_notification({:notification_id => id})
      end
    end

    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.

  • 相关阅读:
    linux命令行下命令参数前的一横(-)和两横(--)的区别
    sql的集合操作
    二叉树的遍历
    linux网络编程中阻塞和非阻塞socket的区别
    Python 信号量
    python中文件的复制
    Linux网络服务器epoll模型的socket通讯的实现(一)
    Linux启动提示“unexpected inconsistency;RUN fsck MANUALLY”
    Linux用户级线程和内核级线程区别
    nodejs的cs模式聊天客户端和服务器实现
  • 原文地址:https://www.cnblogs.com/wangyuyu/p/3853228.html
Copyright © 2020-2023  润新知