Class: TranslationDiff::RateLimiters::ActiveRecord
- Inherits:
-
Object
- Object
- TranslationDiff::RateLimiters::ActiveRecord
- Includes:
- ActiveRecord::Support
- Defined in:
- lib/translation_diff/rate_limiters/active_record.rb
Overview
Throttles by counting characters into namespaced, time-bucketed rows in the application's own database.
Constant Summary collapse
- DEFAULT_THRESHOLD =
8000- DEFAULT_INTERVAL =
60- BUCKET_FRACTION =
A bucket a twelfth of the interval wide caps a window's slop at under 10%, the same shape the
ratelimitgem gets from its own fixed five-second buckets at the default 60-second interval. 12
Constants included from ActiveRecord::Support
ActiveRecord::Support::MINIMUM_ACTIVE_RECORD
Class Method Summary collapse
-
.build(config) ⇒ Object
An unset rate_limit must mean DEFAULT_THRESHOLD, not the nil that would override that keyword default.
Instance Method Summary collapse
-
#check(size) ⇒ Object
A sliding window: every bucket covering the last
intervalseconds is summed, not just the current one. -
#exceeded_message ⇒ Object
Counts and settings, never a character of what was being translated.
-
#initialize(namespace:, table_name:, threshold: DEFAULT_THRESHOLD, interval: DEFAULT_INTERVAL, base: nil, clock: -> { Time.now }) ⇒ ActiveRecord
constructor
A new instance of ActiveRecord.
-
#prune ⇒ Object
Buckets that have fully aged out of the window as of now; the oldest bucket itself is still counted by it.
-
#redacted_error(error) ⇒ Object
The limiter's own statements carry counts, not content -- but a ReadOnlyError quotes the statement, and a raw ActiveRecord error from inside a translate call tells a caller nothing about which gem it came from.
Methods included from ActiveRecord::Support
Constructor Details
#initialize(namespace:, table_name:, threshold: DEFAULT_THRESHOLD, interval: DEFAULT_INTERVAL, base: nil, clock: -> { Time.now }) ⇒ ActiveRecord
Returns a new instance of ActiveRecord.
20 21 22 23 24 25 26 27 28 29 |
# File 'lib/translation_diff/rate_limiters/active_record.rb', line 20 def initialize(namespace:, table_name:, threshold: DEFAULT_THRESHOLD, interval: DEFAULT_INTERVAL, base: nil, clock: -> { Time.now }) @namespace = namespace @table_name = table_name @threshold = threshold @interval = interval @base = base @clock = clock @bucket_width = [@interval / BUCKET_FRACTION, 1].max end |
Class Method Details
.build(config) ⇒ Object
An unset rate_limit must mean DEFAULT_THRESHOLD, not the nil that would override that keyword default.
13 14 15 16 17 18 |
# File 'lib/translation_diff/rate_limiters/active_record.rb', line 13 def self.build(config) = { namespace: config.cache_namespace, table_name: config.rate_limit_table_name, interval: config.rate_interval, base: config.active_record_base } [:threshold] = config.rate_limit unless config.rate_limit.nil? new(**) end |
Instance Method Details
#check(size) ⇒ Object
A sliding window: every bucket covering the last interval seconds is summed, not just the current one.
32 33 34 35 36 37 38 39 40 |
# File 'lib/translation_diff/rate_limiters/active_record.rb', line 32 def check(size) raise TranslationDiff::RateLimitExceeded, if current_total >= @threshold add(size) rescue StandardError => e raise unless ar_error?(e) raise redacted_error(e), cause: nil end |
#exceeded_message ⇒ Object
Counts and settings, never a character of what was being translated.
51 52 53 |
# File 'lib/translation_diff/rate_limiters/active_record.rb', line 51 def "rate limit reached for #{@namespace}: #{@threshold} characters per #{@interval} seconds" end |
#prune ⇒ Object
Buckets that have fully aged out of the window as of now; the oldest bucket itself is still counted by it.
56 |
# File 'lib/translation_diff/rate_limiters/active_record.rb', line 56 def prune = model.where(namespace: @namespace).where(bucket: ...oldest_bucket).delete_all |
#redacted_error(error) ⇒ Object
The limiter's own statements carry counts, not content -- but a ReadOnlyError quotes the statement, and a raw ActiveRecord error from inside a translate call tells a caller nothing about which gem it came from.
44 45 46 47 48 |
# File 'lib/translation_diff/rate_limiters/active_record.rb', line 44 def redacted_error(error) adapter_error = error.cause&.class || error.class TranslationDiff::Error.new("the rate limit check failed (#{adapter_error}): a read or upsert on " \ "#{@table_name}(namespace, bucket, characters)") end |