Rails.cache.fetch_multi するGraphQL BatchのLoader
Rails.cache.fetch_multi
するLoaderを書いてみた。
コードのライセンスはCC-0とするのでご自由にお使いください。
class RailsCacheLoader < GraphQL::Batch::Loader def initialize end # @param args [Array<[untyped, Proc]>] The first item is cache key, and the second item is fallback. def perform(args) fallback_map = args.to_h result = Rails.cache.fetch_multi(*fallback_map.keys) do |key| fallback_map[key].call end result.each do |cache_key, value| fulfill([cache_key, value], value) end end def cache_key(arg) arg.first end end
こう使う。
field :heavy_field, String, null: false, resolve: -> (obj, _args, context) { RailsCacheLoader.load([ ["heavy_field", obj, context.fetch(:current_user)], -> () { obj.fetch_heavy_field(by: context.fetch(:current_user)) } ]) }
これは次のコードと同様に動作しつつ、Rails.cache
のバックエンド(memcachedなど)へのアクセスが1回で済む。
field :heavy_field, String, null: false, resolve: -> (obj, _args, context) { Rails.cache.fetch(["heavy_field", obj, context.fetch(:current_user)]) do obj.fetch_heavy_field(by: context.fetch(:current_user)) end }
手元のRails appで対象のfieldを100件fetchするクエリを試したところ、RailsCacheLoader
を使ってもRails.cache.fetch
を書いてもほとんど速度差は出なかった。localhostに建っているmemcachedへのアクセスなので、差が出づらいのは予想できる。
本番環境には突っ込んでいないため、実際どの程度速くなるかは未確認。
本番環境に突っ込んでいないのはこのLoaderの問題ではなく、そもそも対象のfieldをキャッシュするべきか悩んでいるため。 微妙に遅いfieldの取得を速くするためだけにキャッシュに手を染めるべきか、微妙なところ。