1

我的 Rails 应用程序中似乎有竞争条件。在删除用户和依赖于它的所有关联模型时,用户有时会创建新的关联模型。如果我们要删除大量内容,用户删除可能需要一段时间,因此这里存在竞争条件是有道理的。这最终会创建指向不存在的用户的模型。

我已经尝试通过创建一个 UserDeletion 模型来解决这个问题,该模型充当一种互斥锁。在开始删除用户之前,它将创建一个新的 UserDeletion 记录。当用户尝试创建新内容时,它会检查以确保关联的 UserDeletion 记录不存在。完成后,它会删除它。

不过,这并没有解决问题,所以我想知道其他人是如何处理 AR 回调和竞争条件的类似问题的。

4

1 回答 1

-1

First of all when there is a lot of content associated, we moved on to use manual delete process using SQL DELETE instead off Rails destroy. (Though this may not work for You, If You carelessly introduced a lot of callback dependencies that does something after record is destroyed)

def custom_delete
  self.class.transaction do
    related_objects.delete_all
    related_objects_2.delete_all
    delete
  end
end

If You find Yourself writing this all the time, You can simply wrap it inside class method that accepts list of related_objects keys to delete.

class ActiveRecord::Base
  class << self
    def bulk_delete_related(*args)
      define_method "custom_delete" do
        ActiveRecord::Base.transaction do
          args.each do |field|
            send(field).delete_all
          end
        end

        delete
      end
    end
  end
end


class SomeModel < ActiverRecord::Base
  bulk_delete :related_objects, :related_objects2, :related_object
end

I inserted the class method inside ActiveRecord::Base class directly, but probably You should better extract it to module. Also this only speeds things up, but does not resolve the original problem.

Secondly You can introduce FK constraints (we did that to ensure integrity, as we do a lot of custom SQL). It will work the way that User won't be deleted as long as there are linked objects. Though it might not be what You want. To increase effectivity of this solution You can always delegate user deletion to a background job, that will retry deleting user until it's actually can be deleted (no new objects dropped in)

Alternatively You can do the other way around as we did at my previous work in some cases. If it's more important to delete user rather than to be sure that there are no zombie records, use some swipe process to clean up time to time.

Finally the truth is somewhere in the middle - apply constraints to relations that definitely need to be cleaned up before removing user and just rely on sweeper to remove less important ones that shouldn't interfere with user deletion.

Problem is not trivial but it should be solvable to some extent.

于 2013-10-15T15:21:47.193 回答