Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce graphql-cache #411

Merged
merged 9 commits into from
Apr 22, 2020
Merged

Introduce graphql-cache #411

merged 9 commits into from
Apr 22, 2020

Conversation

dLobatog
Copy link
Contributor

This PR introduces graphql-cache. I'll comment in each file what are the relevant changes.

@@ -11,7 +11,7 @@ gem 'puma', '~> 3.12'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
# gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
gem 'redis', '~> 4.0'
gem 'hiredis'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a more performant version of the redis gem

@@ -68,13 +68,14 @@ gem 'prometheus_exporter'

gem 'activerecord-import'
gem 'oj'
gem 'newrelic_rpm'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are simply not using it and it adds overhead to every API call

@@ -15,6 +15,5 @@ class Schema < GraphQL::Schema
mutation Types::Mutation
lazy_resolve(Promise, :sync)
use GraphQL::Batch
use GraphQL::Execution::Interpreter
use GraphQL::Analysis::AST
use GraphQL::Cache
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the new interpreter is incompatible with gql-cache (https://graphql-ruby.org/queries/interpreter.html) (stackshareio/graphql-cache#71)

@@ -35,7 +35,7 @@ def references
if context[:"rule_references_#{object.id}"].nil?
::CollectionLoader.for(::Rule, :rule_references)
.load(object).then do |references|
references.map { |ref| [ref.href, ref.label] }.to_json
references.map { |ref| { href: ref.href, label: ref.label} }.to_json
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just makes sure we always return the same object. Notice how references_from_context doesn't return this object exactly.

config.namespace = 'compliance' # Cache key prefix for keys generated by graphql-cache
config.cache = Rails.cache # The cache object to use for caching
config.logger = Rails.logger # Logger to receive cache-related log messages
config.expiry = 15552000 # 6 months (in seconds)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably discuss this and even think more seriously about introducing an LRU cache redis instance directly. Fields are cached alongside its updated_at, e.g: compliance:Rule:rules/30b9d4c7-4924-4996-bd89-7e29117fccf7-20200227230834209106:title to force us to realize whether the key can be used or not. It's a sort of 'automatic invalidation' as any time a rule is updated, the old entries will be useless.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the old entries with older updated_at in the key are kept in redis?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, that's why I'd want to go with an instance like https://redis.io/topics/lru-cache

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, that's why I'd want to go with an instance like https://redis.io/topics/lru-cache

module Cache
module DeconstructorExtensions
def perform
return raw.value if method == 'lazy'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GraphQL::Cache::Deconstructor figures out which sort of value each field is returning. Based on that, the current methods cannot handle lazy methods, or Promises. This is just a way to handle those.


class Key
def to_s
@to_s ||= [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This overrides the way the keys are set - I'll comment more on this in a few

Copy link
Contributor

@akofink akofink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good so far! Thanks for exploring this option - it looks much cleaner than the heavy caching implementation. I have a couple questions inline.

@@ -4,6 +4,7 @@
# rule results, compliant,and references
module RulesPreload
include GraphQL::Schema::Interface
field_class GraphQL::Cache::Field
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this required for some reason? This module is implemented from classes which inherit from BaseObject, so I thought the field_class would be set already.

config.namespace = 'compliance' # Cache key prefix for keys generated by graphql-cache
config.cache = Rails.cache # The cache object to use for caching
config.logger = Rails.logger # Logger to receive cache-related log messages
config.expiry = 15552000 # 6 months (in seconds)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the old entries with older updated_at in the key are kept in redis?

@dLobatog dLobatog mentioned this pull request Apr 21, 2020
Copy link
Contributor

@akofink akofink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Thanks for figuring this all out! 🥇 It's definitely faster in the UI. I tested with the in memory cache (not with redis), but the speed should be comparable. I also tested by directly querying the graphql API; Despite caching, it seems much of the time is still spent doing something else (note views and AR only take ~3 milliseconds): the complete response with cache hits still takes ~6 seconds. Hopefully most of that time is dev-related stuff like determining if there are any code changes or something. 🤷 Also note that this graphql query is simple - caching should really speed up those complex queries like we have in the UI.

Started POST "/api/compliance/graphql" for 172.18.0.1 at 2020-04-21 21:57:08 +0000
Processing by GraphqlController#query as JSON
  Parameters: {"query"=>"{\n  allProfiles {\n    policy {\n      id\n    }\n  }\n}", "variables"=>{}, "operationName"=>nil, "graphql"=>{"query"=>"{\n  allProfiles {\n    policy {\n      id\n
    }\n  }\n}", "variables"=>{}, "operationName"=>nil}}
  Account Load (0.3ms)  SELECT  "accounts".* FROM "accounts" WHERE "accounts"."account_number" = $1 LIMIT $2  [["account_number", "1212729"], ["LIMIT", 1]]
  ↳ app/controllers/concerns/authentication.rb:53
  Profile Load (0.6ms)  SELECT "profiles".* FROM "profiles" WHERE "profiles"."account_id" = $1  [["account_id", "a1f12604-dc7c-4b29-8433-78eb4118a3d8"]]
  ↳ app/controllers/graphql_controller.rb:8
  ProfileHost Load (0.4ms)  SELECT "profile_hosts".* FROM "profile_hosts" WHERE "profile_hosts"."profile_id" = $1  [["profile_id", "aa141eed-3237-4d5e-8658-3342b9c9f382"]]
  ↳ app/controllers/graphql_controller.rb:8
  Host Load (0.6ms)  SELECT "hosts".* FROM "hosts" WHERE "hosts"."id" = $1  [["id", "fb04c74a-b6cd-4981-81a5-67cd0b791768"]]
  ↳ app/controllers/graphql_controller.rb:8
Cache miss: (compliance:Profile:profiles/aa141eed-3237-4d5e-8658-3342b9c9f382-20200421171203786540:policy)
Cache miss: (compliance:Profile:profiles/aa141eed-3237-4d5e-8658-3342b9c9f382-20200421171203786540:id)
Completed 200 OK in 6619ms (Views: 0.1ms | ActiveRecord: 27.3ms)
Started POST "/api/compliance/graphql" for 172.18.0.1 at 2020-04-21 21:47:38 +0000
Processing by GraphqlController#query as JSON
  Parameters: {"query"=>"{\n  allProfiles {\n    policy {\n      id\n    }\n  }\n}", "variables"=>{}, "operationName"=>nil, "graphql"=>{"query"=>"{\n  allProfiles {\n    policy {\n      id\n    }\n  }\n}", "variables"=>{}, "operationName"=>nil}}
  Account Load (0.6ms)  SELECT  "accounts".* FROM "accounts" WHERE "accounts"."account_number" = $1 LIMIT $2  [["account_number", "1212729"], ["LIMIT", 1]]
  ↳ app/controllers/concerns/authentication.rb:53
  Profile Load (0.8ms)  SELECT "profiles".* FROM "profiles" WHERE "profiles"."account_id" = $1  [["account_id", "a1f12604-dc7c-4b29-8433-78eb4118a3d8"]]
  ↳ app/controllers/graphql_controller.rb:8
  ProfileHost Load (0.8ms)  SELECT "profile_hosts".* FROM "profile_hosts" WHERE "profile_hosts"."profile_id" = $1  [["profile_id", "aa141eed-3237-4d5e-8658-3342b9c9f382"]]
  ↳ app/controllers/graphql_controller.rb:8
  Host Load (0.8ms)  SELECT "hosts".* FROM "hosts" WHERE "hosts"."id" = $1  [["id", "fb04c74a-b6cd-4981-81a5-67cd0b791768"]]
  ↳ app/controllers/graphql_controller.rb:8
Cache hit: (compliance:Profile:profiles/aa141eed-3237-4d5e-8658-3342b9c9f382-20200421171203786540:policy)
Cache hit: (compliance:Profile:profiles/aa141eed-3237-4d5e-8658-3342b9c9f382-20200421171203786540:id)
Completed 200 OK in 5977ms (Views: 0.3ms | ActiveRecord: 3.0ms)

@dLobatog dLobatog merged commit 3854f85 into RedHatInsights:master Apr 22, 2020
dLobatog added a commit to dLobatog/compliance-backend that referenced this pull request Apr 22, 2020
* GQL-cache POC

* Don't cache batched fields

* Cache lazy types

* Wait for promises before saving to cache

* Re-enable promise fields and to_s

* Use hiredis

* Uncomment timeout in schema

* Rubocop fixes

* Fix href/label test
dLobatog added a commit that referenced this pull request Apr 22, 2020
* Fix transient profile test failure (#407)

Signed-off-by: Andrew Kofink <[email protected]>

* Hardcode latest RHEL supported versions (#408)

* Hardcode latest RHEL supported versions

* Add cache for latest supported versions

* Freeze constant

* Return an AR relation

* Properly search for match ref_id/version

* Task to import supported SSG benchmarks (#412)

* Task to import supported SSG benchmarks

* Regex way of getting RHEL number

Co-Authored-By: Andrew Kofink <[email protected]>

* Fix rubocop

Co-authored-by: Andrew Kofink <[email protected]>

* Introduce graphql-cache (#411)

* GQL-cache POC

* Don't cache batched fields

* Cache lazy types

* Wait for promises before saving to cache

* Re-enable promise fields and to_s

* Use hiredis

* Uncomment timeout in schema

* Rubocop fixes

* Fix href/label test

* Add benchmarks API endpoint (#409)

Signed-off-by: Andrew Kofink <[email protected]>

* Fixes for rake tasks  (#414)

* Require HostInventoryNotFound

* Execute instead of invoking import SSG task

* Add specific config for cache redis (#415)

Co-authored-by: Andrew Kofink <[email protected]>
Co-authored-by: Andrew Kofink <[email protected]>
dLobatog added a commit to dLobatog/compliance-backend that referenced this pull request Apr 22, 2020
dLobatog added a commit that referenced this pull request Apr 22, 2020
* Revert "Introduce graphql-cache (#411)"

This reverts commit 3854f85.

* Unify references JSON response
dLobatog added a commit to dLobatog/compliance-backend that referenced this pull request Apr 22, 2020
dLobatog added a commit to dLobatog/compliance-backend that referenced this pull request Apr 22, 2020
* Revert "Introduce graphql-cache (RedHatInsights#411)"

This reverts commit 3854f85.

* Unify references JSON response
dLobatog added a commit that referenced this pull request Apr 22, 2020
* Revert "Introduce graphql-cache (#411)"

This reverts commit 3854f85.

* Unify references JSON response
dLobatog added a commit to dLobatog/compliance-backend that referenced this pull request Apr 23, 2020
* GQL-cache POC

* Don't cache batched fields

* Cache lazy types

* Wait for promises before saving to cache

* Re-enable promise fields and to_s

* Use hiredis

* Uncomment timeout in schema

* Rubocop fixes

* Fix href/label test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants