composite_cache_store 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b6a9480fa18ff9ed0c17318f3ea093c6f8b586b4595427ff9b1056e7c0e9a5f
4
- data.tar.gz: 5e9af188c3726dbebec690aabcb838f426e9d26f9c96e48d291b3bb8083a28bc
3
+ metadata.gz: bcf1949baebddee06f0ba0ec038fed2c56885f10558cc8fa7a362d96dbd7b112
4
+ data.tar.gz: 435c6f4599e46a80235d8867d3d2812c61fbd012c425f74cce13cbd07df96dd8
5
5
  SHA512:
6
- metadata.gz: 636f9164938ce4dc6d5fbfc54d24e0e2e0870b8822c770b13bfdb293f02ac5fe4787bc0d9f883a398563f0e8210dcdfd7e89824298cc2dff20c03d4a9a98efea
7
- data.tar.gz: c4b79fefc6e70e02243ab8cfdf2c662de139e1e2b3062cd2f171d078cefb2869dae0c8a035d1269be5238015a020d378b77b9288cc42e630858eb91866641706
6
+ metadata.gz: b75956e16c3e2f1d6f2142f661e59b54e6b5ef3f9efa5c7630e7fd580d8f1e74354df78f0e4eab54c0573d1926ce47b6dae7ccd371cc2a54c1e052a76184b1e2
7
+ data.tar.gz: 1b756102d631603fead6e33fa37e7e8c2f58a60dc7b428d3c7f5d0e81913a6350b244aedef0386930a1ee37609fdccc034efb816fe29fc33b9ac3f6c8d85af34
data/README.md CHANGED
@@ -27,9 +27,12 @@ While these services are robust and performant, they can also be a source of lat
27
27
  __A composite (or layered) cache can mitigate these risks__
28
28
  by reducing traffic and backpressure on the persistence service.
29
29
 
30
- Consider a composite cache that wraps a remote Redis-backed store with an local in-memory store.
31
- When both caches are warm, a read hit on the local in-memory store will return instantly, avoiding the overhead
32
- of inter-process communication (IPC) and/or network traffic _(with its attendant data marshaling and socket/wire noise)._
30
+ Consider a composite cache that wraps a remote Redis-backed "outer cache" with a local in-memory "inner cache".
31
+ When both caches are warm, a read hit on the local in-memory cache returns instantly and avoids the overhead of
32
+ inter-process communication (IPC) and/or network traffic _(with its attendant data marshaling and socket/wire noise)_
33
+ associated with accessing the remote Redis-backed cache.
34
+
35
+ To summarize: __Reads prioritize the inner cache and fall back to the outer cache.__
33
36
 
34
37
  ## Sponsors
35
38
 
@@ -68,12 +71,17 @@ end
68
71
  ```ruby
69
72
  # config/initializers/composite_cache_store.rb
70
73
  def Rails.composite_cache
71
- @store ||= CompositeCacheStore.new(
72
- inner_cache_store: Rails.cache, # use whatever makes sense for your app as the remote inner-cache
73
- outer_cache_store: ActiveSupport::Cache::MemoryStore.new( # employs an LRU eviction policy
74
- expires_in: 15.minutes, # constrain entry lifetime so the local outer-cache doesn't drift out of sync
75
- size: 32.megabytes # constrain max memory used by the local outer-cache
76
- )
74
+ @composite_cache ||= CompositeCacheStore.new(
75
+ # Layer 1 cache (inner) - employs an LRU eviction policy
76
+ ActiveSupport::Cache::MemoryStore.new(
77
+ expires_in: 15.minutes, # constrain entry lifetime so the local cache doesn't drift out of sync
78
+ size: 32.megabytes # constrain max memory used by the local cache
79
+ ),
80
+
81
+ # Layer 2 cache (outer)
82
+ Rails.cache, # use whatever makes sense for your app as the remote inner-cache
83
+
84
+ # additional layers are optional
77
85
  )
78
86
  end
79
87
  ```
@@ -85,8 +93,8 @@ A composite cache is ideal for mitigating hot spot latency in frequently invoked
85
93
  ```ruby
86
94
  # method that's invoked frequently by multiple processes
87
95
  def hotspot
88
- # NOTE: the expires_in option is only applied to the remote inner-cache
89
- # the local outer-cache uses its globally configured expiration policy
96
+ # NOTE: expiration options are only applied to the outermost cache
97
+ # inner caches use their globally configured expiration policy
90
98
  Rails.composite_cache.fetch("example/slow/operation", expires_in: 12.hours) do
91
99
  # a slow operation
92
100
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class CompositeCacheStore
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
@@ -4,120 +4,136 @@ require "active_support/cache"
4
4
  require_relative "composite_cache_store/version"
5
5
 
6
6
  class CompositeCacheStore
7
- DEFAULT_OUTER_OPTIONS = {
7
+ DEFAULT_LAYER_1_OPTIONS = {
8
8
  expires_in: 5.minutes,
9
9
  size: 16.megabytes
10
10
  }
11
11
 
12
- DEFAULT_INNER_OPTIONS = {
12
+ DEFAULT_LAYER_2_OPTIONS = {
13
13
  expires_in: 1.day,
14
14
  size: 32.megabytes
15
15
  }
16
16
 
17
- attr_reader :outer_cache_store, :inner_cache_store
18
-
19
- alias_method :outer, :outer_cache_store
20
- alias_method :inner, :inner_cache_store
17
+ attr_reader :layers
21
18
 
22
19
  # Returns a new CompositeCacheStore instance
23
- # - inner_cache_store: An ActiveSupport::Cache::Store instance to use for the inner cache store (typically remote)
24
- # - outer_cache_store: An ActiveSupport::Cache::Store instance to use for the outer cache store (typically local)
25
- def initialize(options = {})
26
- options ||= {}
20
+ def initialize(*layers)
21
+ if layers.blank?
22
+ layers << ActiveSupport::Cache::MemoryStore.new(DEFAULT_LAYER_1_OPTIONS)
23
+ layers << ActiveSupport::Cache::MemoryStore.new(DEFAULT_LAYER_2_OPTIONS)
24
+ end
27
25
 
28
- @inner_cache_store = options[:inner_cache_store]
29
- @inner_cache_store = ActiveSupport::Cache::MemoryStore.new(DEFAULT_INNER_OPTIONS) unless inner.is_a?(ActiveSupport::Cache::Store)
26
+ message = "All layers must be instances of ActiveSupport::Cache::Store"
27
+ layers.each do |layer|
28
+ raise ArgumentError.new(message) unless layer.is_a?(ActiveSupport::Cache::Store)
29
+ end
30
30
 
31
- @outer_cache_store = options[:outer_cache_store]
32
- @outer_cache_store = ActiveSupport::Cache::MemoryStore.new(DEFAULT_OUTER_OPTIONS) unless outer.is_a?(ActiveSupport::Cache::Store)
31
+ layers.freeze
32
+ @layers = layers
33
33
  end
34
34
 
35
35
  def cleanup(...)
36
- outer.cleanup(...)
37
- inner.cleanup(...)
36
+ layers.each { |store| store.cleanup(...) }
38
37
  end
39
38
 
40
39
  def clear(...)
41
- outer.clear(...)
42
- inner.clear(...)
40
+ layers.each { |store| store.clear(...) }
43
41
  end
44
42
 
45
43
  def decrement(...)
46
- outer.decrement(...)
47
- inner.decrement(...)
44
+ layers.each { |store| store.decrement(...) }
48
45
  end
49
46
 
50
47
  def delete(...)
51
- outer.delete(...)
52
- inner.delete(...)
48
+ layers.each { |store| store.delete(...) }
53
49
  end
54
50
 
55
51
  def delete_matched(...)
56
- outer.delete_matched(...)
57
- inner.delete_matched(...)
52
+ layers.each { |store| store.delete_matched(...) }
58
53
  end
59
54
 
60
55
  def delete_multi(...)
61
- outer.delete_multi(...)
62
- inner.delete_multi(...)
56
+ layers.each { |store| store.delete_multi(...) }
63
57
  end
64
58
 
65
59
  def exist?(...)
66
- outer.exist?(...) || inner.exist?(...)
60
+ layers.each do |store|
61
+ return true if store.exist?(...)
62
+ end
63
+ false
67
64
  end
68
65
 
69
66
  def fetch(*args, &block)
70
- outer.fetch(*args) do
71
- inner.fetch(*args, &block)
67
+ f = ->(store) do
68
+ return store.fetch(*args, &block) if store == layers.last
69
+ store.fetch(*args) { f.call(layers[layers.index(store) + 1]) }
72
70
  end
71
+ f.call(layers.first)
73
72
  end
74
73
 
75
74
  def fetch_multi(*args, &block)
76
- outer.fetch_multi(*args) do
77
- inner.fetch_multi(*args, &block)
75
+ fm = ->(store) do
76
+ return store.fetch_multi(*args, &block) if store == layers.last
77
+ store.fetch_multi(*args) { fm.call(layers[layers.index(store) + 1]) }
78
78
  end
79
+ fm.call(layers.first)
79
80
  end
80
81
 
81
- # write
82
82
  def increment(...)
83
- outer.increment(...)
84
- inner.increment(...)
83
+ layers.each { |store| store.increment(...) }
85
84
  end
86
85
 
87
86
  def mute
88
- outer.mute do
89
- inner.mute do
90
- yield
91
- end
87
+ m = ->(store) do
88
+ return store.mute { yield } if store == layers.last
89
+ store.mute { m.call(layers[layers.index(store) + 1]) }
92
90
  end
91
+ m.call(layers.first)
93
92
  end
94
93
 
95
94
  def read(*args)
96
- outer.fetch(*args) do
97
- inner.read(*args)
95
+ r = ->(store) do
96
+ return store.read(*args) if store == layers.last
97
+ store.fetch(*args) { r.call(layers[layers.index(store) + 1]) }
98
98
  end
99
+ r.call(layers.first)
99
100
  end
100
101
 
101
102
  def read_multi(...)
102
- result = outer.read_multi(...)
103
- result = inner.read_multi(...) if result.blank?
104
- result
103
+ layers.each do |store|
104
+ result = store.read_multi(...)
105
+ return result if result.present?
106
+ end
107
+ nil
105
108
  end
106
109
 
107
110
  def silence!
108
- outer.silence!
109
- inner.silence!
111
+ layers.each { |store| store.silence! }
110
112
  end
111
113
 
114
+ # Only applies expiration options to the outermost cache
115
+ # Inner caches use their global expiration options
112
116
  def write(name, value, options = nil)
113
117
  options ||= {}
114
- outer.write(name, value, options.except(:expires_in)) # ? accept expires_in if less than outer.config[:expires_in] ?
115
- inner.write(name, value, options)
118
+ layers.each do |store|
119
+ if store == layers.last
120
+ store.write(name, value, options)
121
+ else
122
+ store.write(name, value, options.except(:expires_in, :expires_at))
123
+ end
124
+ end
116
125
  end
117
126
 
127
+ # Only applies expiration options to the outermost cache
128
+ # Inner caches use their global expiration options
118
129
  def write_multi(hash, options = nil)
119
130
  options ||= {}
120
- outer.write_multi(hash, options.except(:expires_in)) # ? accept expires_in if less than outer.config[:expires_in] ?
121
- inner.write_multi(hash, options)
131
+ layers.each do |store|
132
+ if store == layers.last
133
+ store.write_multi(hash, options)
134
+ else
135
+ store.write_multi(hash, options.except(:expires_in, :expires_at))
136
+ end
137
+ end
122
138
  end
123
139
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: composite_cache_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Hopkins (hopsoft)
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-11 00:00:00.000000000 Z
11
+ date: 2023-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport