# Throttling y rate limiting

> Rechazar en la puerta el tráfico que no cabe, para que el rechazo sea barato y ocurra donde tú lo decides en lugar de caro y donde lo decida el primer servicio que se rompa.

- **Familia:** Frontera
- **También llamado:** Rate limiting
- **Dificultad:** Intermedio
- **Servicios:** API Gateway, Lambda, DynamoDB, CloudWatch

## El problema

Los límites por defecto de tu arquitectura **no están alineados entre sí**, y el
más generoso de todos es justo el primero: el que da a internet.

El problema es que están en unidades distintas y por eso nadie los compara. API
Gateway habla de peticiones por segundo, Lambda de ejecuciones concurrentes y
DynamoDB de unidades de escritura. Puestos en la misma unidad, la forma es
incómoda:

*[Diagrama — se ve en la versión web de esta página]*

La conversión que falta es la [ley de Little](/servicios/lambda/), que en
serverless se usa a diario:

> **concurrencia ≈ peticiones por segundo × duración media en segundos**

Con 1 000 concurrentes y 200 ms de duración, tu Lambda sostiene 5 000
peticiones por segundo. Y una partición de DynamoDB escribe 1 000 por segundo,
y ese número **no se amplía con un ticket**.

Así que la puerta admite diez veces lo que cabe en la habitación. Y sin un
throttle propio, lo que pasa cuando llega el pico no es un rechazo limpio, sino
esto:

- API Gateway acepta las 10 000 peticiones. **Ya las has pagado.**
- Lambda invoca hasta agotar la concurrencia de **toda la cuenta** — incluidas
  las funciones que no tenían nada que ver — y el resto recibe `429`.
- Las que sí entran escriben en DynamoDB, chocan contra el techo de partición y
  reciben `ProvisionedThroughputExceededException`.
- El SDK las reintenta. Con backoff, pero las reintenta.
- El cliente recibe un `500` a los tres segundos y **también** reintenta.

> **La idea**
>
> Sin un límite propio, no evitas el fallo: eliges el sitio más caro para tenerlo.
> Un `429` en la puerta cuesta una petición de API Gateway. El mismo rechazo tres
> capas más adentro cuesta la invocación, la escritura fallida, los reintentos del
> SDK y una cascada de latencia — y encima llega como un `500`, que el cliente
> interpreta como *tu* avería y no como *su* exceso.

Y hay un agravante que sorprende a todo el mundo la primera vez: los 10 000 RPS
son **de la cuenta y de la región**, sumando todas tus APIs y todas tus etapas.
Un test de carga contra *staging* puede tirar producción si comparten cuenta.

## La solución

Poner tu propio límite delante, y ponerlo **donde el rechazo es barato**. API
Gateway lo implementa con el algoritmo del **cubo de fichas**, que tiene dos
parámetros — y la mitad de la gente configura solo uno.

*[Diagrama — se ve en la versión web de esta página]*

- El **rate** es el ritmo al que se reponen las fichas: el tráfico sostenido que
  admites, en peticiones por segundo.
- El **burst** es **la capacidad del cubo**: cuántas peticiones pueden llegar en
  el mismo instante antes de que empiecen los `429`.

La distinción no es académica. Con `rate = 100` y `burst = 200`, un cliente que
llevaba un rato callado puede meterte **200 peticiones simultáneas** y luego
seguir a 100 por segundo. Si dimensionaste lo de detrás para 100, ese pico
inicial va a doler. El burst es un préstamo, y hay que poder devolverlo.

### Dónde se pone el límite

API Gateway aplica cuatro tipos de ajuste, y los aplica **en este orden**:

| Orden | Ajuste | Alcance |
| --- | --- | --- |
| 1 | Por cliente o por método en un **usage plan** | por API key |
| 2 | Por método en una **etapa** | esa etapa |
| 3 | **Por cuenta y región** | todas tus APIs |
| 4 | Límites de AWS por región | todos los clientes de AWS |

Cada nivel está acotado por el de arriba: los límites por cliente **no pueden
superar los de la cuenta**, y los de etapa y método no pueden superar los de
AWS. El que manda en la práctica es siempre el más restrictivo que se toque
primero.

## Cuándo usarlo

**En toda API pública, siempre.** No hay una versión razonable de "mi API no
necesita límite": el valor por defecto es el de la cuenta, y ese valor no lo
elegiste tú.

Los casos concretos donde el patrón es la respuesta:

- **Proteger un cuello de botella conocido.** Una partición de DynamoDB, una
  base de datos relacional con pool de conexiones, un proveedor externo con su
  propio rate limit.
- **Aislar inquilinos.** Un usage plan por cliente evita que uno se lleve la
  capacidad de todos.
- **Vender niveles de servicio.** El *quota* de un usage plan —peticiones por
  día, semana o mes— es un instrumento comercial además de una protección.

Y donde **no** es la respuesta:

**Para autenticar.** Una API key de API Gateway identifica a un cliente para
contarle las peticiones. No es una credencial: viaja en una cabecera, no caduca
sola y no autoriza nada. Si la usas como autenticación, tienes una API abierta
con un contador.

**Para limitar por IP o por usuario.** API Gateway agrupa por API key, por
método y por etapa — no por quien llama. El rate limiting por dirección IP es de
**AWS WAF**, con una *rate-based rule*.

**Cuando el trabajo puede esperar.** Si la petición se puede encolar, tirarla es
la peor opción disponible. El throttle es para lo que hay que contestar ahora;
para lo demás está la [cola](/patrones/cola-punto-a-punto/).

## Cómo implementarlo

### Primero el número, después la consola

El error de método es empezar por el ajuste. El límite correcto **se calcula
desde atrás**: se busca el recurso más estrecho del camino y se sube hasta la
puerta.

Un ejemplo completo, que es lo que realmente hay que hacer con lápiz y papel:

1. **El cuello.** La tabla escribe en una sola partición: **1 000 escrituras/s**.
2. **Margen.** Reserva un 30% para picos y reintentos: **700/s** de objetivo.
3. **Escrituras por petición.** Si cada llamada escribe 2 elementos, la puerta
   admite **350 peticiones/s**.
4. **El rate.** `rate = 350`.
5. **El burst.** Lo que tu cuello aguanta *concurrentemente*, no el doble por
   inercia. Si no tienes un motivo para más, **`burst = rate`** es un punto de
   partida honesto.
6. **Comprobación con la ley de Little.** 350 peticiones/s × 200 ms = **70 de
   concurrencia**. Ése es el número que va en la `reservedConcurrency` de la
   función, con su margen.

Fíjate en que el número final —350— no se parece en nada a los 10 000 del valor
por defecto. Ésa es la distancia que estabas dejando abierta.

### Los tres ajustes de un usage plan

| Ajuste | Qué controla | Unidad |
| --- | --- | --- |
| **Rate** | ritmo sostenido | peticiones por segundo |
| **Burst** | capacidad del cubo | peticiones simultáneas |
| **Quota** | volumen total | peticiones por día, semana o mes |

`quota` es de otra naturaleza: no protege de un pico, protege de un abuso
sostenido y sirve para vender planes. Un cliente puede estar dentro de su rate y
agotar su quota del mes en una tarde.

### Limitar por IP: AWS WAF

Cuando lo que necesitas es "no más de N peticiones desde la misma IP", la
herramienta es una **rate-based rule** de AWS WAF:

- La **ventana de evaluación** admite **60, 120, 300 o 600 segundos**, y el
  valor por defecto son **300** (5 minutos). No es cada cuánto mira, sino cuánto
  hacia atrás mira cada vez.
- El **límite mínimo** que se puede configurar es **10** peticiones por ventana.
- Se puede agregar por IP, por IP reenviada en una cabecera —`X-Forwarded-For`,
  típicamente— o por claves personalizadas.

Con una advertencia que AWS escribe explícitamente: **aplica el límite *cerca*
del valor que configuras, pero no garantiza una coincidencia exacta**. Es una
protección, no un contador.

## El código

**Throttle de etapa y de método** con la CLI. Es el ajuste que falta en el 90%
de las APIs desplegadas:

```bash
aws apigateway update-stage \
  --rest-api-id abc123 \
  --stage-name prod \
  --patch-operations \
    op=replace,path=/*/*/throttling/rateLimit,value=350 \
    op=replace,path=/*/*/throttling/burstLimit,value=350
```

El `/*/*/` significa *todos los recursos, todos los métodos*. Para un método
concreto se sustituye por la ruta y el verbo — y ahí es donde se protege el
endpoint caro sin castigar a los demás:

```bash
op=replace,path=/~1pedidos/POST/throttling/rateLimit,value=50
```

La `~1` es un `/` escapado: `~1pedidos` es el recurso `/pedidos`. Es la parte de
esta API que más tiempo hace perder.

**Un usage plan** con sus tres ajustes:

```bash
aws apigateway create-usage-plan \
  --name "plan-basico" \
  --throttle rateLimit=100,burstLimit=100 \
  --quota limit=100000,period=MONTH \
  --api-stages apiId=abc123,stage=prod
```

**Y el cliente.** Un `429` que nadie interpreta no reduce el tráfico. Lo mínimo
exigible al consumidor es distinguirlo de un error de verdad:

```python
resp = requests.post(url, json=carga)

if resp.status_code == 429:
    # No es un fallo: es la puerta diciendo "ahora no".
    # Reintentar sin backoff convierte el límite en un bucle cerrado.
    esperar_con_backoff_y_jitter(intento)
elif resp.status_code >= 500:
    ...
```

## Te va a morder

> **El límite es un objetivo, no un techo**
>
> AWS lo dice con todas las letras: los throttles y las quotas se aplican
> *"en la medida de lo posible"* y hay que entenderlos como **objetivos, más que
> como techos garantizados**. El propio cubo de fichas permite, por diseño, que se
> sobrepase el límite en una ráfaga, y AWS añade que otros factores pueden causar
> que se rebase en algunos casos.
>
> La consecuencia práctica: **no uses el throttle de API Gateway como la única
> garantía** de que algo no se desborda. Es la primera línea, y es barata y
> efectiva, pero lo de detrás sigue necesitando su propio límite —concurrencia
> reservada, capacidad de la tabla— porque va a ver más tráfico del que pusiste en
> la puerta.

**Configurar el rate y olvidar el burst.** Es el error número uno. Pones
`rate = 100`, duermes tranquilo, y un cliente te mete el burst entero de golpe.
Si no sabes qué burst quieres, ponlo igual al rate y súbelo cuando tengas un
motivo.

**Creer que el límite es por API.** Los 10 000 RPS son **por cuenta y región**.
Comparten ese número producción, *staging*, la API interna y la que montaste
para una demo. Es un argumento fuerte para separar entornos en cuentas
distintas.

**Contar con que tu región va a 10 000.** En catorce regiones el valor por
defecto es **2 500**, con burst **1 250**.

**Pedir un burst mayor.** El burst **lo fija AWS** a partir del RPS de tu
cuenta; no es un parámetro que se solicite por separado. Si necesitas más
ráfaga, lo que se pide es más RPS.

**Poner el throttle y no mirarlo.** Un límite bien puesto rechaza tráfico
legítimo de vez en cuando, y eso hay que verlo. Sin una alarma sobre `4XXError`
y sobre el conteo de peticiones rechazadas, la diferencia entre "el límite está
funcionando" y "llevo tres semanas rechazando al 20% de mis clientes" es
invisible.

**Usar API keys como autenticación.** Identifican, no autentican. La
autorización va en un autorizador —JWT, Lambda authorizer— y la API key solo
dice a qué usage plan perteneces.

**Olvidar que el 429 tiene que enseñar algo.** Devuelve `Retry-After` si puedes,
y documenta los límites. Un cliente que no sabe cuál es su límite no puede
respetarlo, y acabará descubriéndolo a base de reintentar.

## Coste

La aritmética de este patrón es la más favorable de todo el catálogo, porque
**lo que se ahorra es todo lo que había detrás**:

| Dónde se rechaza | Qué has pagado ya |
| --- | --- |
| **En la puerta** (`429`) | la petición de API Gateway |
| En Lambda (concurrencia agotada) | + la invocación y su duración |
| En DynamoDB (throttling) | + la escritura fallida y los reintentos del SDK |
| En el cliente (`500` tras el timeout) | + la latencia, y el reintento del cliente |

Cada fila añade coste **y** empeora la señal: el mismo exceso de tráfico se
presenta como un `429` claro en la primera y como una avería tuya en la última.

Y hay un ahorro que no está en la factura de AWS: **el throttle convierte un
incidente en una decisión**. Sin él, cuando llega el pico, lo que se rompe lo
decide el azar. Con él, sabes exactamente qué se va a rechazar y a partir de
qué número.

## Fuentes

El algoritmo del cubo de fichas, el significado de *rate* como ritmo de reposición
y de *burst* como capacidad del cubo, el orden en que se aplican los cuatro tipos
de ajuste, que los límites por cliente no pueden superar los de la cuenta, que el
límite de cuenta se examina **sobre todas las APIs de la cuenta y la región**, la
respuesta `429 Too Many Requests` y la advertencia de que throttles y quotas se
aplican en la medida de lo posible y deben entenderse como objetivos y no como
techos garantizados:
[Throttle requests to your REST APIs — Amazon API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html).
Los 10 000 RPS por cuenta y región, los 2 500 de catorce regiones, el burst de
5 000 y 1 250 y que lo determina AWS a partir del RPS:
[Amazon API Gateway quotas and important notes](https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html).
El límite de 1 000 ejecuciones concurrentes por región:
[Lambda quotas](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html).
Las 1 000 unidades de escritura por segundo de una partición:
[Amazon DynamoDB service quotas](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html).
La ventana de evaluación de 60, 120, 300 o 600 segundos con 300 por defecto, el
límite mínimo de 10 peticiones y la advertencia de que WAF aplica el límite cerca
del valor configurado sin garantizar una coincidencia exacta:
[Rate-based rule high-level settings in AWS WAF](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-rate-based-high-level-settings.html).

## Patrones relacionados

- **Concurrencia reservada como aislamiento** — *se combina con*. Son la puerta y el mamparo. El throttle decide cuánto tráfico entra; la concurrencia reservada decide cuánto de tu cuenta puede consumir cada función. Sin los dos, el que sobra siempre se lo come alguien.
- **Circuit breaker** — *se combina con*. Las dos caras de la misma moneda. El cortacircuitos decide dejar de llamar; el throttle decide dejar de que te llamen. Y los dos convierten una espera cara en un error barato.
- **Retry con backoff y jitter** — *se combina con*. Un 429 solo sirve si el cliente lo entiende. Contra un cliente que reintenta sin backoff, el rate limit no reduce el tráfico: lo convierte en un bucle cerrado que llega todavía más rápido.
- **Cola punto a punto** — *compite con*. Son las dos respuestas al exceso de tráfico. El throttle lo rechaza; la cola lo guarda. Si el trabajo puede esperar, tirarlo es la peor de las dos opciones.


---

Fuente: <https://aws.crafter.run/patrones/throttling-y-rate-limiting/>

Datos verificados el 2026-09-06.

Los iconos de arquitectura son © Amazon Web Services, Inc., usados sin modificación.

