🌟 Enterprise Feature 🌟 This feature is bundled with GraphQL-Enterprise.

Schema Setup

To prepare the schema to serve cached responses, you have to add GraphQL::Enterprise::ObjectCache and implement a few hooks.

Add the Cache

In your schema, add use GraphQL::Enterprise::ObjectCache, redis: ...:

class MySchema < GraphQL::Schema
  use GraphQL::Enterprise::ObjectCache, redis: CACHE_REDIS
end

See the Redis guide or Memcached guide for details about configuring cache storage.

Additionally, it accepts some options for customizing how introspection is cached, see Caching Introspection

Context Fingerprint

Additionally, you should implement def self.private_context_fingerprint_for(context) to return a string identifying the private scope of the given context. This method will be called whenever a query includes a public: false type or field. For example:

class MySchema < GraphQL::Schema
  # ...
  def self.private_context_fingerprint_for(context)
    viewer = context[:viewer]
    if viewer.nil?
      # This should never happen, but just in case:
      raise("Invariant: No viewer in context! Can't create a private context fingerprint" )
    end

    # include permissions in the fingerprint so that if the viewer's permissions change, the cache will be invalidated
    permission_fingerprint = viewer.team_memberships.map { |tm| "#{tm.team_id}/#{tm.permission}" }.join(":")

    "user:#{viewer.id}:#{permission_fingerprint}"
  end
end

Whenever queries including public: false are cached, the private context fingerprint will be part of the cache key, preventing responses from being shared between different viewers.

The returned String should reflect any aspects of context that, if changed, should invalidate the cache. For example, if a user’s permission level or team memberships change, then any previously-cached responses should be ignored.

Object Fingerprint

In order to determine whether cached results should be returned or invalidated, GraphQL needs a way to determine the “version” of each object in the query. It uses Schema.object_fingerprint_for(object) to do this. By default, it checks .cache_key_with_version (implemented by Rails), then .to_param, then it returns nil. Returning nil tells the cache not to use the cache at all. To customize this behavior, you can implement def self.object_fingerprint_for(object) in your schema:

class MySchema < GraphQL::Schema
  # ...

  # For example, if you defined `.custom_cache_key` and `.uncacheable?`
  # on objects in your application:
  def self.object_fingerprint_for(object)
    if object.respond_to?(:custom_cache_key)
      object.custom_cache_key
    elsif object.respond_to?(:uncacheable?) && object.uncacheable?
      nil # don't cache queries containing this object
    else
      super
    end
  end
end

The returned strings are used as cache keys in the database – whenever they change, stale data is left to be cleaned up by Redis.

Object Identification

ObjectCache depends on object identification hooks used elsewhere in GraphQL-Ruby:

After your schema is setup, you can configure caching on your types and fields.

Schema Fingerprint

ObjectCache will also call .fingerprint on your Schema class. You can implement this method to return a new string if you make breaking changes to your schema, for example:

class MySchema < GraphQL::Schema
  def self.fingerprint
    "v2" # increment this if there are breaking changes to the schema
  end
end

By returning a new MySchema.fingerprint, all previously-cached results will be expired.

Disabling Reauthorization

By default, ObjectCache checks .authorized? on each object before returning a cached result. However, if all authorization-related considerations are present in the object’s cache fingerprint, then you can disable this check in two ways: