composite_cache_store 0.0.1 → 0.0.3
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 +20 -12
- data/lib/composite_cache_store/version.rb +1 -1
- data/lib/composite_cache_store.rb +78 -52
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 689876c5fc7b2bd00343817eac73b522a0687482aaaeb1058a471c7fcac275e7
|
4
|
+
data.tar.gz: dba46b9f25d1302377dd0fb64c9765a8c6016545f78fdb57444282e6b4d94cd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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 "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
|
-
@
|
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
|
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
|
@@ -1,123 +1,149 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/
|
3
|
+
require "active_support/all"
|
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
|
+
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
|
-
|
109
|
-
inner.silence!
|
116
|
+
layers.each { |store| store.silence! }
|
110
117
|
end
|
111
118
|
|
112
119
|
def write(name, value, options = nil)
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
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.
|
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
|
+
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
|
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
|
121
|
+
summary: A composite cache store comprised of layered ActiveSupport::Cache::Store
|
122
|
+
instances
|
122
123
|
test_files: []
|