diff options
author | Yorhel <git@yorhel.nl> | 2022-10-12 14:48:30 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2022-10-12 14:48:44 +0200 |
commit | ad2bc771c25ccec5f158bbd348f494654be347df (patch) | |
tree | 765b6bdf7cf8c995947ddd5ea7418f0d14f783ff | |
parent | 1127e8a5c9dd6bb216493855603a809ed1e58adf (diff) |
API2: Also count timeouts towards throttling + allow more bursting
A 5 second execution time burst ensures that hitting the 3 second
timeout won't instantly get you throttled. It's fairly easy to hit that
limit with some weird filter combinations, so let's not be too harsh.
-rw-r--r-- | lib/VNDB/Config.pm | 2 | ||||
-rw-r--r-- | lib/VNWeb/API.pm | 50 |
2 files changed, 30 insertions, 22 deletions
diff --git a/lib/VNDB/Config.pm b/lib/VNDB/Config.pm index bef57cca..25753192 100644 --- a/lib/VNDB/Config.pm +++ b/lib/VNDB/Config.pm @@ -39,7 +39,7 @@ my $config = { ch_size => [ 256, 300 ], # max. w*h of char images cv_size => [ 256, 400 ], # max. w*h of cover images - api_throttle => [ 60, 1 ], # execution time multiplier, allowed burst + api_throttle => [ 60, 5 ], # execution time multiplier, allowed burst Multi => { Core => {}, diff --git a/lib/VNWeb/API.pm b/lib/VNWeb/API.pm index 3d18269e..769abec4 100644 --- a/lib/VNWeb/API.pm +++ b/lib/VNWeb/API.pm @@ -34,41 +34,49 @@ TUWF::options qr{/api/kana.*}, sub { }; -sub err { - my($status, $msg) = @_; - tuwf->resStatus($status); - tuwf->resHeader('Content-type', 'text'); - print { tuwf->resFd } $msg, "\n"; - tuwf->log(sprintf '[%s] %d %s "%s"', tuwf->reqIP(), $status, $msg, tuwf->reqHeader('user-agent')||''); - tuwf->done; -} - # Production API is currently running as a single process, so we can safely and # efficiently keep the throttle state as a local variable. # This throttle state only handles execution time limiting; request limiting # is done in nginx. my %throttle; # IP -> SQL time +my $throttle_start; -sub count_request { - my($start, $rows, $call) = @_; - tuwf->resFd->flush; +sub add_throttle { my $now = time; - my $time = $now-$start; + my $time = $now - $throttle_start; my $norm = norm_ip tuwf->reqIP(); $throttle{$norm} = $now if !$throttle{$norm} || $throttle{$norm} < $now; $throttle{$norm} += $time * config->{api_throttle}[0]; - tuwf->log(sprintf '%4dms %3dr%6db [%s] %s "%s"', - $time*1000, $rows, length(tuwf->{_TUWF}{Res}{content}), - tuwf->reqIP(), $call, tuwf->reqHeader('user-agent')||'-' - ); + $time; } sub check_throttle { + $throttle_start = time; err(429, 'Throttled on query execution time.') if ($throttle{ norm_ip tuwf->reqIP }||0) >= time + (config->{api_throttle}[0] * config->{api_throttle}[1]); } +sub err { + my($status, $msg) = @_; + my $time = add_throttle; + tuwf->resStatus($status); + tuwf->resHeader('Content-type', 'text'); + print { tuwf->resFd } $msg, "\n"; + tuwf->log(sprintf '%4dms [%s] %d %s "%s"', $time, tuwf->reqIP(), $status, $msg, tuwf->reqHeader('user-agent')||''); + tuwf->done; +} + +sub count_request { + my($rows, $call) = @_; + tuwf->resFd->flush; + my $time = add_throttle; + tuwf->log(sprintf '%4dms %3dr%6db [%s] %s "%s"', + $time*1000, $rows, length(tuwf->{_TUWF}{Res}{content}), + tuwf->reqIP(), $call, tuwf->reqHeader('user-agent')||'-' + ); +} + sub api_get { my($path, $schema, $sub) = @_; @@ -80,7 +88,7 @@ sub api_get { $s->analyze->coerce_for_json($res, unknown => 'reject'); tuwf->resJSON($res); tuwf->resHeader('Access-Control-Allow-Origin', '*') if tuwf->reqHeader('Origin'); - count_request($start, 1, '-'); + count_request(1, '-'); }; } @@ -174,7 +182,6 @@ sub api_query { })->($opt{fields}, {}); check_throttle; - my $start = time; my $req = tuwf->validate(json => $req_schema); if(!$req) { eval { $req->data }; warn $@; @@ -208,6 +215,7 @@ sub api_query { alarm 0; 1; } || do { + alarm 0; err 500, 'Processing timeout' if $@ =~ /^Timeout/ || $@ =~ /canceling statement due to statement timeout/; die $@; }; @@ -218,10 +226,10 @@ sub api_query { $req->{count} ? (count => $count) : (), $req->{compact_filters} ? (compact_filters => $req->{filters}->query_encode) : (), $req->{normalized_filters} ? (normalized_filters => $req->{filters}->json) : (), - $req->{time} ? (time => int(1000*(time()-$start))) : (), + $req->{time} ? (time => int(1000*(time()-$throttle_start))) : (), }); tuwf->resHeader('Access-Control-Allow-Origin', '*') if tuwf->reqHeader('Origin'); - count_request($start, scalar @$results, sprintf '[%s] {%s %s r%dp%d} %s', fmt_fields($req->{fields}), + count_request(scalar @$results, sprintf '[%s] {%s %s r%dp%d} %s', fmt_fields($req->{fields}), $req->{sort}, $req->{reverse}?'asc':'desc', $req->{results}, $req->{page}, $req->{filters}->query_encode()||'-'); }; |