graphql-persisted_queries 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +4 -0
- data/docs/tracing.md +51 -0
- data/lib/graphql/persisted_queries.rb +2 -0
- data/lib/graphql/persisted_queries/schema_patch.rb +17 -1
- data/lib/graphql/persisted_queries/store_adapters/base_store_adapter.rb +30 -3
- data/lib/graphql/persisted_queries/store_adapters/memcached_store_adapter.rb +5 -2
- data/lib/graphql/persisted_queries/store_adapters/memory_store_adapter.rb +5 -2
- data/lib/graphql/persisted_queries/store_adapters/redis_store_adapter.rb +5 -2
- data/lib/graphql/persisted_queries/store_adapters/redis_with_local_cache_store_adapter.rb +13 -2
- data/lib/graphql/persisted_queries/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47cee014870d4382ee5d140302c25a5942ac2e0b8ba3886577a9299e8e097e24
|
4
|
+
data.tar.gz: 1d0406419d6bcce3137863906d72c3d5a670cf56477c166d54b1dc182e016dfa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5cf4c0e7d3021dff138122c176983edc1ebbf17ffad0327423389d354e19f6fdc1ef541be58adb5aa4d364aa0e21bf17226ede8dee47cb94f283d677dae326a
|
7
|
+
data.tar.gz: 10763b571169cdddd8c47c31f4a0350ee8a0a5d3aa6729f6a7669799800b3474810edecd5126c4c6f18c5cf5b52de7ac4f686c5ead9d648eb3032f8cddedbe4c
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.5.0 (2020-03-12)
|
6
|
+
|
7
|
+
- [PR#29](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/29) Instrumentation via graphql-ruby's tracer feature ([@bmorton][])
|
8
|
+
|
5
9
|
## 0.4.0 (2020-02-26)
|
6
10
|
|
7
11
|
- [PR#26](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/26) Add Memcached store ([@JanStevens][])
|
data/README.md
CHANGED
@@ -208,6 +208,10 @@ Using `GET` requests for persisted queries allows you to enable HTTP caching (e.
|
|
208
208
|
|
209
209
|
HTTP method verification is important, because when mutations are allowed via `GET` requests, it's easy to perform an attack by sending the link containing mutation to a signed in user.
|
210
210
|
|
211
|
+
## Tracing and instrumentation
|
212
|
+
|
213
|
+
An experimental tracing feature can be enabled by setting `tracing: true` when configuring the plugin. Read more about this feature in the [Tracing guide](docs/tracing.md).
|
214
|
+
|
211
215
|
## Contributing
|
212
216
|
|
213
217
|
Bug reports and pull requests are welcome on GitHub at https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries.
|
data/docs/tracing.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Tracing
|
2
|
+
|
3
|
+
Tracing is an experimental feature that, when enabled, uses the tracing system defined in `graphql-ruby` to surface these events:
|
4
|
+
|
5
|
+
* `persisted_queries.fetch_query.cache_hit` - Triggered when a store adapter successfully looks up a hash and finds a query.
|
6
|
+
* `persisted_queries.fetch_query.cache_miss` - Triggered when a store adapter attempts to look up a hash but cannot find it.
|
7
|
+
* `persisted_queries.save_query` - Triggered when a store adapter persists a query.
|
8
|
+
|
9
|
+
All events include a metadata hash as their `data` parameter. This hash currently only includes the name of the adapter that triggered the event.
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Tracing must be opted into via the plugin configuration for the events to trigger. Once they are enabled, any tracer that is defined on the schema will get the following events yielded to them. An example configuration will look similar to this:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class GraphqlSchema < GraphQL::Schema
|
17
|
+
use GraphQL::PersistedQueries, tracing: true
|
18
|
+
tracer MyPersistedQueriesTracer
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
Tracers in this plugin integrate with the `GraphQL::Tracing` feature in [`graphql-ruby`](https://github.com/rmosolgo/graphql-ruby). The same tracers are used for tracing events directly from `graphql-ruby` and this plugin. The [guide on "Tracing"](https://graphql-ruby.org/queries/tracing.html) in `graphql-ruby` has implementation details, but an example tracer would look similar to this:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class MyPersistedQueriesTracer
|
26
|
+
def self.trace(key, data)
|
27
|
+
yield.tap do |result|
|
28
|
+
# Note: this tracer will get called for these persisted queries events as
|
29
|
+
# well as all events traced by the graphql-ruby gem.
|
30
|
+
case key
|
31
|
+
when "persisted_queries.fetch_query.cache_hit"
|
32
|
+
# data = { adapter: :redis }
|
33
|
+
# result = nil
|
34
|
+
# increment a counter metric to track cache hits
|
35
|
+
when "persisted_queries.fetch_query.cache_miss"
|
36
|
+
# data = { adapter: :redis }
|
37
|
+
# result = nil
|
38
|
+
# increment a counter metric to track cache misses
|
39
|
+
when "persisted_queries.save_query"
|
40
|
+
# data = { adapter: :redis }
|
41
|
+
# result = return value from method call
|
42
|
+
# increment a counter metric to track saved queries
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
## Be aware of tracers-as-notifications
|
50
|
+
|
51
|
+
A word of caution about the `cache_hit` and `cache_miss` events: they yield an empty block. The `GraphQL::Tracing` feature typically wraps around the code performing the event. The `save_query` event works this way, too -- the block that is yielded is essentially the `StoreAdapter#save` method. This means you can add timing instrumentation for this call. However, the `cache_hit` and `cache_miss` events are simply event notifications and do not wrap any code. This means that they won't yield anything meaningful and they can't be timed.
|
@@ -21,6 +21,8 @@ module GraphQL
|
|
21
21
|
error_handler = options.delete(:error_handler) || :default
|
22
22
|
schema.configure_persisted_query_error_handler(error_handler)
|
23
23
|
|
24
|
+
schema.persisted_queries_tracing_enabled = options.delete(:tracing)
|
25
|
+
|
24
26
|
store = options.delete(:store) || :memory
|
25
27
|
schema.configure_persisted_query_store(store, options)
|
26
28
|
end
|
@@ -16,9 +16,12 @@ module GraphQL
|
|
16
16
|
end
|
17
17
|
|
18
18
|
attr_reader :persisted_query_store, :hash_generator_proc, :persisted_query_error_handler
|
19
|
+
attr_writer :persisted_queries_tracing_enabled
|
19
20
|
|
20
21
|
def configure_persisted_query_store(store, options)
|
21
|
-
@persisted_query_store = StoreAdapters.build(store, options)
|
22
|
+
@persisted_query_store = StoreAdapters.build(store, options).tap do |adapter|
|
23
|
+
adapter.tracers = tracers if persisted_queries_tracing_enabled?
|
24
|
+
end
|
22
25
|
end
|
23
26
|
|
24
27
|
def configure_persisted_query_error_handler(handler)
|
@@ -41,9 +44,22 @@ module GraphQL
|
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
47
|
+
def persisted_queries_tracing_enabled?
|
48
|
+
@persisted_queries_tracing_enabled
|
49
|
+
end
|
50
|
+
|
44
51
|
def multiplex(queries, **kwargs)
|
45
52
|
MultiplexResolver.new(self, queries, kwargs).resolve
|
46
53
|
end
|
54
|
+
|
55
|
+
def tracer(name)
|
56
|
+
super.tap do
|
57
|
+
# Since tracers can be set before *and* after our plugin hooks in,
|
58
|
+
# we need to set tracers both when this plugin gets initialized
|
59
|
+
# and any time a tracer is added after initialization
|
60
|
+
persisted_query_store.tracers = tracers if persisted_queries_tracing_enabled?
|
61
|
+
end
|
62
|
+
end
|
47
63
|
end
|
48
64
|
end
|
49
65
|
end
|
@@ -5,15 +5,42 @@ module GraphQL
|
|
5
5
|
module StoreAdapters
|
6
6
|
# Base class for all store adapters
|
7
7
|
class BaseStoreAdapter
|
8
|
-
|
8
|
+
include GraphQL::Tracing::Traceable
|
9
|
+
attr_writer :tracers
|
9
10
|
|
10
|
-
def
|
11
|
+
def initialize(_options)
|
12
|
+
@name = :base
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch_query(hash)
|
16
|
+
fetch(hash).tap do |result|
|
17
|
+
event = result ? "cache_hit" : "cache_miss"
|
18
|
+
trace("fetch_query.#{event}", adapter: @name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def save_query(hash, query)
|
23
|
+
trace("save_query", adapter: @name) { save(hash, query) }
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def fetch(_hash)
|
11
29
|
raise NotImplementedError
|
12
30
|
end
|
13
31
|
|
14
|
-
def
|
32
|
+
def save(_hash, _query)
|
15
33
|
raise NotImplementedError
|
16
34
|
end
|
35
|
+
|
36
|
+
def trace(key, metadata)
|
37
|
+
if @tracers
|
38
|
+
key = "persisted_queries.#{key}"
|
39
|
+
block_given? ? super : super {}
|
40
|
+
elsif block_given?
|
41
|
+
yield
|
42
|
+
end
|
43
|
+
end
|
17
44
|
end
|
18
45
|
end
|
19
46
|
end
|
@@ -14,13 +14,16 @@ module GraphQL
|
|
14
14
|
@dalli_proc = build_dalli_proc(dalli_client)
|
15
15
|
@expiration = expiration || DEFAULT_EXPIRATION
|
16
16
|
@namespace = namespace || DEFAULT_NAMESPACE
|
17
|
+
@name = :memcached
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
+
protected
|
21
|
+
|
22
|
+
def fetch(hash)
|
20
23
|
@dalli_proc.call { |dalli| dalli.get(key_for(hash)) }
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
26
|
+
def save(hash, query)
|
24
27
|
@dalli_proc.call { |dalli| dalli.set(key_for(hash), query, @expiration) }
|
25
28
|
end
|
26
29
|
|
@@ -7,13 +7,16 @@ module GraphQL
|
|
7
7
|
class MemoryStoreAdapter < BaseStoreAdapter
|
8
8
|
def initialize(_options)
|
9
9
|
@storage = {}
|
10
|
+
@name = :memory
|
10
11
|
end
|
11
12
|
|
12
|
-
|
13
|
+
protected
|
14
|
+
|
15
|
+
def fetch(hash)
|
13
16
|
@storage[hash]
|
14
17
|
end
|
15
18
|
|
16
|
-
def
|
19
|
+
def save(hash, query)
|
17
20
|
@storage[hash] = query
|
18
21
|
end
|
19
22
|
end
|
@@ -14,13 +14,16 @@ module GraphQL
|
|
14
14
|
@redis_proc = build_redis_proc(redis_client)
|
15
15
|
@expiration = expiration || DEFAULT_EXPIRATION
|
16
16
|
@namespace = namespace || DEFAULT_NAMESPACE
|
17
|
+
@name = :redis
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
+
protected
|
21
|
+
|
22
|
+
def fetch(hash)
|
20
23
|
@redis_proc.call { |redis| redis.get(key_for(hash)) }
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
26
|
+
def save(hash, query)
|
24
27
|
@redis_proc.call { |redis| redis.set(key_for(hash), query, ex: @expiration) }
|
25
28
|
end
|
26
29
|
|
@@ -19,9 +19,20 @@ module GraphQL
|
|
19
19
|
namespace: namespace
|
20
20
|
)
|
21
21
|
@memory_adapter = memory_adapter_class.new(nil)
|
22
|
+
@name = :redis_with_local_cache
|
22
23
|
end
|
23
24
|
|
24
|
-
|
25
|
+
# We don't need to implement our own traces for this adapter since the
|
26
|
+
# underlying adapters will emit the proper events for us. However,
|
27
|
+
# since tracers can be defined at any time, we need to pass them through.
|
28
|
+
def tracers=(tracers)
|
29
|
+
@memory_adapter.tracers = tracers
|
30
|
+
@redis_adapter.tracers = tracers
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def fetch(hash)
|
25
36
|
result = @memory_adapter.fetch_query(hash)
|
26
37
|
result ||= begin
|
27
38
|
inner_result = @redis_adapter.fetch_query(hash)
|
@@ -31,7 +42,7 @@ module GraphQL
|
|
31
42
|
result
|
32
43
|
end
|
33
44
|
|
34
|
-
def
|
45
|
+
def save(hash, query)
|
35
46
|
@redis_adapter.save_query(hash, query)
|
36
47
|
@memory_adapter.save_query(hash, query)
|
37
48
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-persisted_queries
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DmitryTsepelev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-03-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -125,6 +125,7 @@ files:
|
|
125
125
|
- Rakefile
|
126
126
|
- bin/console
|
127
127
|
- bin/setup
|
128
|
+
- docs/tracing.md
|
128
129
|
- gemfiles/graphql_1_10.gemfile
|
129
130
|
- gemfiles/graphql_1_8.gemfile
|
130
131
|
- gemfiles/graphql_1_9.gemfile
|