Limitación de velocidad NGINX en pocas palabras

NGINX es increíble ... pero encontré que su documentación sobre la limitación de velocidad es algo ... limitada. Así que escribí esta guía para la limitación de velocidad y la configuración del tráfico con NGINX.

Iremos a:

  • describir las directivas NGINX
  • explicar la lógica de aceptar / rechazar de NGINX
  • Ayudarle a visualizar cómo se procesa una ráfaga real de tráfico mediante varias configuraciones: limitación de velocidad, política de tráfico y permitir ráfagas pequeñas

Como beneficio adicional, he incluido un repositorio de GitHub y la imagen de Docker resultante para que pueda experimentar y reproducir las pruebas. ¡Siempre es más fácil aprender haciendo!

Directivas de límite de velocidad de NGINX y sus funciones

Esta publicación se centra en ngx_http_limit_req_module, que le proporciona las directivas limit_req_zoney limit_req. También proporciona los archivos limit_req_statusy limit_req_level. Juntos, estos le permiten controlar el código de estado de respuesta HTTP para las solicitudes rechazadas y cómo se registran estos rechazos.

La mayor parte de la confusión proviene de la lógica del rechazo.

En primer lugar, es necesario comprender la limit_reqDirectiva, que necesita un zoneparámetro, y también proporciona opcionalburst y nodelayparámetros.

Hay varios conceptos en juego aquí:

  • zonele permite definir un depósito, un "espacio" compartido en el que contar las solicitudes entrantes. Todas las solicitudes que ingresen al mismo grupo se contarán en el mismo límite de tasa. Esto es lo que le permite limitar por URL, por IP o cualquier cosa elegante.
  • burstes opcional. Si se establece, define cuántas solicitudes excedentes puede aceptar sobre la tarifa base. Una cosa importante a tener en cuenta aquí: la ráfaga es un valor absoluto, no es una tasa .
  • nodelaytambién es opcional y solo es útil cuando también establece un burstvalor, y veremos por qué a continuación.

¿Cómo decide NGINX si una solicitud es aceptada o rechazada?

Cuando establece una zona, define una tasa, como 300r/mpermitir 300 solicitudes por minuto o 5r/s5 solicitudes por segundo.

Por ejemplo:

  • limit_req_zone $request_uri zone=zone1:10m rate=300r/m;
  • limit_req_zone $request_uri zone=zone2:10m rate=5r/s;

Es importante comprender que estas 2 zonas tienen los mismos límites. La rateconfiguración se usa en Nginx para calcular una frecuencia: ¿cuál es el intervalo de tiempo antes de aceptar una nueva solicitud? NGINX aplicará el algoritmo del cubo con fugas con esta frecuencia de actualización del token.

Para NGINX, 300r/my 5r/sse tratan de la misma manera: permita una solicitud cada 0.2 segundos para esta zona. Cada 0.2 segundos, en este caso, NGINX establecerá una bandera para recordar que puede aceptar una solicitud. Cuando entra una solicitud que se ajusta a esta zona, NGINX establece el indicador en falso y lo procesa. Si llega otra solicitud antes de que se active el temporizador, se rechazará inmediatamente con un código de estado 503. Si el temporizador marca y la bandera ya estaba configurada para aceptar una solicitud, nada cambia.

¿Necesita limitar la velocidad o modelar el tráfico?

Ingrese el burstparámetro. Para entenderlo, imagine que la bandera que explicamos anteriormente ya no es un booleano, sino un número entero: el número máximo de solicitudes que NGINX puede permitir en una ráfaga.

Este ya no es un algoritmo de depósito con fugas, sino un depósito de fichas. El ratecontrola qué tan rápido se activa el temporizador, pero ya no es un token de verdadero / falso, sino un contador que va de 0a 1+burst value. Cada vez que el temporizador marca, el contador se incrementa, a menos que ya esté en su valor máximo de b+1. Ahora debe comprender por qué la burstconfiguración es un valor y no una tasa.

Cuando llega una nueva solicitud, NGINX comprueba si hay un token disponible (es decir, el contador es> 0); de lo contrario, la solicitud se rechaza. Si hay un token, la solicitud se acepta y se tratará, y ese token se consumirá (el contador disminuye).

Ok, entonces NGINX aceptará la solicitud si hay un token de ráfaga disponible. Pero, ¿cuándo procesará NGINX esta solicitud?

Le pidió a NGINX que aplicara una tasa máxima de 5r/s, NGINX acepta las solicitudes excedentes si los tokens de ráfaga están disponibles, pero esperará algo de espacio para procesarlos dentro de ese límite de tasa máxima. Por lo tanto, estas solicitudes de ráfagas se procesarán con cierto retraso o se agotarán.

En otras palabras, NGINX no superará el límite de velocidad establecido en la declaración de zona y, por lo tanto, pondrá en cola las solicitudes adicionales y las procesará con cierta demora, ya que el temporizador del token marca y se reciben menos solicitudes.

Para usar un ejemplo simple, digamos que tiene una tasa de 1r/sy una ráfaga de 3. NGINX recibe 5 solicitudes al mismo tiempo:

  • El primero es aceptado y procesado
  • Debido a que permite 1 + 3, hay 1 solicitud que se rechaza de inmediato, con un código de estado 503
  • Los otros 3 serán tratados, uno por uno, pero no de inmediato. Serán tratados a razón de 1r/spermanecer dentro del límite establecido. Si no entra ninguna otra solicitud, ya está consumiendo esta cuota. Una vez que la cola está vacía, el contador de ráfagas comenzará a incrementarse nuevamente (el depósito de tokens comienza a llenarse nuevamente)

Si usa NGINX como proxy, el flujo ascendente recibirá la solicitud a una velocidad máxima de 1r/s, y no será consciente de ninguna ráfaga de solicitudes entrantes, todo tendrá un límite a esa velocidad.

Acaba de configurar el tráfico, introduciendo un retraso para regular las ráfagas y producir una transmisión más regular fuera de NGINX.

Ingrese nodelay

nodelay le dice a NGINX que las solicitudes que acepta en la ventana de ráfaga deben procesarse inmediatamente, como las solicitudes regulares.

Como consecuencia, los picos se propagarán a NGINX upstream, pero con algún límite, definido por el burstvalor.

Visualización de límites de velocidad

Debido a que creo que la mejor manera de recordar esto es experimentarlo de manera práctica, configuré una pequeña imagen de Docker con una configuración de NGINX que expone varias configuraciones de límite de velocidad para ver las respuestas a una ubicación limitada de velocidad básica, a un burst-ubicación con tarifa limitada habilitada, y burstcon la ubicación con nodelaytarifa limitada, juguemos con eso.

Estas muestras usan esta configuración simple de NGINX (para la que proporcionaremos una imagen de Docker al final de esta publicación para que pueda probar esto más fácilmente):

limit_req_zone $request_uri zone=by_uri:10m rate=30r/m; server { listen 80; location /by-uri/burst0 { limit_req zone=by_uri; try_files $uri /index.html; } location /by-uri/burst5 { limit_req zone=by_uri burst=5; try_files $uri /index.html; } location /by-uri/burst5_nodelay { limit_req zone=by_uri burst=5 nodelay; try_files $uri /index.html; } }

A partir de esta configuración, todos los ejemplos siguientes enviarán 10 solicitudes simultáneas a la vez. Veamos:

  • ¿Cuántos son rechazados por el límite de tasa?
  • ¿Cuál es la tasa de procesamiento de los aceptados?

Sending 10 parallel requests to a rate-limited endpoint

That config allows 30 requests per minute. But 9 out of 10 requests are rejected in that case. If you followed the previous steps, this should make sense: The 30r/m means that a new request is allowed every 2 seconds. Here 10 requests arrive at the same time, one is allowed, the 9 other ones are seen by NGINX before the token-timer ticks, and are therefore all rejected.

But I’m OK to tolerate some burst for some client/endpoints

Ok, so let’s add the burst=5 argument to let NGINX handle small bursts for this endpoint of the rate-limited zone:

What’s going on here? As expected with the burst argument, 5 more requests are accepted, so we went from 1 /10 to 6/10 success (and the rest is rejected). But the way NGINX refreshed its token and processed the accepted requests is quite visible here: the outgoing rate is capped at 30r/m which is equivalent to 1 request every 2 seconds.

The first one is returned after 0.2 seconds. The timer ticks after 2 seconds, and one of the pending requests is processed and returned, with a total roundtrip time of 2.02 seconds. 2 seconds later, the timer ticks again, processing another pending request, which is returned with a total roundtrip time of 4.02 seconds. And so on and so forth…

The burst argument just lets you turn NGINX rate-limit from some basic threshold filter to a traffic shaping policy gateway.

My server has some extra capacity. I want to use a rate-limit to prevent it from going over this capacity.

In this case, the nodelay argument will be helpful. Let’s send the same 10 requests to a burst=5 nodelay endpoint:

As expected with the burst=5 we still have the same number of status 200 and 503. But now the outgoing rate is no longer strictly constrained to the rate of 1 requests every 2 seconds. As long as some burst tokens are available, any incoming request is accepted and processed immediately. The timer tick rate is still as important as before to control the refresh/refill rate of these burst tokens, but accepted requests no longer suffer any additional delay.

Note: in this case, the zone uses the $request_uri but all the following tests work exactly the same way for a $binary_remote_addr config which would rate-limit by client IP. You’ll be able to play with this in the Docker image.

Let’s recap

If we try to visualize how NGINX accepts the incoming requests, then processes them depending on the rate, burst, and nodelay parameter, here’s a synthetic view.

To keep things simple, we’ll show the number of incoming requests (then accepted or rejected, and processed) per time step, the value of the time step depending on the zone-defined rate limit. But the actual duration of that step doesn’t matter in the end. What is meaningful is the number of requests NGINX has to process within each of these steps.

So here is the traffic we’ll send through various rate limit settings:

Without using the burst (i.e. burst=0), we saw that NGINX acts as a pure rate-limit/traffic-policy actor. All requests are either immediately processed, if the rate timer has ticked, or immediately rejected otherwise.

Now if we want to allow the small burst to use the unused capacity under the rate-limit, we saw that adding a burst argument lets use do that, which implies some additional delay in processing the requests consuming the burst tokens:

We can see that the overall number of rejected requests is lower, and NGINX processes more requests. Only the extra requests when no burst tokens are available are rejected. In this setup, NGINX performs some real traffic-shaping.

Finally, we saw that NGINX can be used to either do some traffic-policy or to limit the size of the burst, but still propagates some of these bursts to the processing workers (upstreams or local), which, in the end, does generate less stable outgoing rate, but with a better latency, if you can process these extra requests:

Playing with the rate limit sandbox yourself

Now you can go explore the code, clone the repo, play with the Docker image, and get your hands on it real quick to better solidify your understanding of these concepts. //github.com/sportebois/nginx-rate-limit-sandbox

Update (June 14th, 2017)

NGINX published a few days ago their own detailed explanation of their rate-limiting mechanism. You can now learn more about it in their Rate Limiting with NGINX and NGINX Plus blog post.