我如何确保ActiveRecord保存更改到数据库



我有一个复杂的系统,涉及许多救援人员、工作和监控过程。作业具有父子依赖关系,并且它们通过一系列状态(使用状态机)运行,这就是监视过程的原因。我们依靠数据库状态来确保跨进程跟踪是同步的。

这是一个粗略的想法:

class ParentMonitor < ActiveRecord::Base
  has_many children, class: ChildMonitor
  state_machine :state, initial: :work_needed do
    event :succeed do
      transition :work_needed => :work_succeeded
    end
    event :fail do
      transition :work_needed => :work_failed
    end
  end
  def child_transition
    return if children.any? { |child| child.work_needed? }
    if children.any? { |child| child.work_succeeded? }
      succeed
    else
      fail
    end
  end
end
class ChildMonitor < ActiveRecord::Base
  belongs_to: owner, class: ParentMonitor
  state_machine :state, initial: :work_needed do
    event :succeed do
      transition :work_needed => :work_succeeded
    end
    after_transition :to => :work_succeeded, :do => :notify_owner
    event :fail do
      transition :work_needed => :work_failed
    end
    after_transition :to => :work_failed, :do => :notify_owner
  end
  def notify_owner
    owner.child_transition
  end    
end

发生的事情是,对于最初的几个这样的作业(比如几百个作业中的一打或两个),即使所有的子作业都在work_succeededwork_failed中,ParentMonitors仍然处于work_needed状态。通过跟踪和测试,我确定每次调用ParentMonitor#child_transition时,处于"工作需要"状态的子列表都被依次减少,直到某一点它使数据库加载并将所有子列表替换为"工作需要"的值。尽管有些已经完成了。

此外,我没有看到任何UPDATE日志在日志文件中为这些前几个孩子,直到它突然开始记录更新。当它似乎重置所有子线程的状态时,日志记录是同步的。

这让我认为这些变化都发生在内存中,由于一些缓存状态,但我已经添加了reload, savefind调用,他们似乎没有影响变化。我也试过在uncache中包装这些调用,但没有帮助。

事实证明,这是由于写操作是在长时间运行的事务中进行的,因为状态机gem在状态更改和任何after钩子结束之间保持打开一个事务。我们已经编写了在主监视循环上运行几个小时的钩子。

我们通过在状态更改之间而不是在回调中执行操作来解决这个问题。

顺便说一下,错误的行为与最新的红皮书中描述的完全一样,即大多数RDMBS中实现的"弱隔离"并发的副作用:

异常的例子包括读取另一个事务产生的中间数据,读取被终止的数据,在执行同一事务期间为同一项读取两个或多个不同的值,以及由于并发写入同一项而"丢失"事务的一些影响

最新更新