我正在使用排队系统(Sidekiq)并希望转移到ActiveJob以获得性能优势,即每次将ActiveRecord对象传递给worker时都不必查询数据库。我想询问并确认,因为我不是100%肯定,但我的理解是,当ActiveJob使用GlobalID传递ActiveRecord对象时,这些对象全部在内存中完成并且未对数据库进行单独查询,是否正确?
答案 0 :(得分:1)
这是不正确的。
如果使用ActiveJob,它会将任何ActiveRecord对象序列化为global_id字符串,以便保存到队列中。然后在作业开始时再从该字符串中查找它。默认情况下,该字符串仅包含应用程序名称,类名和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
你可以在没有去DB的情况下得到你的对象。但是,YAML会很大,而Redis的性能损失可能不值得。
还有一些你应该注意的陷阱。在数据和结构方面,对象可能已过时。如果更改代码,由于结构更改,序列化对象可能无法再次加载。如果在序列化对象后更新数据库,则在加载对象时,您将在不知情的情况下使用旧数据。
希望能帮助您了解ActiveJob和GlobalId提供的内容。
答案 1 :(得分:0)
数据库查询将同样但透明地执行。以下面的代码作为ActiveJob内部操作的示例:
gid = User.find(1).to_global_id
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["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" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, ... >
使用GlobalID的一个问题是,如果在作业入队后但在#perform方法被调用之前删除传递的记录,则活动作业将引发ActiveJob::DeserializationError
异常。
根据Sidekiq的作者Mike Perham进行的基准测试显示,ActiveJob将作业推送到Redis的速度要慢2到20倍,并且处理开销大约是(https://github.com/mperham/sidekiq/wiki/Active-Job#performance的3倍)。
有关Sidekiq,ActiveJob和GlobalID的所有信息都可以在这里找到:https://github.com/mperham/sidekiq/wiki/Active-Job#using-global-id