composite_cache_store 0.0.1 → 0.0.3

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: 6b6a9480fa18ff9ed0c17318f3ea093c6f8b586b4595427ff9b1056e7c0e9a5f
4
- data.tar.gz: 5e9af188c3726dbebec690aabcb838f426e9d26f9c96e48d291b3bb8083a28bc
3
+ metadata.gz: 689876c5fc7b2bd00343817eac73b522a0687482aaaeb1058a471c7fcac275e7
4
+ data.tar.gz: dba46b9f25d1302377dd0fb64c9765a8c6016545f78fdb57444282e6b4d94cd8
5
5
  SHA512:
6
- metadata.gz: 636f9164938ce4dc6d5fbfc54d24e0e2e0870b8822c770b13bfdb293f02ac5fe4787bc0d9f883a398563f0e8210dcdfd7e89824298cc2dff20c03d4a9a98efea
7
- data.tar.gz: c4b79fefc6e70e02243ab8cfdf2c662de139e1e2b3062cd2f171d078cefb2869dae0c8a035d1269be5238015a020d378b77b9288cc42e630858eb91866641706
6
+ metadata.gz: a04ad705343eea18cdd44d399cbd2264b11fa7eb91a6be8c0d98960b8937510abd77a780a95c816217bf2752cf7fde885dbf423b04aaee11024280876e19f994
7
+ data.tar.gz: 72bcee27ef6647b444582c481dfa011fedbb587d540537d193a3f1042965bf16e7cb740ed059f6483b7926afdd99dd2b84fe80715665ea8908dcf2c3efc1ca0f
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # CompositeCacheStore
2
2
 
3
- ### A composite cache store comprised of 2 ActiveSupport::Cache::Store instances
3
+ ### A composite cache store comprised of layered ActiveSupport::Cache::Store instances
4
4
 
5
5
  <!-- Tocer[start]: Auto-generated, don't remove. -->
6
6
 
@@ -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 "layer 2 cache" with a local in-memory "layer 1 cache".
31
+ When both caches are warm, a read hit on the local in-memory "layer 1 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 "layer 2 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
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.3"
5
5
  end
@@ -1,123 +1,149 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/cache"
3
+ require "active_support/all"
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
+ missed_layers = []
104
+ layers.each do |store|
105
+ hash = store.read_multi(...)
106
+ if hash.present?
107
+ missed_layers.each { |s| s.write_multi(hash) }
108
+ return hash
109
+ end
110
+ missed_layers << store
111
+ end
112
+ {}
105
113
  end
106
114
 
107
115
  def silence!
108
- outer.silence!
109
- inner.silence!
116
+ layers.each { |store| store.silence! }
110
117
  end
111
118
 
112
119
  def write(name, value, options = nil)
113
- 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)
120
+ layers.each do |store|
121
+ store.write name, value, permitted_options(store, options)
122
+ end
116
123
  end
117
124
 
118
125
  def write_multi(hash, options = nil)
119
- 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)
126
+ layers.each do |store|
127
+ store.write_multi hash, permitted_options(store, options)
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ def permitted_options(store, options = {})
134
+ return options if options.blank?
135
+ return options if keep_expiration?(store, options)
136
+ options.except(:expires_in, :expires_at)
137
+ end
138
+
139
+ def keep_expiration?(store, options = {})
140
+ return true if store == layers.last
141
+ return true unless store.options[:expires_in]
142
+
143
+ expires_in = options[:expires_in]
144
+ expires_in ||= Time.current - options[:expires_at] if options[:expires_at]
145
+ return false unless expires_in
146
+
147
+ expires_in < store.options[:expires_in]
122
148
  end
123
149
  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.3
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-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -82,7 +82,7 @@ dependencies:
82
82
  version: '0'
83
83
  description: |
84
84
  Enhanced application performance with faster reads, data redundancy,
85
- and reduced backpressure on the inner cache store.
85
+ and reduced backpressure on the outer cache store.
86
86
  email:
87
87
  - natehop@gmail.com
88
88
  executables: []
@@ -118,5 +118,6 @@ requirements: []
118
118
  rubygems_version: 3.4.6
119
119
  signing_key:
120
120
  specification_version: 4
121
- summary: A composite cache store comprised of 2 ActiveSupport::Cache::Store instances
121
+ summary: A composite cache store comprised of layered ActiveSupport::Cache::Store
122
+ instances
122
123
  test_files: []