Ruby Rspec,求一个工作线程被递归调用的次数

Ruby Rspec, Finding how many times a worker being called recursively

提问人:jansha 提问时间:11/15/2023 最后编辑:jansha 更新时间:11/16/2023 访问量:50

问:

我需要在 Rspec 上测试一个工作线程,以查找以递归方式一次又一次地调用同一个工作线程的次数。 例如:

Class Myworker
  def perform(id)
    model = Mymodel.find(id)
    associated_records = Mymodel.users.limit(1000)
    associated_records.each(&:destroy)
    if Mymodel.users.exists?
      Myworker.perform_async(id)
    end
  end
end

我需要为此编写 RPSEC 来计算此迭代发生了多少次,

我试图存根工作器,并递增计数器,因为我正在存根工作器,它不会再次执行同一个工作器,并且我被计数器 1 作为最终值。如何找到在 RSPEC 中递归调用工作线程的次数。

Ruby-on-Rails Ruby rubygems rspec-sidekiq

评论

0赞 Benjamin Scharbau 11/15/2023
你为什么不在这里使用?find_each
1赞 Stefan 11/15/2023
计算迭代次数会将测试与实现耦合在一起。检查是否所有关联的用户都被销毁不是更有意义吗?这样,您就可以在不破坏测试的情况下重构代码(更改限制、删除递归)。
0赞 jansha 11/15/2023
@Stefan 如果我有更多数量的关联用户,比如 10000,每个用户都有多个销毁回调,把它放在同一个工作线程中而不递归调用同一个工作线程会使工作线程运行很长时间,并且它会保留其他线程的队列,该怎么办?
0赞 Stefan 11/15/2023
@jansha,您可以为长时间运行的工作线程定义一个单独的低优先级队列。或者让 worker 为每个用户 ID 启动一个“DestroyUserWorker”(Sidekick 可以轻松处理 10k 个作业)

答:

1赞 Benjamin Scharbau 11/15/2023 #1

我将在这里提出一个框架挑战:在您的特定用例中,您不想递归调用工作线程,而只想用于迭代删除所有用户,即find_eachMymodel

Class Myworker
  def perform(id)
    User.where(mymodel_id: id)
        .find_each do |user|
      user.destroy
    end
  end
end

编辑:如果您不喜欢在一次运行工作线程中删除所有关联对象的想法,并且肯定希望以递归方式运行工作线程,那么在我看来,您最好的选择不是测试工作线程的实际递归量,而是有两个测试用例:

  1. 测试在一次递归中删除的关联记录多于删除的关联记录时是否重新计划工作线程
  2. 测试当可以在一次迭代中删除所有关联记录时,工作线程不会重新调度

这样,您还可以验证工作线程是否将根据需要运行以删除所有记录,而无需验证所使用的递归的实际计数。

(在我家有点晚了,我实际上确实想睡一觉,所以我不在这里放示例代码,如果你需要它,请告诉我,然后我明天可以添加一些)

评论

0赞 jansha 11/15/2023
尽管如此,如果记录计数增加,那么工作线程可能会运行很长时间,对吗?,以避免我们习惯于在每个工作线程中逐批删除数据。为了避免长时间运行的worker和删除记录将花费相同的时间,对,使用find each将提前加载数据并避免内存问题,但是执行时间会相同吧?并且 worker 会长时间运行并保持,因此其他 worker 可能会排队直到完成。
0赞 jansha 11/15/2023
此外,MyModel 不仅有一个关联,它有 5 到 6 个关联模型,每个模型可能有 5000-7000 多条记录。因此,我为每个模型创建了 6 个不同的工作线程,并且在每个模型上,我将像这样一次又一次地递归调用相同的工作线程。
1赞 spickermann 11/15/2023 #2

您说得对,当删除大量数据库记录时,作业将需要更多时间,并最终延迟其他后台作业。

但这确实是一个问题,因为通常无论如何,您都会有多个工作线程同时运行。当您仍然遇到多个大型作业延迟更重要作业的问题时,您应该考虑使用具有不同优先级的多个队列。

当您在 Sidekiq 中配置了多个优先级队列时,您可以将作业更改为在低优先级队列上运行,并且不会像这样阻止关键队列或默认队列:

class DestroyUsersAssociatedToMyModel
  include Sidekiq::Job
  sidekiq_options queue: 'low'

  def perform(id)
    MyModel.find(id).users.destroy_all
  end
end

评论

0赞 jansha 11/15/2023
由于在 mymodel 销毁后删除用户是强制性的,并且需要以高优先级处理,因此我们必须立即删除用户访问权限。所以这就是我计划递归方法的原因。
0赞 jansha 11/15/2023
@spiickermann 此外,MyModel 不仅有一个关联,它有 5 到 6 个关联模型,每个模型可能有 5000-7000 条以上的记录。因此,我为每个模型创建了 6 个不同的工作线程,并且在每个模型上,我将像这样一次又一次地递归调用相同的工作线程
1赞 spickermann 11/15/2023
当涉及到删除关联的记录时,这种方法将不起作用,因为在删除记录时已经失败。相反,我建议使用 Ruby on Rails 的构建来自动删除相关记录。 甚至 :d estroy_async(请参阅有关此功能的文章)MyModel.find(id)MyModel has_many :users, dependent: :destroy