From 2cffc00c444f1fabd16d20321077d55f994c9fd1 Mon Sep 17 00:00:00 2001 From: Daniel Hartnell Date: Fri, 3 Jan 2025 04:53:15 -0800 Subject: [PATCH] Introduce cache lookup instrumentation hook (#125) * Introduce hook to monitor cache hits * Fixes failing specs when query context is missing * Support any number of named arguments * fix: address rubocop violation * Introduce option to enable cache lookup monitoring * Adds documentation for cache monitoring --------- Co-authored-by: Daniel Hartnell --- README.md | 24 ++++++++++++++++++++++++ lib/graphql/fragment_cache.rb | 2 ++ lib/graphql/fragment_cache/fragment.rb | 20 ++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/README.md b/README.md index faa93ab..ecce0a4 100644 --- a/README.md +++ b/README.md @@ -446,6 +446,30 @@ Cache processing can be disabled if needed. For example: GraphQL::FragmentCache.enabled = false if Rails.env.test? ``` +## Cache lookup monitoring + +It may be useful to capture cache lookup events. When monitoring is enabled, the `cache_key`, `operation_name`, `path` and a boolean indicating a cache hit or miss will be sent to a `cache_lookup_event` method. This method can be implemented in your application to handle the event. + +Example handler defined in a Rails initializer: + +```ruby +module GraphQL + module FragmentCache + class Fragment + def self.cache_lookup_event(**args) + # Monitoring such as incrementing a cache hit counter metric + end + end + end +end +``` + +Like managing caching itself, monitoring can be enabled if needed. It is disabled by default. For example: + +```ruby +GraphQL::FragmentCache.monitoring_enabled = true +``` + ## Limitations 1. `Schema#execute`, [graphql-batch](https://github.com/Shopify/graphql-batch) and _graphql-ruby-fragment_cache_ do not [play well](https://github.com/DmitryTsepelev/graphql-ruby-fragment_cache/issues/45) together. The problem appears when `cache_fragment` is _inside_ the `.then` block: diff --git a/lib/graphql/fragment_cache.rb b/lib/graphql/fragment_cache.rb index 710e7a9..ab8e100 100644 --- a/lib/graphql/fragment_cache.rb +++ b/lib/graphql/fragment_cache.rb @@ -25,6 +25,7 @@ module FragmentCache class << self attr_reader :cache_store attr_accessor :enabled + attr_accessor :monitoring_enabled attr_accessor :namespace attr_accessor :default_options @@ -87,6 +88,7 @@ def verify_interpreter_and_analysis!(schema_defn) self.cache_store = MemoryStore.new self.enabled = true + self.monitoring_enabled = false self.namespace = "graphql" self.default_options = {} self.skip_cache_when_query_has_errors = false diff --git a/lib/graphql/fragment_cache/fragment.rb b/lib/graphql/fragment_cache/fragment.rb index ea7ab86..fcae4a8 100644 --- a/lib/graphql/fragment_cache/fragment.rb +++ b/lib/graphql/fragment_cache/fragment.rb @@ -30,6 +30,21 @@ def read_multi(fragments) FragmentCache.cache_store.read_multi(*cache_keys) end + if GraphQL::FragmentCache.monitoring_enabled + begin + fragments.map do |fragment| + cache_lookup_event( + cache_key: fragment.cache_key, + operation_name: fragment.context.query.operation_name, + path: fragment.path, + cache_hit: cache_keys_to_values.key?(fragment.cache_key) + ) + end + rescue + # Allow cache_lookup_event to fail when we do not have all of the requested attributes + end + end + # Fragmenst without values or with renew_cache: true in their context will have nil values like the read method fragments_to_cache_keys .map { |fragment, cache_key| [fragment, cache_keys_to_values[cache_key]] }.to_h @@ -87,6 +102,11 @@ def interpreter_context def final_value @final_value ||= context.query.result["data"] end + + def cache_lookup_event(**args) + # This method can be implemented in your application + # This provides a mechanism to monitor cache hits for a fragment + end end end end