Class: ActiveSupport::Cache::RedisStore

Inherits:
Store
  • Object
show all
Defined in:
lib/active_support/cache/redis_store.rb

Constant Summary collapse

ERRORS_TO_RESCUE =
[
  Errno::ECONNREFUSED,
  Errno::EHOSTUNREACH,
  # This is what rails' redis cache store rescues
  # https://github.com/rails/rails/blob/5-2-stable/activesupport/lib/active_support/cache/redis_cache_store.rb#L447
  Redis::BaseConnectionError
].freeze
DEFAULT_ERROR_HANDLER =
-> (method: nil, returning: nil, exception: nil) do
  if logger
    logger.error { "RedisStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
  end
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*addresses) ⇒ RedisStore

Instantiate the store.

Example:

RedisStore.new
  # => host: localhost,   port: 6379,  db: 0

RedisStore.new client: Redis.new(url: "redis://127.0.0.1:6380/1")
  # => host: localhost,   port: 6379,  db: 0

RedisStore.new "redis://example.com"
  # => host: example.com, port: 6379,  db: 0

RedisStore.new "redis://example.com:23682"
  # => host: example.com, port: 23682, db: 0

RedisStore.new "redis://example.com:23682/1"
  # => host: example.com, port: 23682, db: 1

RedisStore.new "redis://example.com:23682/1/theplaylist"
  # => host: example.com, port: 23682, db: 1, namespace: theplaylist

RedisStore.new "redis://localhost:6379/0", "redis://localhost:6380/0"
  # => instantiate a cluster

RedisStore.new "redis://localhost:6379/0", "redis://localhost:6380/0", pool_size: 5, pool_timeout: 10
  # => use a ConnectionPool

RedisStore.new "redis://localhost:6379/0", "redis://localhost:6380/0",
  pool: ::ConnectionPool.new(size: 1, timeout: 1) { ::Redis::Store::Factory.create("localhost:6379/0") })
  # => supply an existing connection pool (e.g. for use with redis-sentinel or redis-failover)


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/active_support/cache/redis_store.rb', line 54

def initialize(*addresses)
  @options = addresses.extract_options!
  addresses = addresses.compact.map(&:dup)

  @data = if @options[:pool]
            raise "pool must be an instance of ConnectionPool" unless @options[:pool].is_a?(ConnectionPool)
            @pooled = true
            @options[:pool]
          elsif [:pool_size, :pool_timeout].any? { |key| @options.has_key?(key) }
            pool_options           = {}
            pool_options[:size]    = options[:pool_size] if options[:pool_size]
            pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout]
            @pooled = true
            ::ConnectionPool.new(pool_options) { ::Redis::Store::Factory.create(*addresses, @options) }
          elsif @options[:client]
            @options[:client]
          else
            ::Redis::Store::Factory.create(*addresses, @options)
          end

  @error_handler = @options[:error_handler] || DEFAULT_ERROR_HANDLER

  super(@options)
end

Instance Attribute Details

#dataObject (readonly)

Returns the value of attribute data.



22
23
24
# File 'lib/active_support/cache/redis_store.rb', line 22

def data
  @data
end

Instance Method Details

#clearObject

Clear all the data from the store.



247
248
249
250
251
252
253
# File 'lib/active_support/cache/redis_store.rb', line 247

def clear
  instrument(:clear, nil, nil) do
    failsafe(:clear) do
      with(&:flushdb)
    end
  end
end

#decrement(name, amount = 1, options = {}) ⇒ Object

Decrement a key in the store

If the key doesn’t exist it will be initialized on 0. If the key exist but it isn’t a Fixnum it will be initialized on 0.

Example:

We have two objects in cache:
  counter # => 23
  rabbit  # => #<Rabbit:0x5eee6c>

cache.decrement "counter"
cache.read "counter", :raw => true      # => "22"

cache.decrement "counter", 2
cache.read "counter", :raw => true      # => "20"

cache.decrement "a counter"
cache.read "a counter", :raw => true    # => "-1"

cache.decrement "rabbit"
cache.read "rabbit", :raw => true       # => "-1"


226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/active_support/cache/redis_store.rb', line 226

def decrement(name, amount = 1, options = {})
  instrument :decrement, name, amount: amount do
    failsafe :decrement do
      options = merged_options(options)
      key = normalize_key(name, options)

      with do |c|
        c.decrby(key, amount).tap do
          write_key_expiry(c, key, options)
        end
      end
    end
  end
end

#delete_matched(matcher, options = nil) ⇒ Object

Delete objects for matched keys.

Performance note: this operation can be dangerous for large production databases, as it uses the Redis “KEYS” command, which is O(N) over the total number of keys in the database. Users of large Redis caches should avoid this method.

Example:

cache.delete_matched "rab*"


99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/active_support/cache/redis_store.rb', line 99

def delete_matched(matcher, options = nil)
  options = merged_options(options)
  instrument(:delete_matched, matcher.inspect) do
    failsafe(:read_multi, returning: false) do
      matcher = key_matcher(matcher, options)
      begin
        with do |store|
          !(keys = store.keys(matcher)).empty? && store.del(*keys)
        end
      end
    end
  end
end

#exist?(name, options = nil) ⇒ Boolean

Returns:

  • (Boolean)


257
258
259
260
# File 'lib/active_support/cache/redis_store.rb', line 257

def exist?(name, options = nil)
  res = super(name, options)
  res || false
end

#expire(key, ttl) ⇒ Object



241
242
243
244
# File 'lib/active_support/cache/redis_store.rb', line 241

def expire(key, ttl)
  options = merged_options(nil)
  with { |c| c.expire normalize_key(key, options), ttl }
end

#fetch_multi(*names) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/active_support/cache/redis_store.rb', line 139

def fetch_multi(*names)
  options = names.extract_options!
  return {} if names == []

  results = read_multi(*names, options)
  need_writes = {}

  fetched = names.inject({}) do |memo, name|
    memo[name] = results.fetch(name) do
      value = yield name
      need_writes[name] = value
      value
    end

    memo
  end

  failsafe(:fetch_multi_write) do
    with do |c|
      c.multi do
        need_writes.each do |name, value|
          write(name, value, options)
        end
      end
    end
  end

  fetched
end

#increment(name, amount = 1, options = {}) ⇒ Object

Increment a key in the store.

If the key doesn’t exist it will be initialized on 0. If the key exist but it isn’t a Fixnum it will be initialized on 0.

Example:

We have two objects in cache:
  counter # => 23
  rabbit  # => #<Rabbit:0x5eee6c>

cache.increment "counter"
cache.read "counter", :raw => true      # => "24"

cache.increment "counter", 6
cache.read "counter", :raw => true      # => "30"

cache.increment "a counter"
cache.read "a counter", :raw => true    # => "1"

cache.increment "rabbit"
cache.read "rabbit", :raw => true       # => "1"


190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/active_support/cache/redis_store.rb', line 190

def increment(name, amount = 1, options = {})
  instrument :increment, name, amount: amount do
    failsafe :increment do
      options = merged_options(options)
      key = normalize_key(name, options)

      with do |c|
        c.incrby(key, amount).tap do
          write_key_expiry(c, key, options)
        end
      end
    end
  end
end

#read_multi(*names) ⇒ Object

Reads multiple keys from the cache using a single call to the servers for all keys. Options can be passed in the last argument.

Example:

cache.read_multi "rabbit", "white-rabbit"
cache.read_multi "rabbit", "white-rabbit", :raw => true


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/active_support/cache/redis_store.rb', line 119

def read_multi(*names)
  options = names.extract_options!
  return {} if names == []

  keys = names.map{|name| normalize_key(name, options)}
  args = [keys, options]
  args.flatten!

  instrument(:read_multi, names) do |payload|
    failsafe(:read_multi, returning: {}) do
      values = with { |c| c.mget(*args) }
      values.map! { |v| v.is_a?(ActiveSupport::Cache::Entry) ? v.value : v }

      Hash[names.zip(values)].reject{|k,v| v.nil?}.tap do |result|
        payload[:hits] = result.keys if payload
      end
    end
  end
end

#reconnectObject



274
275
276
# File 'lib/active_support/cache/redis_store.rb', line 274

def reconnect
  @data.reconnect if @data.respond_to?(:reconnect)
end

#statsObject



262
263
264
# File 'lib/active_support/cache/redis_store.rb', line 262

def stats
  with(&:info)
end

#with(&block) ⇒ Object



266
267
268
269
270
271
272
# File 'lib/active_support/cache/redis_store.rb', line 266

def with(&block)
  if defined?(@pooled) && @pooled
    @data.with(&block)
  else
    block.call(@data)
  end
end

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



79
80
81
82
83
84
85
86
87
88
# File 'lib/active_support/cache/redis_store.rb', line 79

def write(name, value, options = nil)
  options = merged_options(options.to_h.symbolize_keys)
  instrument(:write, name, options) do |_payload|
    if options[:expires_in].present? && options[:race_condition_ttl].present? && options[:raw].blank?
      options[:expires_in] = options[:expires_in].to_f + options[:race_condition_ttl].to_f
    end
    entry = options[:raw].present? ? value : Entry.new(value, **options)
    write_entry(normalize_key(name, options), entry, options)
  end
end