ActiveJob GlobalID 和内存中的 ActiveRecord 对象

ActiveJob GlobalID and in-memory ActiveRecord objects

我正在使用排队系统 (Sidekiq) 并且想转移到 ActiveJob 以获得性能优势,因为我每次将 ActiveRecord 对象传递给工作人员时都不必查询数据库。我想问并确认,因为我不是 100% 确定,但我的理解是,当 ActiveJob 使用 GlobalID 传递 ActiveRecord 对象时,这些对象全部在内存中完成,并且没有完成对数据库的单独查询,对吗?

这是不正确的。

如果您使用 ActiveJob,它会将任何 ActiveRecord 对象序列化为 global_id 字符串,以便保存到您的队列中。然后在作业开始时从该字符串中再次查找它。默认情况下,该字符串仅包含应用程序名称、class 名称和 ID,它将使用您的数据库加载模型。

"gid://app/User/1"

DelayedJob 会将您提供给它的任何对象序列化为 yaml 字符串,并在加载作业后不影响数据库的情况下反序列化它。您也可以使用 Sidekiq 执行此操作,而不是点击 Redis 来加载作业而不是触及主数据库。

user = User.find(1)
MyJob.perform_later(user.to_yaml)

# Load the user object from the yaml
YAML::load(user.to_yaml) == user # true

您无需前往数据库即可获得您的物品。然而,YAML 会很大,Redis 带来的性能损失可能不值得。

您还应该注意一些陷阱。该对象可能在数据和结构方面都已过时。如果更改代码,序列化对象可能会因结构更改而无法再次加载。如果您在序列化对象后更新数据库,当您加载它时,您将在不知不觉中使用旧数据。

希望能帮助您了解 ActiveJob 和 GlobalId 提供的功能。

无论如何都会执行数据库查询,但是是透明的。以下面的代码为例,ActiveJob内部做了什么:

gid = User.find(1).to_global_id
  User Load (0.8ms)  SELECT  "users".* FROM "users" WHERE "users"."id" =  LIMIT   [["id", 1], ["LIMIT", 1]]
=> #<GlobalID:0x00007f86f76f46d8 @uri=#<URI::GID gid://app/User/1>>

然后,当作业执行时,ActiveJob 在内部运行以下代码,无论如何都会查询数据库:

GlobalID::Locator.locate(gid)
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" =  LIMIT   [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, ... >

使用 GlobalIDs 的一个问题是,如果在作业入队之后但在调用 #perform 方法之前删除传递的记录,Active Job 将引发 ActiveJob::DeserializationError 异常。

性能

根据 Sidekiq 的作者 Mike Perham 的说法,基准测试表明 ActiveJob 将作业推送到 Redis 的速度要慢 2-20 倍,处理开销大约是原来的 3 倍 (https://github.com/mperham/sidekiq/wiki/Active-Job#performance)。

附加信息

有关 Sidekiq、ActiveJob 和 GlobalID 的所有信息都可以在这里找到:https://github.com/mperham/sidekiq/wiki/Active-Job#using-global-id