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 +4 -4
- data/README.md +19 -11
- data/lib/composite_cache_store/version.rb +1 -1
- data/lib/composite_cache_store.rb +65 -49
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bcf1949baebddee06f0ba0ec038fed2c56885f10558cc8fa7a362d96dbd7b112
|
4
|
+
data.tar.gz: 435c6f4599e46a80235d8867d3d2812c61fbd012c425f74cce13cbd07df96dd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
31
|
-
When both caches are warm, a read hit on the local in-memory
|
32
|
-
|
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
|
-
@
|
72
|
-
|
73
|
-
|
74
|
-
expires_in: 15.minutes, # constrain entry lifetime so the local
|
75
|
-
size: 32.megabytes # constrain max memory used by the local
|
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:
|
89
|
-
#
|
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
|
@@ -4,120 +4,136 @@ require "active_support/cache"
|
|
4
4
|
require_relative "composite_cache_store/version"
|
5
5
|
|
6
6
|
class CompositeCacheStore
|
7
|
-
|
7
|
+
DEFAULT_LAYER_1_OPTIONS = {
|
8
8
|
expires_in: 5.minutes,
|
9
9
|
size: 16.megabytes
|
10
10
|
}
|
11
11
|
|
12
|
-
|
12
|
+
DEFAULT_LAYER_2_OPTIONS = {
|
13
13
|
expires_in: 1.day,
|
14
14
|
size: 32.megabytes
|
15
15
|
}
|
16
16
|
|
17
|
-
attr_reader :
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
29
|
-
|
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
|
-
|
32
|
-
@
|
31
|
+
layers.freeze
|
32
|
+
@layers = layers
|
33
33
|
end
|
34
34
|
|
35
35
|
def cleanup(...)
|
36
|
-
|
37
|
-
inner.cleanup(...)
|
36
|
+
layers.each { |store| store.cleanup(...) }
|
38
37
|
end
|
39
38
|
|
40
39
|
def clear(...)
|
41
|
-
|
42
|
-
inner.clear(...)
|
40
|
+
layers.each { |store| store.clear(...) }
|
43
41
|
end
|
44
42
|
|
45
43
|
def decrement(...)
|
46
|
-
|
47
|
-
inner.decrement(...)
|
44
|
+
layers.each { |store| store.decrement(...) }
|
48
45
|
end
|
49
46
|
|
50
47
|
def delete(...)
|
51
|
-
|
52
|
-
inner.delete(...)
|
48
|
+
layers.each { |store| store.delete(...) }
|
53
49
|
end
|
54
50
|
|
55
51
|
def delete_matched(...)
|
56
|
-
|
57
|
-
inner.delete_matched(...)
|
52
|
+
layers.each { |store| store.delete_matched(...) }
|
58
53
|
end
|
59
54
|
|
60
55
|
def delete_multi(...)
|
61
|
-
|
62
|
-
inner.delete_multi(...)
|
56
|
+
layers.each { |store| store.delete_multi(...) }
|
63
57
|
end
|
64
58
|
|
65
59
|
def exist?(...)
|
66
|
-
|
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
|
-
|
71
|
-
|
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
|
-
|
77
|
-
|
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
|
-
|
84
|
-
inner.increment(...)
|
83
|
+
layers.each { |store| store.increment(...) }
|
85
84
|
end
|
86
85
|
|
87
86
|
def mute
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
97
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
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
|
-
|
115
|
-
|
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
|
-
|
121
|
-
|
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.
|
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
|
+
date: 2023-03-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|