Class: Prorate::LeakyBucket

Inherits:
Object
  • Object
show all
Defined in:
lib/prorate/leaky_bucket.rb

Overview

This offers just the leaky bucket implementation with fill control, but without the timed lock. It does not raise any exceptions, it just tracks the state of a leaky bucket in Redis.

Important differences from the more full-featured Throttle class are:

  • No logging (as most meaningful code lives in Lua anyway)

  • No timed block - if you need to keep track of timed blocking it can be done externally

  • Leak rate is specified directly in tokens per second, instead of specifying the block period.

  • The bucket level is stored and returned as a Float which allows for finer-grained measurement, but more importantly - makes testing from the outside easier.

It does have a few downsides compared to the Throttle though

  • Bucket is only full momentarily. On subsequent calls some tokens will leak already, so you either need to do delta checks on the value or rely on putting the token into the bucket.

Defined Under Namespace

Classes: BucketState

Constant Summary collapse

LUA_SCRIPT_CODE =
File.read(File.join(__dir__, "leaky_bucket.lua"))
LUA_SCRIPT_HASH =
Digest::SHA1.hexdigest(LUA_SCRIPT_CODE)

Instance Method Summary collapse

Constructor Details

#initialize(redis_key_prefix:, leak_rate:, redis:, bucket_capacity:) ⇒ LeakyBucket

Creates a new LeakyBucket. The object controls 2 keys in Redis: one for the last access time, and one for the contents of the key.

Parameters:

  • redis_key_prefix (String)

    the prefix that is going to be used for keys. If your bucket is specific to a user, a browser or an IP address you need to mix in those values into the key prefix as appropriate.

  • leak_rate (Float)

    the leak rate of the bucket, in tokens per second

  • redis (Redis, #with)

    a Redis connection or a ConnectionPool instance if you are using the connection_pool gem. With a connection pool Prorate will checkout a connection using ‘#with` and check it in when it’s done.

  • bucket_capacity (Numeric)

    how many tokens is the bucket capped at. Filling up the bucket using ‘fillup()` will add to that number, but the bucket contents will then be capped at this value. So with bucket_capacity set to 12 and a `fillup(14)` the bucket will reach the level of 12, and will then immediately start leaking again.



71
72
73
74
75
76
# File 'lib/prorate/leaky_bucket.rb', line 71

def initialize(redis_key_prefix:, leak_rate:, redis:, bucket_capacity:)
  @redis_key_prefix = redis_key_prefix
  @redis = redis.respond_to?(:with) ? redis : NullPool.new(redis)
  @leak_rate = leak_rate.to_f
  @capacity = bucket_capacity.to_f
end

Instance Method Details

#fillup(n_tokens) ⇒ BucketState

Places ‘n` tokens in the bucket.

Returns:

  • (BucketState)

    the state of the bucket after the operation



81
82
83
# File 'lib/prorate/leaky_bucket.rb', line 81

def fillup(n_tokens)
  run_lua_bucket_script(n_tokens.to_f)
end

#last_updated_keyString

Returns the Redis key under which the last updated time of the bucket gets stored. Note that the key is not guaranteed to contain a value if the bucket has not been filled up recently.

Returns:

  • (String)


106
107
108
# File 'lib/prorate/leaky_bucket.rb', line 106

def last_updated_key
  "#{@redis_key_prefix}.leaky_bucket.last_updated"
end

#leaky_bucket_keyString

Returns the Redis key for the leaky bucket itself Note that the key is not guaranteed to contain a value if the bucket has not been filled up recently.

Returns:

  • (String)


97
98
99
# File 'lib/prorate/leaky_bucket.rb', line 97

def leaky_bucket_key
  "#{@redis_key_prefix}.leaky_bucket.bucket_level"
end

#stateBucketState

Returns the current state of the bucket, containing the level and whether the bucket is full

Returns:

  • (BucketState)

    the state of the bucket after the operation



88
89
90
# File 'lib/prorate/leaky_bucket.rb', line 88

def state
  run_lua_bucket_script(0)
end