![nginx のレート制限が期待通りに動作しない](https://rvso.com/image/1684440/nginx%20%E3%81%AE%E3%83%AC%E3%83%BC%E3%83%88%E5%88%B6%E9%99%90%E3%81%8C%E6%9C%9F%E5%BE%85%E9%80%9A%E3%82%8A%E3%81%AB%E5%8B%95%E4%BD%9C%E3%81%97%E3%81%AA%E3%81%84.png)
nginx 接続のレート制限を試みていますが、期待どおりに動作しないようです。1 秒あたり 2 件と 10 件のリクエストを使用してテストしてみました。
まず、1秒あたり2回のリクエスト
limit_req_zone $binary_remote_addr zone=myzone:10m rate=2r/s;
limit_req_status 429;
server {
listen *:80;
server_name 172.23.97.94;
root /var/www/html;
index index.html;
location / {
limit_req zone=myzone;
try_files $uri $uri/ =404;
}
}
curl によるテスト:
for i in {1..2}; do curl -I -s "http://172.23.97.94" | head -n 1; done
HTTP/1.1 200 OK
HTTP/1.1 429 Too Many Requests
Access.log では、同時に 2 つのリクエストしかないことが確認されていますが、2 番目のリクエストは 429 になります。
172.23.106.65 - - [08/Feb/2023:17:10:35 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.68.0"
172.23.106.65 - - [08/Feb/2023:17:10:35 +0000] "HEAD / HTTP/1.1" 429 0 "-" "curl/7.68.0"
しかし、同じリクエストを 0.5 秒のスリープで実行すると、うまくいきます。
for i in {1..2}; do curl -I -s "http://172.23.97.94" | head -n 1; sleep 0.5; done
HTTP/1.1 200 OK
HTTP/1.1 200 OK
2番目、1秒あたり10リクエスト
limit_req_zone $binary_remote_addr zone=myzone:10m rate=10r/s;
limit_req_status 429;
server {
listen *:80;
server_name 172.23.97.94;
root /var/www/html;
index index.html;
location / {
limit_req zone=myzone;
try_files $uri $uri/ =404;
}
}
curl によるテスト:
for i in {1..10}; do curl -I -s "http://172.23.97.94" | head -n 1; done
HTTP/1.1 200 OK
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
Access.log では、同時に接続が 10 件しかないことが確認されていますが、200 件になるのは最初の接続のみです。
172.23.106.65 - - [08/Feb/2023:17:14:53 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.68.0"
172.23.106.65 - - [08/Feb/2023:17:14:53 +0000] "HEAD / HTTP/1.1" 429 0 "-" "curl/7.68.0"
172.23.106.65 - - [08/Feb/2023:17:14:53 +0000] "HEAD / HTTP/1.1" 429 0 "-" "curl/7.68.0"
172.23.106.65 - - [08/Feb/2023:17:14:53 +0000] "HEAD / HTTP/1.1" 429 0 "-" "curl/7.68.0"
172.23.106.65 - - [08/Feb/2023:17:14:53 +0000] "HEAD / HTTP/1.1" 429 0 "-" "curl/7.68.0"
172.23.106.65 - - [08/Feb/2023:17:14:53 +0000] "HEAD / HTTP/1.1" 429 0 "-" "curl/7.68.0"
172.23.106.65 - - [08/Feb/2023:17:14:53 +0000] "HEAD / HTTP/1.1" 429 0 "-" "curl/7.68.0"
172.23.106.65 - - [08/Feb/2023:17:14:53 +0000] "HEAD / HTTP/1.1" 429 0 "-" "curl/7.68.0"
172.23.106.65 - - [08/Feb/2023:17:14:53 +0000] "HEAD / HTTP/1.1" 429 0 "-" "curl/7.68.0"
172.23.106.65 - - [08/Feb/2023:17:14:53 +0000] "HEAD / HTTP/1.1" 429 0 "-" "curl/7.68.0"
しかし、同じリクエストを 0.01 秒のスリープで実行すると、一部は 200 になり、その他は 429 になります。
for i in {1..10}; do curl -I -s "http://172.23.97.94/device/1" | head -n 1; sleep 0.01; done
HTTP/1.1 200 OK
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 200 OK
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 429 Too Many Requests
HTTP/1.1 200 OK
何か間違ったことをしているのでしょうか? それともレート制限が期待どおりに機能しないだけでしょうか?
答え1
私もこの問題に遭遇しました。10r/s は実際には 10 分の 1 秒ごとに 1 つのリクエストです。
関連する Stack Overflow の投稿は次のとおりです。https://stackoverflow.com/questions/62262540/ideal-config-for-nginx-rate-limiting
nginx がこれについて言及している記事は次のとおりです。https://www.nginx.com/blog/rate-limiting-nginx/
1 つのページでいくつかの Ajax リクエストを読み込むのが一般的であり、この動作方法ではレート制限を行うと遅延が発生するため、これは悪い決定であるように思われます。
記事によると、 'nodelay' オプションを使用して 2 段階のレート制限を行うことで、この問題を回避できるようです。
limit_req zone=ip burst=12 delay=8;
これにより、一度に 8 つのリクエストが許可され、その後、キューに 12 個あるまでリクエストが遅延され、その後、リクエストが拒否され始めます。