kasket 4.12.0 → 4.14.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52a098184223192a1d4a6cd6a92363dd5f9324b4d2c56da1fa33769402e0c602
4
- data.tar.gz: 4fca1aba72b41281fb71f91a61ba18d96edb93cb60ff94924c63fe7353bd96d7
3
+ metadata.gz: a6e7fba4b86593d0f6df819b8e4af5417db71843ca84f6aa713688fad70af03d
4
+ data.tar.gz: d0b55338667baf18f1f85b74eb6063e1c2c216fb66bcc6f66c8f0c72a8233b58
5
5
  SHA512:
6
- metadata.gz: 4aa6be9f52ab44578929a3e66fa2ddfb71a6d935e00d13e80258fe4ec413ac69cae42666512e3711c189edf03e6e011ecd64a13d9a210449edc292aeea098680
7
- data.tar.gz: 0131bde86df784810b892da100e6e5c26e40620071c6169def6f7cbb7f176775ed981c7174c2be247a1c188053b755dab6469d9cd39e51104921f57d73721f72
6
+ metadata.gz: 258485dd05909a3741841bac6ef9c1fbf41ac37c8978f9efa2284cf0caa3a05dbc38ba76c7f74b8ee033ff6106ddbbcd9a0b86943323e08e4e2dd8d95ee86a99
7
+ data.tar.gz: bf59e003102eef756e49977f99441e4f0930c0032bce7dc183f47e27b2e0887f5d682bdb1b34cd5e3892136e9ebf21d18dbe9ad5c685d3daaf8271b2d5c4c2a1
data/README.md CHANGED
@@ -48,6 +48,19 @@ semantics instead. In this mode, the model will be updated in the cache as well
48
48
  Kasket.setup(write_through: true)
49
49
  ```
50
50
 
51
+ #### Events Callback
52
+
53
+ You can configure a callable object to listen to events. This can be useful to emit metrics and observe Kasket's behaviour.
54
+
55
+ ```ruby
56
+ Kasket.setup(events_callback: -> (event, ar_klass) do
57
+ MyMetrics.increase_some_counter("kasket.#{event}", tags: ["table:#{ar_klass.table_name}"])
58
+ end)
59
+ ```
60
+
61
+ The following events are emitted:
62
+ * `"cache_hit"`, when Kasket has found some record's data in the cache, which can be returned.
63
+
51
64
  ## Configuring caching of your models
52
65
 
53
66
  You can configure Kasket for any ActiveRecord model, and subclasses will automatically inherit the caching
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kasket
4
+ # Interface to the internal instrumentation event.
5
+ module Events
6
+ class << self
7
+ # Invokes the configured events callback, if provided.
8
+ #
9
+ # The callback behaves like a listener, and receives the same arguments
10
+ # that are passed to this `report` method.
11
+ #
12
+ # @param [String] event the type of event being instrumented.
13
+ # @param [class] ar_klass the ActiveRecord::Base subclass that the event
14
+ # refers to.
15
+ #
16
+ # @return [nil]
17
+ #
18
+ def report(event, ar_klass)
19
+ return unless fn
20
+
21
+ fn.call(event, ar_klass)
22
+ nil
23
+ end
24
+
25
+ private
26
+
27
+ def fn
28
+ return @fn if defined?(@fn)
29
+
30
+ @fn = Kasket::CONFIGURATION[:events_callback]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -23,7 +23,7 @@ module Kasket
23
23
 
24
24
  if query && has_kasket_index_on?(query[:index])
25
25
  if query[:key].is_a?(Array)
26
- filter_pending_records(find_by_sql_with_kasket_on_id_array(query[:key]))
26
+ filter_pending_records(find_by_sql_with_kasket_on_id_array(query[:key]), &blk)
27
27
  else
28
28
  if value = Kasket.cache.read(query[:key])
29
29
  # Identified a specific edge case where memcached server returns 0x00 binary protocol response with no data
@@ -36,9 +36,16 @@ module Kasket
36
36
  result_set = if value.is_a?(TrueClass)
37
37
  find_by_sql_without_kasket(sql, binds, *restargs, **kwargs, &blk)
38
38
  elsif value.is_a?(Array)
39
+ # The data from the Kasket cache is a list of keys to other Kasket entries.
40
+ # This usually happens when we're trying to load a collection association,
41
+ # e.g. a list of comments using their post_id in the query.
42
+ # Do not report a cache hit yet, and defer it until we've verified that at
43
+ # least one of the retrieved keys is actually in the cache.
39
44
  filter_pending_records(find_by_sql_with_kasket_on_id_array(value))
40
45
  else
41
- filter_pending_records(Array.wrap(value).collect { |record| instantiate(record.dup) })
46
+ # Direct cache hit for the key.
47
+ Events.report("cache_hit", self)
48
+ filter_pending_records(Array.wrap(value).collect { |record| instantiate(record.dup, &blk) })
42
49
  end
43
50
 
44
51
  payload = {
@@ -56,11 +63,14 @@ module Kasket
56
63
  end
57
64
  end
58
65
 
59
- def find_by_sql_with_kasket_on_id_array(keys)
66
+ def find_by_sql_with_kasket_on_id_array(keys, &blk)
60
67
  key_attributes_map = Kasket.cache.read_multi(*keys)
61
68
 
62
69
  found_keys, missing_keys = keys.partition {|k| key_attributes_map[k] }
63
- found_keys.each {|k| key_attributes_map[k] = instantiate(key_attributes_map[k].dup) }
70
+ # Only report a cache hit if at least some keys were found in the cache.
71
+ Events.report("cache_hit", self) if found_keys.any?
72
+
73
+ found_keys.each {|k| key_attributes_map[k] = instantiate(key_attributes_map[k].dup, &blk) }
64
74
  key_attributes_map.merge!(missing_records_from_db(missing_keys))
65
75
 
66
76
  key_attributes_map.values.compact
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Kasket
3
- VERSION = '4.12.0'
3
+ VERSION = '4.14.0'
4
4
  class Version
5
5
  MAJOR = Kasket::VERSION.split('.')[0]
6
6
  MINOR = Kasket::VERSION.split('.')[1]
data/lib/kasket.rb CHANGED
@@ -14,21 +14,35 @@ module Kasket
14
14
  autoload :Visitor, 'kasket/visitor'
15
15
  autoload :SelectManagerMixin, 'kasket/select_manager_mixin'
16
16
  autoload :RelationMixin, 'kasket/relation_mixin'
17
+ autoload :Events, 'kasket/events'
17
18
 
18
19
  CONFIGURATION = { # rubocop:disable Style/MutableConstant
19
20
  max_collection_size: 100,
20
21
  write_through: false,
21
- default_expires_in: nil
22
+ default_expires_in: nil,
23
+ events_callback: nil,
22
24
  }
23
25
 
24
26
  module_function
25
27
 
28
+ # Configure Kasket.
29
+ #
30
+ # @param [Hash] options the configuration options for Kasket.
31
+ # @option options [Integer] :max_collection_size max size limit for a cacheable
32
+ # collection of records.
33
+ # @option options [Boolean] :write_through
34
+ # @option options [Integer, nil] :default_expires_in the cache TTL.
35
+ # @option options [#call] :events_callback a callable object used to instrument
36
+ # Kasket operations. It is invoked with two arguments: the name of the event,
37
+ # as a String, and the Klass of the ActiveRecord model the event is about.
38
+ #
26
39
  def setup(options = {})
27
40
  return if ActiveRecord::Base.respond_to?(:has_kasket)
28
41
 
29
42
  CONFIGURATION[:max_collection_size] = options[:max_collection_size] if options[:max_collection_size]
30
43
  CONFIGURATION[:write_through] = options[:write_through] if options[:write_through]
31
44
  CONFIGURATION[:default_expires_in] = options[:default_expires_in] if options[:default_expires_in]
45
+ CONFIGURATION[:events_callback] = options[:events_callback] if options[:events_callback]
32
46
 
33
47
  ActiveRecord::Base.extend(Kasket::ConfigurationMixin)
34
48
  ActiveRecord::Relation.include(Kasket::RelationMixin)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kasket
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.12.0
4
+ version: 4.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mick Staugaard
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-03-24 00:00:00.000000000 Z
12
+ date: 2024-01-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -42,6 +42,7 @@ files:
42
42
  - lib/kasket.rb
43
43
  - lib/kasket/configuration_mixin.rb
44
44
  - lib/kasket/dirty_mixin.rb
45
+ - lib/kasket/events.rb
45
46
  - lib/kasket/query_parser.rb
46
47
  - lib/kasket/read_mixin.rb
47
48
  - lib/kasket/relation_mixin.rb
@@ -68,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
69
  - !ruby/object:Gem::Version
69
70
  version: '0'
70
71
  requirements: []
71
- rubygems_version: 3.0.3.1
72
+ rubygems_version: 3.5.3
72
73
  signing_key:
73
74
  specification_version: 4
74
75
  summary: A write back caching layer on active record