Class: Faulty::Storage::Redis

Inherits:
Object
  • Object
show all
Defined in:
lib/faulty/storage/redis.rb

Overview

A storage backend for storing circuit state in Redis.

When using this or any networked backend, be sure to evaluate the risk, and set conservative timeouts so that the circuit storage does not cause cascading failures in your application when evaluating circuits. Always wrap this backend with a FaultTolerantProxy to limit the effect of these types of events.

Defined Under Namespace

Classes: Options

Constant Summary collapse

ENTRY_SEPARATOR =

Separates the time/status for history entry strings

':'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**options) {|Options| ... } ⇒ Redis

Returns a new instance of Redis.

Parameters:

  • options (Hash)

    Attributes for Options

Yields:

  • (Options)

    For setting options in a block



85
86
87
88
89
# File 'lib/faulty/storage/redis.rb', line 85

def initialize(**options, &block)
  @options = Options.new(options, &block)

  check_client_options!
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



16
17
18
# File 'lib/faulty/storage/redis.rb', line 16

def options
  @options
end

Instance Method Details

#close(circuit) ⇒ Boolean

Mark a circuit as closed

Returns:

  • (Boolean)

    True if the circuit transitioned from open to closed

See Also:



138
139
140
141
142
143
144
# File 'lib/faulty/storage/redis.rb', line 138

def close(circuit)
  redis do |r|
    closed = compare_and_set(r, state_key(circuit), ['open'], 'closed', ex: options.circuit_ttl)
    r.del(entries_key(circuit)) if closed
    closed
  end
end

#entry(circuit, time, success) ⇒ Array<Array>

Add an entry to storage

Parameters:

  • circuit (Circuit)

    The circuit that ran

  • time (Integer)

    The unix timestamp for the run

  • success (Boolean)

    True if the run succeeded

Returns:

  • (Array<Array>)

    An array of the new history tuples after adding the new entry, see #history

See Also:



96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/faulty/storage/redis.rb', line 96

def entry(circuit, time, success)
  key = entries_key(circuit)
  result = pipe do |r|
    r.sadd(list_key, circuit.name)
    r.expire(list_key, options.circuit_ttl + options.list_granularity) if options.circuit_ttl
    r.lpush(key, "#{time}#{ENTRY_SEPARATOR}#{success ? 1 : 0}")
    r.ltrim(key, 0, options.max_sample_size - 1)
    r.expire(key, options.sample_ttl) if options.sample_ttl
    r.lrange(key, 0, -1)
  end
  map_entries(result.last)
end

#fault_tolerant?true

Redis storage is not fault-tolerant

Returns:

  • (true)


225
226
227
# File 'lib/faulty/storage/redis.rb', line 225

def fault_tolerant?
  false
end

#history(circuit) ⇒ Array<Array>

Get the circuit history up to max_sample_size

Parameters:

  • circuit (Circuit)

    The circuit to get history for

Returns:

  • (Array<Array>)

    An array of history tuples

See Also:



210
211
212
213
# File 'lib/faulty/storage/redis.rb', line 210

def history(circuit)
  entries = redis { |r| r.lrange(entries_key(circuit), 0, -1) }
  map_entries(entries).reverse
end

#listArray<String>

List all unexpired circuits

Returns:

  • (Array<String>)


218
219
220
# File 'lib/faulty/storage/redis.rb', line 218

def list
  redis { |r| r.sunion(*all_list_keys) }
end

#lock(circuit, state) ⇒ void

This method returns an undefined value.

Lock a circuit open or closed

The circuit_ttl does not apply to locks

Parameters:

  • circuit (Circuit)

    The circuit to lock

  • state (:open, :closed)

    The state to lock the circuit in

See Also:



153
154
155
# File 'lib/faulty/storage/redis.rb', line 153

def lock(circuit, state)
  redis { |r| r.set(lock_key(circuit), state) }
end

#open(circuit, opened_at) ⇒ Boolean

Mark a circuit as open

Parameters:

  • circuit (Circuit)

    The circuit to open

  • opened_at (Integer)

    The timestmp the circuit was opened at

Returns:

  • (Boolean)

    True if the circuit transitioned from closed to open

See Also:



114
115
116
117
118
119
120
# File 'lib/faulty/storage/redis.rb', line 114

def open(circuit, opened_at)
  redis do |r|
    opened = compare_and_set(r, state_key(circuit), ['closed', nil], 'open', ex: options.circuit_ttl)
    r.set(opened_at_key(circuit), opened_at, ex: options.circuit_ttl) if opened
    opened
  end
end

#reopen(circuit, opened_at, previous_opened_at) ⇒ Boolean

Mark a circuit as reopened

Parameters:

  • circuit (Circuit)

    The circuit to reopen

  • opened_at (Integer)

    The timestmp the circuit was opened at

  • previous_opened_at (Integer)

    The last known value of opened_at. Can be used to comare-and-set.

Returns:

  • (Boolean)

    True if the opened_at time was updated

See Also:



127
128
129
130
131
# File 'lib/faulty/storage/redis.rb', line 127

def reopen(circuit, opened_at, previous_opened_at)
  redis do |r|
    compare_and_set(r, opened_at_key(circuit), [previous_opened_at.to_s], opened_at, ex: options.circuit_ttl)
  end
end

#reset(circuit) ⇒ void

This method returns an undefined value.

Reset a circuit

Parameters:

  • circuit (Circuit)

    The circuit to unlock

See Also:



171
172
173
174
175
176
177
178
179
180
# File 'lib/faulty/storage/redis.rb', line 171

def reset(circuit)
  pipe do |r|
    r.del(
      entries_key(circuit),
      opened_at_key(circuit),
      lock_key(circuit)
    )
    r.set(state_key(circuit), 'closed', ex: options.circuit_ttl)
  end
end

#status(circuit) ⇒ Status

Get the status of a circuit

Parameters:

  • circuit (Circuit)

    The circuit to get status for

Returns:

  • (Status)

    The current status

See Also:



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/faulty/storage/redis.rb', line 187

def status(circuit)
  futures = {}
  pipe do |r|
    futures[:state] = r.get(state_key(circuit))
    futures[:lock] = r.get(lock_key(circuit))
    futures[:opened_at] = r.get(opened_at_key(circuit))
    futures[:entries] = r.lrange(entries_key(circuit), 0, -1)
  end

  Faulty::Status.from_entries(
    map_entries(futures[:entries].value),
    state: futures[:state].value&.to_sym || :closed,
    lock: futures[:lock].value&.to_sym,
    opened_at: futures[:opened_at].value ? futures[:opened_at].value.to_i : nil,
    options: circuit.options
  )
end

#unlock(circuit) ⇒ void

This method returns an undefined value.

Unlock a circuit

Parameters:

  • circuit (Circuit)

    The circuit to unlock

See Also:



162
163
164
# File 'lib/faulty/storage/redis.rb', line 162

def unlock(circuit)
  redis { |r| r.del(lock_key(circuit)) }
end