PERF: improve performance once logged in rate limiter hits

If "logged in" is being forced anonymous on certain routes, trigger
the protection for any requests that spend 50ms queueing

This means that ...

1. You need to trip it by having 3 requests take longer than 1 second in 10 second interval
2. Once tripped, if your route is still spending 50m queueuing it will continue to be protected

This means that site will continue to function with almost no delays while it is scaling up to handle the new load
This commit is contained in:
Sam 2018-04-23 11:54:58 +10:00
parent d0353fb2f2
commit ded84a4b58
3 changed files with 38 additions and 10 deletions

View File

@ -198,7 +198,7 @@ max_reqs_rate_limit_on_private = false
# logged in DoS protection # logged in DoS protection
# protection will only trigger for requests that queue longer than this amount # protection will only trigger for requests that queue longer than this amount
force_anonymous_min_queue_seconds = 2 force_anonymous_min_queue_seconds = 1
# only trigger anon if we see more than N requests for this path in last 10 seconds # only trigger anon if we see more than N requests for this path in last 10 seconds
force_anonymous_min_per_10_seconds = 3 force_anonymous_min_per_10_seconds = 3

View File

@ -104,20 +104,29 @@ module Middleware
request.delete_param('api_key') request.delete_param('api_key')
end end
def check_logged_in_rate_limit! def logged_in_anon_limiter
limiter = RateLimiter.new( @logged_in_anon_limiter ||= RateLimiter.new(
nil, nil,
"logged_in_anon_cache_#{@env["HOST"]}/#{@env["REQUEST_URI"]}", "logged_in_anon_cache_#{@env["HOST"]}/#{@env["REQUEST_URI"]}",
GlobalSetting.force_anonymous_min_per_10_seconds, GlobalSetting.force_anonymous_min_per_10_seconds,
10 10
) )
!limiter.performed!(raise_error: false)
end end
def check_logged_in_rate_limit!
!logged_in_anon_limiter.performed!(raise_error: false)
end
MIN_TIME_TO_CHECK = 0.05
def should_force_anonymous? def should_force_anonymous?
if queue_time = @env['REQUEST_QUEUE_SECONDS'] if (queue_time = @env['REQUEST_QUEUE_SECONDS']) && get?
if queue_time > GlobalSetting.force_anonymous_min_queue_seconds && get? if queue_time > GlobalSetting.force_anonymous_min_queue_seconds
return check_logged_in_rate_limit! return check_logged_in_rate_limit!
elsif queue_time >= MIN_TIME_TO_CHECK
if !logged_in_anon_limiter.can_perform?
return check_logged_in_rate_limit!
end
end end
end end

View File

@ -79,18 +79,37 @@ describe Middleware::AnonymousCache::Helper do
"rack.input" => StringIO.new "rack.input" => StringIO.new
} }
app.call(env) is_anon = false
app.call(env.dup)
expect(is_anon).to eq(false) expect(is_anon).to eq(false)
app.call(env) is_anon = false
app.call(env.dup)
expect(is_anon).to eq(false) expect(is_anon).to eq(false)
app.call(env) is_anon = false
app.call(env.dup)
expect(is_anon).to eq(true) expect(is_anon).to eq(true)
_status, headers, _body = app.call(env) is_anon = false
_status, headers, _body = app.call(env.dup)
expect(is_anon).to eq(true) expect(is_anon).to eq(true)
expect(headers['Set-Cookie']).to eq('dosp=1') expect(headers['Set-Cookie']).to eq('dosp=1')
# tricky change, a 50ms delay still will trigger protection
# once it is tripped
env["REQUEST_QUEUE_SECONDS"] = 0.05
is_anon = false
app.call(env.dup)
expect(is_anon).to eq(true)
is_anon = false
env["REQUEST_QUEUE_SECONDS"] = 0.01
app.call(env.dup)
expect(is_anon).to eq(false)
end end
end end