¿Cómo evitar el Rate Limiting?

Técnicas para realizar múltiples llamadas respetando las políticas de Rate Limiting. Proyecto ejemplo.

hace 7 meses   •   4 min de lectura

Por Andrés Tuñón

Primero que todo, Rate Limiting son múltiples estrategias utilizadas por el API Gateway de terceros para evitar demasiadas llamadas en un muy corto lapso de tiempo.

Por ejemplo, imagina que dejas un servicio libre al Internet y un atacante le realiza 500 llamadas por segundo; se cae tu servicio, aumentan tus gastos y adiós clientes. Justamente estas estrategias buscan evitar esto.

¿Qué pasa con el cliente?

El cliente realiza múltiples llamadas, llega un momento en el que se excede el límite establecido por el servicio, el cliente recibe un mensaje de 429 devuelta y después de un tiempo vuelve a habilitarse el servicio (esperemos que sí).

Ejemplo gráfico
Ejemplo gráfico

Si necesito realizar múltiples llamadas a servicios protegidos, debo controlar la cantidad de llamadas realizadas de algún modo.

Usualmente el ratio de bloqueo es alto para que un usuario común lo exceda, pero muy bajo para que un bot sea detectado. Por ejemplo 20 llamadas por segundo, es complicado que un usuario ejecute esa cantidad de llamadas a propósito.

¿Pero que pasa en el caso de que necesite realizar muchas llamadas desde una aplicación?

Proyecto: My LoL Tier List

Este es un proyecto que utiliza un API para obtener las estadísticas de un jugador de League of Legends. En este proyecto tuve que realizar una llamada por cada partida emparejada, para obtener el registro de los campeones en cada uno.

Llamada al servicio de juegos para obtener el id de cada uno
Llamada al servicio de partidas emparejadas para obtener el id de cada uno

Cada vez que un jugador realiza la búsqueda, se consulta 50 veces a un servicio; es decir, facilmente me responde 429 si no coloco alguna especie de control.

¿Dónde realizo el control?

Encontré uno que otro artículo aplicando el control en el cliente y no entendí porqué. Si yo aplico algún control en un SPA, ¿Cómo evito que múltiples clientes bursteen el servicio que estoy consultando?

1. Control en el Cliente 2. Control en el servidor
1. Control en el Cliente 2. Control en el servidor

La única forma lógica que le halló para solventar este punto, es tener un servidor  que funcione como embudo y por esto apliqué Next.JS.

NextJS es un framework que soporta Server-Side Rendering (SSR) con un serverless function que se ejecuta antes de cargar la pantalla, brindando una pantalla con solo la respuesta necesaria.

De este modo cualquier jugador que realiza la búsqueda ejecutará el serverless function acoplado a la pantalla y pasará por el mismo embudo.

¿Cómo realizo el control?

Hay múltiples librerías que simplifican esta tarea e incluso tienen múltiples estrategias que satisfacen la mayoría de casos que conozco. Las librerías que probé fueron bottleneck y qottle, quedándome con el último.

No profundizaré mucho en los detalles; ya que, se pueden leer en la documentación de las librerías. Un resumen de las soluciones sería:

  • Llamadas en intervalos de tiempo
  • Alcanzar un máximo de llamadas y un periodo para restablecer el contador (la que apliqué)

Un problema con la infraestructura

Cómo utilicé un serverless function, no tenía forma de "persistir" la instancia de Qottle y cada vez que ejecutaba la llamada se volvía a reiniciar el contador; provocando un 429 por la desincronización.

Ya no me quedaba tanto tiempo disponible
Ya no me quedaba tanto tiempo disponible

En otras palabras, ¿Cómo sé que agoté las llamadas que puedo realizar en un tiempo determinado? Tal vez con la llamada en invervalos podía solventarlo, pero eso lo vuelve más lento.

A la final lo que hice fue persistir la fecha cuando realizaba la llamada en redis, cosa que se acerca a la solución de Almacenamiento en Caché que descarté por tratar de lograr todo gratuito. (Vamos que solo es un POC, no voy a gastar por algo que tal vez no se use, ni que mantendré)

A qué me refiero con "Almacenamiento en Caché"

Si ya realizaste una consulta, guardala en un componente de rápido acceso como un redis; para que siempre el usuario obtenga la llamada que realizo previamente, hasta que pueda sincronizar su información.

on cooldown
on cooldown

Mi solución cierra el circuito y le dice al usuario que se encuentra en enfriamiento el servicio. Hago referencia a este artículo que condensa lo que aprendí.

Conclusión

Realmente todo el proyecto es una gran conclusión, te dejo la página de proyecto aquí mismo en el blog, con el repositorio y la demo.

Te animo a que igual lo intentes, fue muy interesante; aunque terminé muy exhausto xD.

Referencias

My LoL Tier List
Proyecto que utiliza técnicas de resilencia para hacerle frente al Rate-Limit de servicios.
bottleneck
Distributed task scheduler and rate limiter
qottle
a queue to handle concurrency, async, rate limiting, duplication skipping
Rate-limiting strategies and techniques | Cloud Architecture Center
sharded-interval-queue
Sharded queue with job limit for running async functions on an interval

Corre la voz

Sigue leyendo