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

Caching Results

GraphQL::Enterprise::ObjectCache supports several different caching configurations for objects and fields. To get started, include the extension in your base object class and base field class and use cacheable(...) to set up the default cache behavior:

# app/graphql/types/base_object.rb
class Types::BaseObject < GraphQL::Schema::Object
  include GraphQL::Enterprise::ObjectCache::ObjectIntegration
  cacheable(...) # see below
  # ...
end
# app/graphql/types/base_field.rb
class Types::BaseField < GraphQL::Schema::Field
  include GraphQL::Enterprise::ObjectCache::FieldIntegration
  cacheable(...) # see below
  # ...
end

Field caching can be configured per-field, too, for example:

field :latest_update, Types::Update, null: false, cacheable: { ttl: 60 }

field :random_number, Int, null: false, cacheable: false

Only queries are cached. ObjectCache skips mutations and subscriptions altogether.

cacheable(true|false)

cacheable(true) means that the configured type or field may be stored in the cache until its cache fingerprint changes. It also defaults to public: false, meaning that clients will not share cached responses. See public: below for more about this option.

cacheable(false) disables caching for the configured type or field. Any query that includes this type or field will neither check for an already-cached value nor update the cache with its result.

public:

cacheable(public: false) means that a type or field may be cached, but Schema.private_context_fingerprint_for(ctx) should be included in its cache key. In practice, this means that each client can have its own cached responses. Any query that contains a cacheable(public: false) type or field will use a private cache key.

cacheable(public: true) means that cached values from this type or field may be shared by all clients. Use this for public-facing data which is the same for all viewers. Queries that include only public: true types and fields will not include Schema.private_context_fingerprint_for(ctx) in their cache keys. That way their responses will be shared by all clients who request them.

ttl:

cacheable(ttl: seconds) expires any cached value after the given number of seconds, regardless of cache fingerprint. ttl: shines in a few cases:

Under the hood, ttl: is implemented with Redis’s EXPIRE.

Caching lists and connections

Lists and connections require a little extra consideration. In order to effectively bust the cache, items that belong to the list of “parent” object should update the parent whenever they’re modified in a way that changes the state of the list. For example, if there’s a list of players on a team:

{
  team { players { totalCount } }
}

None of the specific Players will be part of the cached response, but the Team will be. To properly invalidate the cache, the Team’s updated_at (or other cache key) should be updated whenever a Player is added or removed from the Team.

If a list may be sorted, then updates to Players should also update the Team so that any sorted results in the cache are invalidated, too. Alternatively (or additionally), you could use a ttl: to expire cached results after a certain duration, just to be sure that results are eventually expired.

By default, connection-related objects (like *Connection and *Edge types) “inherit” cacheability from their node types. You can override this in your base classes as long as GraphQL::Enterprise::ObjectCache::ObjectIntegration is included in the inheritance chain somewhere.