Commit 395d7ca5 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Remove rate limiter feature flag

Remove feature flag for switching the rate limiter to use the new
increment strategy

Changelog: other
parent 5e510997
---
name: rate_limiter_safe_increment
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73343
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/285352
milestone: '14.5'
type: development
group: group::project management
default_enabled: false
...@@ -79,28 +79,6 @@ module Gitlab ...@@ -79,28 +79,6 @@ module Gitlab
increment(key, options[:scope]) > threshold_value increment(key, options[:scope]) > threshold_value
end end
# Increments the given cache key and increments the value by 1 with the
# expiration interval defined in `.rate_limits`.
#
# @param key [Symbol] Key attribute registered in `.rate_limits`
# @option scope [Array<ActiveRecord>] Array of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)
#
# @return [Integer] incremented value
def increment(key, scope)
return safe_increment(key, scope) if Feature.enabled?(:rate_limiter_safe_increment, default_enabled: :yaml)
value = 0
interval_value = interval(key)
::Gitlab::Redis::RateLimiting.with do |redis|
cache_key = action_key(key, scope)
value = redis.incr(cache_key)
redis.expire(cache_key, interval_value) if value == 1
end
value
end
# Increments a cache key that is based on the current time and interval. # Increments a cache key that is based on the current time and interval.
# So that when time passes to the next interval, the key changes and the count starts again from 0. # So that when time passes to the next interval, the key changes and the count starts again from 0.
# #
...@@ -110,7 +88,7 @@ module Gitlab ...@@ -110,7 +88,7 @@ module Gitlab
# @option scope [Array<ActiveRecord>] Array of ActiveRecord models to scope throttling to a specific request (e.g. per user per project) # @option scope [Array<ActiveRecord>] Array of ActiveRecord models to scope throttling to a specific request (e.g. per user per project)
# #
# @return [Integer] incremented value # @return [Integer] incremented value
def safe_increment(key, scope) def increment(key, scope)
interval_value = interval(key) interval_value = interval(key)
period_key, time_elapsed_in_period = Time.now.to_i.divmod(interval_value) period_key, time_elapsed_in_period = Time.now.to_i.divmod(interval_value)
......
...@@ -8,7 +8,7 @@ RSpec.describe Gitlab::ApplicationRateLimiter do ...@@ -8,7 +8,7 @@ RSpec.describe Gitlab::ApplicationRateLimiter do
subject { described_class } subject { described_class }
describe '.throttled?' do describe '.throttled?', :clean_gitlab_redis_rate_limiting do
let(:rate_limits) do let(:rate_limits) do
{ {
test_action: { test_action: {
...@@ -56,136 +56,51 @@ RSpec.describe Gitlab::ApplicationRateLimiter do ...@@ -56,136 +56,51 @@ RSpec.describe Gitlab::ApplicationRateLimiter do
end end
end end
context 'when rate_limiter_safe_increment is disabled' do shared_examples 'throttles based on key and scope' do
let(:redis) { double('redis') } let(:start_time) { Time.current.beginning_of_hour }
let(:key) { rate_limits.keys[0] }
before do it 'returns true when threshold is exceeded' do
allow(Gitlab::Redis::RateLimiting).to receive(:with).and_yield(redis) travel_to(start_time) do
expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
stub_feature_flags(rate_limiter_safe_increment: false)
end
shared_examples 'action rate limiter' do
it 'increases the throttle count and sets the expiration time' do
expect(redis).to receive(:incr).with(cache_key).and_return(1)
expect(redis).to receive(:expire).with(cache_key, 120)
expect(subject.throttled?(key, scope: scope)).to be_falsy
end end
it 'returns true if the key is throttled' do travel_to(start_time + 1.minute) do
expect(redis).to receive(:incr).with(cache_key).and_return(2) expect(subject.throttled?(:test_action, scope: scope)).to eq(true)
expect(redis).not_to receive(:expire)
expect(subject.throttled?(key, scope: scope)).to be_truthy # Assert that it does not affect other actions or scope
end expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
expect(subject.throttled?(:test_action, scope: [user])).to eq(false)
context 'when throttling is disabled' do
it 'returns false and does not set expiration time' do
expect(redis).not_to receive(:incr)
expect(redis).not_to receive(:expire)
expect(subject.throttled?(key, scope: scope, threshold: 0)).to be_falsy
end
end end
end end
context 'when the key is an array of only ActiveRecord models' do it 'returns false when interval has elapsed' do
let(:scope) { [user, project] } travel_to(start_time) do
expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
let(:cache_key) do # another_action has a threshold of 3 so we simulate 2 requests
"application_rate_limiter:test_action:user:#{user.id}:project:#{project.id}" expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
end end
it_behaves_like 'action rate limiter' travel_to(start_time + 2.minutes) do
expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
context 'when a scope attribute is nil' do
let(:scope) { [user, nil] }
let(:cache_key) do
"application_rate_limiter:test_action:user:#{user.id}"
end
it_behaves_like 'action rate limiter'
end
end
context 'when the key is a combination of ActiveRecord models and strings' do
let(:project) { create(:project, :public, :repository) }
let(:commit) { project.repository.commit }
let(:path) { 'app/controllers/groups_controller.rb' }
let(:scope) { [project, commit, path] }
let(:cache_key) do
"application_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}:#{path}"
end
it_behaves_like 'action rate limiter'
context 'when a scope attribute is nil' do
let(:scope) { [project, commit, nil] }
let(:cache_key) do
"application_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}"
end
it_behaves_like 'action rate limiter' # Assert that another_action has its own interval that hasn't elapsed
expect(subject.throttled?(:another_action, scope: scope)).to eq(true)
end end
end end
end end
context 'when rate_limiter_safe_increment is enabled', :clean_gitlab_redis_rate_limiting do context 'when using ActiveRecord models as scope' do
before do let(:scope) { [user, project] }
stub_feature_flags(rate_limiter_safe_increment: true)
end
shared_examples 'throttles based on key and scope' do
let(:start_time) { Time.current.beginning_of_hour }
it 'returns true when threshold is exceeded' do
travel_to(start_time) do
expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
end
travel_to(start_time + 1.minute) do
expect(subject.throttled?(:test_action, scope: scope)).to eq(true)
# Assert that it does not affect other actions or scope
expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
expect(subject.throttled?(:test_action, scope: [user])).to eq(false)
end
end
it 'returns false when interval has elapsed' do
travel_to(start_time) do
expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
# another_action has a threshold of 3 so we simulate 2 requests
expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
end
travel_to(start_time + 2.minutes) do
expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
# Assert that another_action has its own interval that hasn't elapsed
expect(subject.throttled?(:another_action, scope: scope)).to eq(true)
end
end
end
context 'when using ActiveRecord models as scope' do
let(:scope) { [user, project] }
it_behaves_like 'throttles based on key and scope' it_behaves_like 'throttles based on key and scope'
end end
context 'when using ActiveRecord models and strings as scope' do context 'when using ActiveRecord models and strings as scope' do
let(:scope) { [project, 'app/controllers/groups_controller.rb'] } let(:scope) { [project, 'app/controllers/groups_controller.rb'] }
it_behaves_like 'throttles based on key and scope' it_behaves_like 'throttles based on key and scope'
end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment