summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2022-10-12 14:48:30 +0200
committerYorhel <git@yorhel.nl>2022-10-12 14:48:44 +0200
commitad2bc771c25ccec5f158bbd348f494654be347df (patch)
tree765b6bdf7cf8c995947ddd5ea7418f0d14f783ff
parent1127e8a5c9dd6bb216493855603a809ed1e58adf (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.pm2
-rw-r--r--lib/VNWeb/API.pm50
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()||'-');
};