cache_failover 0.1.1 → 0.2.0
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 +12 -1
- data/lib/cache_failover/store.rb +169 -61
- data/lib/cache_failover/version.rb +1 -1
- 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: 61679cceaaaf7075f3481d5b2ff7cdb9fd978414ffb859622c8a381897654f41
|
4
|
+
data.tar.gz: bfe5ec32fe1d16861b448fd48f51517fb1a131bead3ef6fb7c046ffc526753eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e41b6fb49aa9877f0379e719e2ec199f9b89ff0368c2ef85865716365f71e6c7a93e963e981e2addde5596068c2e40c39f142469905d5a84de1574a9cb6cca75
|
7
|
+
data.tar.gz: 1718e7588f2cce108b46a23dc02e5354b11ad50721b2ff05a7f422661311b433925c9442877fc61e99690056051b2844b3e1f01d5cd2b31d6c17a4fc478ecd9f
|
data/README.md
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
## Installation
|
4
4
|
|
5
|
+
This Gem allows you to configure multiple cache stores in a failover configuration. If the first cache store fails, this gem will attempt to use the next cache store in the list. If the first cache store comes back online, it will revert to using that store.
|
6
|
+
|
7
|
+
This is useful for high availability and redundancy, such as using a Redis Cache with SolidCache (DB) as a backup in case Redis goes down.
|
8
|
+
|
9
|
+
Keep in mind, if you use your cache as a session store, users will be logged out.
|
10
|
+
|
5
11
|
`Gemfile`
|
6
12
|
|
7
13
|
```ruby
|
@@ -25,6 +31,10 @@ Configure your cache_store normally, but use `CacheFailover::Store` with one arg
|
|
25
31
|
```ruby
|
26
32
|
config.cache_store = CacheFailover::Store.new(
|
27
33
|
[
|
34
|
+
{
|
35
|
+
store: ActiveSupport::Cache::MemCacheStore.new(CONFIG[:MEMCACHED_SERVERS], {}),
|
36
|
+
options: {}
|
37
|
+
},
|
28
38
|
{
|
29
39
|
store: ActiveSupport::Cache::RedisCacheStore.new(
|
30
40
|
url: CONFIG[:REDIS_URL],
|
@@ -43,8 +53,9 @@ config.cache_store = CacheFailover::Store.new(
|
|
43
53
|
```
|
44
54
|
|
45
55
|
## WIP
|
46
|
-
- Dalli/Memcached support
|
47
56
|
- Memory Cache Support
|
48
57
|
- File Cache support
|
58
|
+
- Sync cache stores
|
59
|
+
- Add option to not use cache stores after failure unless the application is rebooted.
|
49
60
|
- More options
|
50
61
|
- Tests
|
data/lib/cache_failover/store.rb
CHANGED
@@ -19,17 +19,37 @@ module CacheFailover
|
|
19
19
|
cache_db: 'cache'
|
20
20
|
}
|
21
21
|
|
22
|
-
attr_reader :core_store
|
23
|
-
|
24
22
|
def initialize(cache_stores)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
23
|
+
core_stores(cache_stores)
|
24
|
+
core_store
|
25
|
+
end
|
26
|
+
|
27
|
+
def core_store
|
28
|
+
if defined?(Thread.current[:cache_core_store]) && Thread.current[:cache_core_store].present? && caching_up?(Thread.current[:cache_core_store], options)
|
29
|
+
return Thread.current[:cache_core_store]
|
30
|
+
else
|
31
|
+
Thread.current[:cache_core_store] = nil
|
32
|
+
core_stores.each do |cs|
|
33
|
+
next if defined?(Thread.current[:cache_core_store]) && Thread.current[:cache_core_store].present?
|
34
|
+
Thread.current[:cache_core_store] = cs[:store]
|
35
|
+
options(cs[:options])
|
36
|
+
cache_failover_logger.info("CacheFailover: caching_up?: #{cs[:store].class.name}")
|
37
|
+
cache_failover_logger.info("CacheFailover: caching_up?: #{options}")
|
38
|
+
_store_up = caching_up?(cs[:store], options)
|
39
|
+
cache_failover_logger.info("#{_store_up}")
|
40
|
+
if _store_up == true
|
41
|
+
break
|
42
|
+
else
|
43
|
+
Thread.current[:cache_core_store] = nil
|
44
|
+
next
|
45
|
+
end
|
46
|
+
end
|
31
47
|
end
|
32
|
-
|
48
|
+
end
|
49
|
+
|
50
|
+
def core_stores(core_stores = [])
|
51
|
+
return @core_stores if defined?(@core_stores) && core_stores.blank?
|
52
|
+
@core_stores = core_stores
|
33
53
|
end
|
34
54
|
|
35
55
|
def options(init_options = {})
|
@@ -38,6 +58,8 @@ module CacheFailover
|
|
38
58
|
end
|
39
59
|
|
40
60
|
def fetch(name, init_options = nil, &block)
|
61
|
+
retries = 0
|
62
|
+
begin
|
41
63
|
options(init_options)
|
42
64
|
|
43
65
|
if !block_given? && options[:force]
|
@@ -45,7 +67,7 @@ module CacheFailover
|
|
45
67
|
end
|
46
68
|
|
47
69
|
get_value(
|
48
|
-
|
70
|
+
core_store.fetch(expanded_cache_key(name), options.merge(compress: false)) do
|
49
71
|
if block_given?
|
50
72
|
store_value(block.call, options)
|
51
73
|
else
|
@@ -54,100 +76,179 @@ module CacheFailover
|
|
54
76
|
end,
|
55
77
|
options
|
56
78
|
)
|
79
|
+
rescue => ex
|
80
|
+
Thread.current[:cache_core_store] = nil
|
81
|
+
core_store
|
82
|
+
retry if (retries += 1) < core_stores.length
|
83
|
+
end
|
57
84
|
end
|
58
85
|
|
59
86
|
def write(name, value, init_options = nil)
|
60
|
-
|
87
|
+
retries = 0
|
88
|
+
begin
|
89
|
+
options(init_options)
|
61
90
|
|
62
|
-
|
91
|
+
payload = store_value(value, options)
|
63
92
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
93
|
+
core_store.write(
|
94
|
+
expanded_cache_key(name),
|
95
|
+
payload,
|
96
|
+
options.merge(compress: false)
|
97
|
+
)
|
98
|
+
rescue => ex
|
99
|
+
Thread.current[:cache_core_store] = nil
|
100
|
+
core_store
|
101
|
+
retry if (retries += 1) < core_stores.length
|
102
|
+
end
|
69
103
|
end
|
70
104
|
|
71
105
|
def read(name, init_options = nil)
|
72
|
-
|
106
|
+
retries = 0
|
107
|
+
begin
|
108
|
+
options(init_options)
|
73
109
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
110
|
+
payload = core_store.read(
|
111
|
+
expanded_cache_key(name),
|
112
|
+
options
|
113
|
+
)
|
78
114
|
|
79
|
-
|
115
|
+
get_value(payload, options)
|
116
|
+
rescue => ex
|
117
|
+
Thread.current[:cache_core_store] = nil
|
118
|
+
core_store
|
119
|
+
retry if (retries += 1) < core_stores.length
|
120
|
+
end
|
80
121
|
end
|
81
122
|
|
82
123
|
def write_multi(hash, init_options = nil)
|
83
|
-
|
124
|
+
retries = 0
|
125
|
+
begin
|
126
|
+
options(init_options)
|
84
127
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
128
|
+
new_hash = hash.map do |key, val|
|
129
|
+
[
|
130
|
+
expanded_cache_key(key),
|
131
|
+
store_value(val, options),
|
132
|
+
]
|
133
|
+
end
|
91
134
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
135
|
+
core_store.write_multi(
|
136
|
+
new_hash,
|
137
|
+
options.merge(compress: false)
|
138
|
+
)
|
139
|
+
rescue => ex
|
140
|
+
Thread.current[:cache_core_store] = nil
|
141
|
+
core_store
|
142
|
+
retry if (retries += 1) < core_stores.length
|
143
|
+
end
|
96
144
|
end
|
97
145
|
|
98
146
|
def read_multi(*names)
|
99
|
-
|
100
|
-
|
101
|
-
|
147
|
+
retries = 0
|
148
|
+
begin
|
149
|
+
options = names.extract_options!
|
150
|
+
names = names.map { |name| expanded_cache_key(name) }
|
151
|
+
options(options)
|
102
152
|
|
103
|
-
|
104
|
-
|
105
|
-
|
153
|
+
core_store.read_multi(*names, options).map do |key, val|
|
154
|
+
[key, get_value(val, options)]
|
155
|
+
end.to_h
|
156
|
+
rescue => ex
|
157
|
+
Thread.current[:cache_core_store] = nil
|
158
|
+
core_store
|
159
|
+
retry if (retries += 1) < core_stores.length
|
160
|
+
end
|
106
161
|
end
|
107
162
|
|
108
163
|
def fetch_multi(*names)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
164
|
+
retries = 0
|
165
|
+
begin
|
166
|
+
options = names.extract_options!
|
167
|
+
expanded_names = names.map { |name| expanded_cache_key(name) }
|
168
|
+
options(options)
|
169
|
+
|
170
|
+
reads = core_store.send(:read_multi_entries, expanded_names, **options)
|
171
|
+
reads.map do |key, val|
|
172
|
+
[key, store_value(val, options)]
|
173
|
+
end.to_h
|
174
|
+
|
175
|
+
writes = {}
|
176
|
+
ordered = names.index_with do |name|
|
177
|
+
reads.fetch(name) { writes[name] = yield(name) }
|
178
|
+
end
|
122
179
|
|
123
|
-
|
124
|
-
|
180
|
+
write_multi(writes)
|
181
|
+
ordered
|
182
|
+
rescue => ex
|
183
|
+
Thread.current[:cache_core_store] = nil
|
184
|
+
core_store
|
185
|
+
retry if (retries += 1) < core_stores.length
|
186
|
+
end
|
125
187
|
end
|
126
188
|
|
127
189
|
def exist?(name, init_options = {})
|
128
|
-
|
190
|
+
retries = 0
|
191
|
+
begin
|
192
|
+
core_store.exist?(expanded_cache_key(name), init_options)
|
193
|
+
rescue => ex
|
194
|
+
Thread.current[:cache_core_store] = nil
|
195
|
+
core_store
|
196
|
+
retry if (retries += 1) < core_stores.length
|
197
|
+
end
|
129
198
|
end
|
130
199
|
|
131
200
|
def delete(name, init_options = {})
|
132
|
-
|
201
|
+
retries = 0
|
202
|
+
begin
|
203
|
+
core_store.delete(expanded_cache_key(name), init_options)
|
204
|
+
rescue => ex
|
205
|
+
Thread.current[:cache_core_store] = nil
|
206
|
+
core_store
|
207
|
+
retry if (retries += 1) < core_stores.length
|
208
|
+
end
|
133
209
|
end
|
134
210
|
|
135
211
|
def clear(init_options = {})
|
136
|
-
|
212
|
+
retries = 0
|
213
|
+
begin
|
214
|
+
core_store.clear(**init_options)
|
215
|
+
rescue => ex
|
216
|
+
Thread.current[:cache_core_store] = nil
|
217
|
+
core_store
|
218
|
+
retry if (retries += 1) < core_stores.length
|
219
|
+
end
|
137
220
|
end
|
138
221
|
|
139
222
|
def increment(name, amount = 1, **init_options)
|
140
|
-
|
223
|
+
retries = 0
|
224
|
+
begin
|
225
|
+
core_store.increment(expanded_cache_key(name), amount, **init_options)
|
226
|
+
rescue => ex
|
227
|
+
Thread.current[:cache_core_store] = nil
|
228
|
+
core_store
|
229
|
+
retry if (retries += 1) < core_stores.length
|
230
|
+
end
|
141
231
|
end
|
142
232
|
|
143
233
|
def decrement(name, amount = 1, **init_options)
|
144
|
-
|
234
|
+
retries = 0
|
235
|
+
begin
|
236
|
+
core_store.decrement(expanded_cache_key(name), amount, **init_options)
|
237
|
+
rescue => ex
|
238
|
+
Thread.current[:cache_core_store] = nil
|
239
|
+
core_store
|
240
|
+
retry if (retries += 1) < core_stores.length
|
241
|
+
end
|
145
242
|
end
|
146
243
|
|
147
244
|
def self.supports_cache_versioning?
|
148
245
|
true
|
149
246
|
end
|
150
247
|
|
248
|
+
def cache_failover_logger
|
249
|
+
@cache_failover_logger ||= Logger.new("log/#{CONFIG[:RAILS_ENV]}.log")
|
250
|
+
end
|
251
|
+
|
151
252
|
private
|
152
253
|
|
153
254
|
def redis_cnxn(init_options)
|
@@ -183,8 +284,12 @@ module CacheFailover
|
|
183
284
|
Timeout.timeout((init_options[:timeout] || 1)) do
|
184
285
|
case store.class.name
|
185
286
|
when 'ActiveSupport::Cache::RedisCacheStore'
|
186
|
-
|
287
|
+
redis_cnxn(init_options).call('ping') == 'PONG'
|
187
288
|
when 'ActiveSupport::Cache::MemCacheStore'
|
289
|
+
dalli_cnxn(init_options).alive!
|
290
|
+
dalli_cnxn(init_options).delete('cache_test')
|
291
|
+
dalli_cnxn(init_options).add('cache_test', 'success')
|
292
|
+
dalli_cnxn(init_options).get('cache_test') == 'success'
|
188
293
|
when 'SolidCache::Store'
|
189
294
|
cache_db_cnxn(init_options).with_connection { ActiveRecord::Base.connection.select_value('SELECT 1=1') == 1 }
|
190
295
|
when 'ActiveSupport::Cache::MemoryStore'
|
@@ -193,6 +298,7 @@ module CacheFailover
|
|
193
298
|
end
|
194
299
|
end
|
195
300
|
rescue => ex
|
301
|
+
Thread.current[:cache_core_store] = nil
|
196
302
|
false
|
197
303
|
end
|
198
304
|
end
|
@@ -262,5 +368,7 @@ module CacheFailover
|
|
262
368
|
end
|
263
369
|
|
264
370
|
end
|
371
|
+
|
372
|
+
|
265
373
|
end
|
266
374
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cache_failover
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- chronicaust
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|