Class: Pecorino::CachedThrottle

Inherits:
Object
  • Object
show all
Defined in:
lib/pecorino/cached_throttle.rb

Overview

The cached throttles can be used when you want to lift your throttle blocks into a higher-level cache. If you are dealing with clients which are hammering on your throttles a lot, it is useful to have a process-local cache of the timestamp when the blocks that are set are going to expire. If you are running, say, 10 web app containers - and someone is hammering at an endpoint which starts blocking - you don't really need to query your DB for every request. The first request indicated as "blocked" by Pecorino can write a cache entry into a shared in-memory table, and all subsequent calls to the same process can reuse that blocked_until value to quickly refuse the request

Instance Method Summary collapse

Constructor Details

#initialize(cache_store, throttle) ⇒ CachedThrottle

Returns a new instance of CachedThrottle.

Parameters:

  • cache_store (ActiveSupport::Cache::Store)

    the store for the cached blocks. We recommend a MemoryStore per-process.

  • throttle (Pecorino::Throttle)

    the throttle to cache



13
14
15
16
# File 'lib/pecorino/cached_throttle.rb', line 13

def initialize(cache_store, throttle)
  @cache_store = cache_store
  @throttle = throttle
end

Instance Method Details

#able_to_accept?(n = 1) ⇒ Boolean

Returns false if there is a currently active block for that throttle in the cache. Otherwise forwards to underlying throttle.

Returns:

  • (Boolean)

See Also:



50
51
52
53
54
55
# File 'lib/pecorino/cached_throttle.rb', line 50

def able_to_accept?(n = 1)
  blocked_state = read_cached_blocked_state
  return false if blocked_state&.blocked?

  @throttle.able_to_accept?(n)
end

#keyObject

Returns the key of the throttle

See Also:



69
70
71
# File 'lib/pecorino/cached_throttle.rb', line 69

def key
  @throttle.key
end

#request(n = 1) ⇒ Object

Returns the cached state for the throttle if there is a currently active block for that throttle in the cache. Otherwise forwards to underlying throttle.

See Also:



38
39
40
41
42
43
44
45
# File 'lib/pecorino/cached_throttle.rb', line 38

def request(n = 1)
  blocked_state = read_cached_blocked_state
  return blocked_state if blocked_state&.blocked?

  @throttle.request(n).tap do |state|
    write_cache_blocked_state(state) if state.blocked_until
  end
end

#request!(n = 1) ⇒ Object

Increments the cached throttle by the given number of tokens. If there is currently a known cached block on that throttle an exception will be raised immediately instead of querying the actual throttle data. Otherwise the call gets forwarded to the underlying throttle.



23
24
25
26
27
28
29
30
31
32
33
# File 'lib/pecorino/cached_throttle.rb', line 23

def request!(n = 1)
  blocked_state = read_cached_blocked_state
  raise Pecorino::Throttle::Throttled.new(@throttle, blocked_state) if blocked_state&.blocked?

  begin
    @throttle.request!(n)
  rescue Pecorino::Throttle::Throttled => throttled_ex
    write_cache_blocked_state(throttled_ex.state) if throttled_ex.throttle == @throttle
    raise
  end
end

#stateObject

Returns false if there is a currently active block for that throttle in the cache. Otherwise forwards to underlying throttle.



76
77
78
79
80
81
82
83
84
# File 'lib/pecorino/cached_throttle.rb', line 76

def state
  blocked_state = read_cached_blocked_state
  warn "Read blocked state #{blocked_state.inspect}"
  return blocked_state if blocked_state&.blocked?

  @throttle.state.tap do |state|
    write_cache_blocked_state(state) if state.blocked?
  end
end

#throttled(&blk) ⇒ Object

Does not run the block if there is a currently active block for that throttle in the cache. Otherwise forwards to underlying throttle.

See Also:



60
61
62
63
64
# File 'lib/pecorino/cached_throttle.rb', line 60

def throttled(&blk)
  # We can't wrap the implementation of "throttled". Or - we can, but it will be obtuse.
  return if request(1).blocked?
  yield
end