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:
La conversión que falta es la ley de Little, 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
500a los tres segundos y también reintenta.
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.
- 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.
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:
- El cuello. La tabla escribe en una sola partición: 1 000 escrituras/s.
- Margen. Reserva un 30% para picos y reintentos: 700/s de objetivo.
- Escrituras por petición. Si cada llamada escribe 2 elementos, la puerta admite 350 peticiones/s.
- El rate.
rate = 350. - El burst. Lo que tu cuello aguanta concurrentemente, no el doble por
inercia. Si no tienes un motivo para más,
burst = ratees un punto de partida honesto. - Comprobación con la ley de Little. 350 peticiones/s × 200 ms = 70 de
concurrencia. Ése es el número que va en la
reservedConcurrencyde 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:
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:
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:
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:
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
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.
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.
El límite de 1 000 ejecuciones concurrentes por región:
Lambda quotas.
Las 1 000 unidades de escritura por segundo de una partición:
Amazon DynamoDB service quotas.
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.