Class: CacheFailover::Store

Inherits:
ActiveSupport::Cache::Store
  • Object
show all
Defined in:
lib/cache_failover/store.rb

Defined Under Namespace

Classes: BrotliCompressor

Constant Summary collapse

MARK_BR_COMPRESSED =
"\x02".b
DEFAULT_OPTIONS =
{
  timeout: 5,
  compress: true,
  cache_db: 'cache'
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cache_stores) ⇒ Store

Returns a new instance of Store.



24
25
26
27
28
29
30
31
32
33
# File 'lib/cache_failover/store.rb', line 24

def initialize(cache_stores)
  _core_store = cache_stores.find do |cs|
    options(cs[:options])
    Logger.new("log/#{CONFIG[:RAILS_ENV]}.log").info("CacheFailover: caching_up?: #{cs[:store].class.name}")
    Logger.new("log/#{CONFIG[:RAILS_ENV]}.log").info("CacheFailover: caching_up?: #{options}")
    Logger.new("log/#{CONFIG[:RAILS_ENV]}.log").info("#{caching_up?(cs[:store], options)}")
    caching_up?(cs[:store], options)
  end
  @core_store = _core_store[:store]
end

Instance Attribute Details

#core_storeObject (readonly)

Returns the value of attribute core_store.



22
23
24
# File 'lib/cache_failover/store.rb', line 22

def core_store
  @core_store
end

Class Method Details

.supports_cache_versioning?Boolean

Returns:

  • (Boolean)


147
148
149
# File 'lib/cache_failover/store.rb', line 147

def self.supports_cache_versioning?
  true
end

Instance Method Details

#cache_db_cnxn(init_options) ⇒ Object



166
167
168
169
170
171
172
# File 'lib/cache_failover/store.rb', line 166

def cache_db_cnxn(init_options)
  @db_cache_client ||=
    ActiveRecord::Base.
      establish_connection(
        Rails.configuration.database_configuration[Rails.env.to_s][init_options[:cache_db]]
      )
end

#caching_up?(store, init_options) ⇒ Boolean

Returns:

  • (Boolean)


174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/cache_failover/store.rb', line 174

def caching_up?(store, init_options)
  begin
    Timeout.timeout((init_options[:timeout] || 1)) do
      case store.class.name
      when 'ActiveSupport::Cache::RedisCacheStore'
        (redis_cnxn(init_options).call('ping') == 'PONG' rescue false)
      when 'ActiveSupport::Cache::MemCacheStore'
      when 'SolidCache::Store'
        cache_db_cnxn(init_options).with_connection { ActiveRecord::Base.connection.select_value('SELECT 1=1') == 1 }
      when 'ActiveSupport::Cache::MemoryStore'
      when 'ActiveSupport::Cache::FileStore'
      when 'ActiveSupport::Cache::NullStore'
      end
    end
  rescue => ex
    false
  end
end

#clear(init_options = {}) ⇒ Object



135
136
137
# File 'lib/cache_failover/store.rb', line 135

def clear(init_options = {})
  @core_store.clear(**init_options)
end

#compressed(value) ⇒ Object



211
212
213
214
215
216
217
218
# File 'lib/cache_failover/store.rb', line 211

def compressed(value)
  begin
    BrotliCompressor.deflate(value)
  rescue Brotli::Error => ex
    Rails.logger.info("CacheFailover Error: BrotliCompressor.deflate: #{ex.message}")
    value
  end
end

#decrement(name, amount = 1, **init_options) ⇒ Object



143
144
145
# File 'lib/cache_failover/store.rb', line 143

def decrement(name, amount = 1, **init_options)
  @core_store.decrement(expanded_cache_key(name), amount, **init_options)
end

#delete(name, init_options = {}) ⇒ Object



131
132
133
# File 'lib/cache_failover/store.rb', line 131

def delete(name, init_options = {})
  @core_store.delete(expanded_cache_key(name), init_options)
end

#exist?(name, init_options = {}) ⇒ Boolean

Returns:

  • (Boolean)


127
128
129
# File 'lib/cache_failover/store.rb', line 127

def exist?(name, init_options = {})
  @core_store.exist?(expanded_cache_key(name), init_options)
end

#expanded_cache_key(name) ⇒ Object



253
254
255
# File 'lib/cache_failover/store.rb', line 253

def expanded_cache_key(name)
  "#{::ActiveSupport::Cache.expand_cache_key(name)}"
end

#fetch(name, init_options = nil, &block) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/cache_failover/store.rb', line 40

def fetch(name, init_options = nil, &block)
  options(init_options)

  if !block_given? && options[:force]
    raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
  end

  get_value(
    @core_store.fetch(expanded_cache_key(name), options.merge(compress: false)) do
      if block_given?
        store_value(block.call, options)
      else
        nil
      end
    end,
    options
  )
end

#fetch_multi(*names) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/cache_failover/store.rb', line 108

def fetch_multi(*names)
  options = names.extract_options!
  expanded_names = names.map { |name| expanded_cache_key(name) }
  options(options)

  reads = core_store.send(:read_multi_entries, expanded_names, **options)
  reads.map do |key, val|
    [key, store_value(val, options)]
  end.to_h

  writes = {}
  ordered = names.index_with do |name|
    reads.fetch(name) { writes[name] = yield(name) }
  end

  write_multi(writes)
  ordered
end

#get_value(payload, init_options) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/cache_failover/store.rb', line 240

def get_value(payload, init_options)
  return nil unless payload.present?
  return payload if payload.is_a?(Integer)
  payload =
    if payload.start_with?(MARK_BR_COMPRESSED)
      uncompressed(payload)
    else
      payload
    end
  payload = unserialized(payload)
  payload
end

#increment(name, amount = 1, **init_options) ⇒ Object



139
140
141
# File 'lib/cache_failover/store.rb', line 139

def increment(name, amount = 1, **init_options)
  @core_store.increment(expanded_cache_key(name), amount, **init_options)
end

#options(init_options = {}) ⇒ Object



35
36
37
38
# File 'lib/cache_failover/store.rb', line 35

def options(init_options = {})
  return @init_options if defined?(@init_options) && init_options.blank?
  @init_options = init_options.reverse_merge(DEFAULT_OPTIONS)
end

#read(name, init_options = nil) ⇒ Object



71
72
73
74
75
76
77
78
79
80
# File 'lib/cache_failover/store.rb', line 71

def read(name, init_options = nil)
  options(init_options)

  payload = @core_store.read(
    expanded_cache_key(name),
    options
  )

  get_value(payload, options)
end

#read_multi(*names) ⇒ Object



98
99
100
101
102
103
104
105
106
# File 'lib/cache_failover/store.rb', line 98

def read_multi(*names)
  options = names.extract_options!
  names = names.map { |name| expanded_cache_key(name) }
  options(options)

  core_store.read_multi(*names, options).map do |key, val|
    [key, get_value(val, options)]
  end.to_h
end

#redis_cnxn(init_options) ⇒ Object



155
156
157
158
159
160
161
162
163
164
# File 'lib/cache_failover/store.rb', line 155

def redis_cnxn(init_options)
  @redis_cache_client ||=
    RedisClient.config(
      url: CONFIG[:REDIS_URL],
      password: CONFIG[:REDIS_PASSWORD],
      driver: init_options[:adapter] || :hiredis,
      timeout: init_options[:timeout] || 1,
      inherit_socket: true,
      ).new_pool
end

#serialized(value) ⇒ Object



193
194
195
196
197
198
199
200
201
# File 'lib/cache_failover/store.rb', line 193

def serialized(value)
  mpval = (value.try(:to_msgpack) rescue nil)
  msval = (Marshal.dump(value) rescue nil)
  if mpval.present? && mpval.bytesize < msval.bytesize
    mpval
  else
    msval
  end
end

#store_value(value, init_options) ⇒ Object



229
230
231
232
233
234
235
236
237
238
# File 'lib/cache_failover/store.rb', line 229

def store_value(value, init_options)
  return value if value.is_a?(Integer)
  value = serialized(value)
  if init_options.blank? || init_options[:compress].blank? || !options[:compress] == false
    value = compressed(value)
    MARK_BR_COMPRESSED + value
  else
    value
  end
end

#uncompressed(payload) ⇒ Object



220
221
222
223
224
225
226
227
# File 'lib/cache_failover/store.rb', line 220

def uncompressed(payload)
  begin
    BrotliCompressor.inflate(payload.byteslice(1..-1))
  rescue Brotli::Error => ex
    Rails.logger.info("CacheFailover Error: BrotliCompressor.inflate: #{ex.message}")
    payload
  end
end

#unserialized(payload) ⇒ Object



203
204
205
206
207
208
209
# File 'lib/cache_failover/store.rb', line 203

def unserialized(payload)
  begin
    MessagePack.unpack(payload)
  rescue => ex
    Marshal.load(payload)
  end
end

#write(name, value, init_options = nil) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/cache_failover/store.rb', line 59

def write(name, value, init_options = nil)
  options(init_options)

  payload = store_value(value, options)

  @core_store.write(
    expanded_cache_key(name),
    payload,
    options.merge(compress: false)
  )
end

#write_multi(hash, init_options = nil) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/cache_failover/store.rb', line 82

def write_multi(hash, init_options = nil)
  options(init_options)

  new_hash = hash.map do |key, val|
    [
      expanded_cache_key(key),
      store_value(val, options),
    ]
  end

  @core_store.write_multi(
    new_hash,
    options.merge(compress: false)
  )
end