nginx のレート制限が期待通りに動作しない

nginx のレート制限が期待通りに動作しない

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 個あるまでリクエストが遅延され、その後、リクエストが拒否され始めます。

関連情報