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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1dc6c8a3ec9341137a0873d4c4beb2603f0bf64fadb23f5a0fc3712852a6b216
4
- data.tar.gz: d3b7b20bd41accc9cbae4618eb1c05237c8968cf7d841c02dafe47c90b763451
3
+ metadata.gz: 61679cceaaaf7075f3481d5b2ff7cdb9fd978414ffb859622c8a381897654f41
4
+ data.tar.gz: bfe5ec32fe1d16861b448fd48f51517fb1a131bead3ef6fb7c046ffc526753eb
5
5
  SHA512:
6
- metadata.gz: e3c0cbb11b456521b80c803af725411614192b9eb64aa729fb651faa40d7037efd07456502e00e54b5126a5f7df2ea42877a4333246cadeda58bae0dcdeff08a
7
- data.tar.gz: 5386790fa9e513fb28508a0cc4ddbc6ce809325d5edca06c00a88076e586dbc132429fd302867e2b7e10d1084aa62cce02b54d898f04bfb8bbc0f44f7341a8b0
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
@@ -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
- _core_store = cache_stores.find do |cs|
26
- options(cs[:options])
27
- Logger.new("log/#{CONFIG[:RAILS_ENV]}.log").info("CacheFailover: caching_up?: #{cs[:store].class.name}")
28
- Logger.new("log/#{CONFIG[:RAILS_ENV]}.log").info("CacheFailover: caching_up?: #{options}")
29
- Logger.new("log/#{CONFIG[:RAILS_ENV]}.log").info("#{caching_up?(cs[:store], options)}")
30
- caching_up?(cs[:store], options)
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
- @core_store = _core_store[:store]
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
- @core_store.fetch(expanded_cache_key(name), options.merge(compress: false)) do
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
- options(init_options)
87
+ retries = 0
88
+ begin
89
+ options(init_options)
61
90
 
62
- payload = store_value(value, options)
91
+ payload = store_value(value, options)
63
92
 
64
- @core_store.write(
65
- expanded_cache_key(name),
66
- payload,
67
- options.merge(compress: false)
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
- options(init_options)
106
+ retries = 0
107
+ begin
108
+ options(init_options)
73
109
 
74
- payload = @core_store.read(
75
- expanded_cache_key(name),
76
- options
77
- )
110
+ payload = core_store.read(
111
+ expanded_cache_key(name),
112
+ options
113
+ )
78
114
 
79
- get_value(payload, options)
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
- options(init_options)
124
+ retries = 0
125
+ begin
126
+ options(init_options)
84
127
 
85
- new_hash = hash.map do |key, val|
86
- [
87
- expanded_cache_key(key),
88
- store_value(val, options),
89
- ]
90
- end
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
- @core_store.write_multi(
93
- new_hash,
94
- options.merge(compress: false)
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
- options = names.extract_options!
100
- names = names.map { |name| expanded_cache_key(name) }
101
- options(options)
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
- core_store.read_multi(*names, options).map do |key, val|
104
- [key, get_value(val, options)]
105
- end.to_h
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
- options = names.extract_options!
110
- expanded_names = names.map { |name| expanded_cache_key(name) }
111
- options(options)
112
-
113
- reads = core_store.send(:read_multi_entries, expanded_names, **options)
114
- reads.map do |key, val|
115
- [key, store_value(val, options)]
116
- end.to_h
117
-
118
- writes = {}
119
- ordered = names.index_with do |name|
120
- reads.fetch(name) { writes[name] = yield(name) }
121
- end
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
- write_multi(writes)
124
- ordered
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
- @core_store.exist?(expanded_cache_key(name), init_options)
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
- @core_store.delete(expanded_cache_key(name), init_options)
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
- @core_store.clear(**init_options)
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
- @core_store.increment(expanded_cache_key(name), amount, **init_options)
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
- @core_store.decrement(expanded_cache_key(name), amount, **init_options)
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
- (redis_cnxn(init_options).call('ping') == 'PONG' rescue false)
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CacheFailover
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
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.1.1
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-27 00:00:00.000000000 Z
11
+ date: 2024-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport