cache_failover 0.1.2 → 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: ed9e82f26ea1cb43e4b188cee7620bb345adba1f320144e3eee891c8b6eba03c
4
- data.tar.gz: 702f7c9c24287051704a9bc56c5a758db81d764cf5a3b26204ae3dc7b3ae529b
3
+ metadata.gz: 61679cceaaaf7075f3481d5b2ff7cdb9fd978414ffb859622c8a381897654f41
4
+ data.tar.gz: bfe5ec32fe1d16861b448fd48f51517fb1a131bead3ef6fb7c046ffc526753eb
5
5
  SHA512:
6
- metadata.gz: 84a2ebf66e5e039fb714bff24879fc52aa63d76aa80434dd69f85fdcb24fbfa4763e32996288f8cc55b95dde9ef1f09693f68acacd2c1f98fe0c3137bb4b6c8d
7
- data.tar.gz: bb2fadf0c46ecd0a4bcd28b7ffbc7535badc43aac63f23937007136a069256d508745d177113c82102a8da50f2d168cb4de1720b7595b356baa514a9c8de5ee1
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
@@ -49,5 +55,7 @@ config.cache_store = CacheFailover::Store.new(
49
55
  ## WIP
50
56
  - Memory Cache Support
51
57
  - File Cache support
58
+ - Sync cache stores
59
+ - Add option to not use cache stores after failure unless the application is rebooted.
52
60
  - More options
53
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,7 +284,7 @@ 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'
188
289
  dalli_cnxn(init_options).alive!
189
290
  dalli_cnxn(init_options).delete('cache_test')
@@ -197,6 +298,7 @@ module CacheFailover
197
298
  end
198
299
  end
199
300
  rescue => ex
301
+ Thread.current[:cache_core_store] = nil
200
302
  false
201
303
  end
202
304
  end
@@ -266,5 +368,7 @@ module CacheFailover
266
368
  end
267
369
 
268
370
  end
371
+
372
+
269
373
  end
270
374
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CacheFailover
4
- VERSION = "0.1.2"
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.2
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