{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window","title":"Algoritmos de Rate Limiting: De Token Buckets a Scripts Lua Distribuidos","description":"Token bucket, sliding window log, GCRA, rate limiting distribuido con Redis, los algoritmos detrás de cada API gateway, con implementaciones reales en Lua listas para producción.","language":"es","datePublished":"2025-10-21","dateModified":null,"contentFormat":"text/markdown","content":"Backend\n\n# Algoritmos de Rate Limiting: De Token Buckets a Scripts Lua Distribuidos\n\n2025-10-215 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/rate-limiting-algorithms-token-bucket-sliding-window>)\n\n## Fixed window counter: el approach naive\n\nEl rate limiter más simple. Dividí el tiempo en ventanas fijas (ej: intervalos de 1 minuto), contá requests por ventana, rechazá cuando el conteo excede el límite.\n\n```python\nimport time, redis\nr = redis.Redis()\n\ndef fixed_window(key: str, limit: int, window_seconds: int) -> bool:\n window = int(time.time() / window_seconds)\n redis_key = f\"ratelimit:{key}:{window}\"\n current = r.incr(redis_key)\n if current == 1:\n r.expire(redis_key, window_seconds + 1)\n return current <= limit\n```\n\nEl problema: ráfaga en el borde. En el límite de dos ventanas, un cliente puede hacer ` 2 * limit ` requests en un lapso de ` window_seconds `.\n\n## Sliding window log: exacto pero caro\n\nManteners un sorted set de cada timestamp de request:\n\n```lua\n-- sliding_window_log.lua\nlocal key = KEYS[1]\nlocal now = tonumber(ARGV[1])\nlocal window = tonumber(ARGV[2])\nlocal limit = tonumber(ARGV[3])\n\nredis.call('ZREMRANGEBYSCORE', key, 0, now - window)\nlocal count = redis.call('ZCARD', key)\n\nif count < limit then\n redis.call('ZADD', key, now, now .. ':' .. math.random(1000000))\n redis.call('PEXPIRE', key, window)\n return {1, limit - count - 1}\nelse\n return {0, 0}\nend\n```\n\nConteo exacto, sin problema de ráfaga en el borde. Pero guarda cada timestamp de request. A 1.000 req/s con una ventana de 60 segundos, son 60.000 entradas por key.\n\n## Sliding window counter: el medio práctico\n\nCombiná dos ventanas fijas con conteo pesado:\n\n```lua\n-- sliding_window_counter.lua\nlocal key = KEYS[1]\nlocal limit = tonumber(ARGV[1])\nlocal window = tonumber(ARGV[2])\nlocal now = tonumber(ARGV[3])\n\nlocal current_window = math.floor(now / window)\nlocal previous_window = current_window - 1\nlocal elapsed = (now % window) / window\n\nlocal current_count = tonumber(redis.call('GET', key .. ':' .. current_window) or '0')\nlocal previous_count = tonumber(redis.call('GET', key .. ':' .. previous_window) or '0')\n\nlocal estimated = previous_count * (1 - elapsed) + current_count\n\nif estimated < limit then\n redis.call('INCR', key .. ':' .. current_window)\n redis.call('PEXPIRE', key .. ':' .. current_window, window * 2)\n return {1, math.floor(limit - estimated - 1)}\nelse\n return {0, 0}\nend\n```\n\n## Token bucket: permitiendo ráfagas controladas\n\n```lua\n-- token_bucket.lua\nlocal key = KEYS[1]\nlocal capacity = tonumber(ARGV[1])\nlocal rate = tonumber(ARGV[2])\nlocal now = tonumber(ARGV[3])\nlocal requested = tonumber(ARGV[4])\n\nlocal data = redis.call('HMGET', key, 'tokens', 'last_refill')\nlocal tokens = tonumber(data[1])\nlocal last_refill = tonumber(data[2])\n\nif tokens == nil then\n tokens = capacity\n last_refill = now\nend\n\nlocal elapsed = (now - last_refill) / 1000.0\ntokens = math.min(capacity, tokens + elapsed * rate)\n\nlocal allowed = 0\nif tokens >= requested then\n tokens = tokens - requested\n allowed = 1\nend\n\nredis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)\nredis.call('PEXPIRE', key, math.ceil(capacity / rate * 1000) + 1000)\nreturn {allowed, math.floor(tokens)}\n```\n\n## GCRA: el algoritmo de tasa de celdas\n\nGCRA viene del traffic shaping de redes ATM. En vez de trackear tokens, trackea cuándo debería permitirse el próximo request:\n\n```lua\n-- gcra.lua\nlocal key = KEYS[1]\nlocal emission_interval = tonumber(ARGV[1])\nlocal burst_offset = tonumber(ARGV[2])\nlocal now = tonumber(ARGV[3])\n\nlocal tat = tonumber(redis.call('GET', key))\n\nif tat == nil then\n tat = now + emission_interval\n redis.call('SET', key, tat)\n redis.call('PEXPIRE', key, burst_offset + emission_interval)\n return {1, 0}\nend\n\nlocal allow_at = tat - burst_offset\nif now < allow_at then\n return {0, allow_at - now}\nelse\n local new_tat = math.max(now, tat) + emission_interval\n redis.call('SET', key, new_tat)\n redis.call('PEXPIRE', key, burst_offset + emission_interval)\n return {1, 0}\nend\n```\n\nGCRA tiene propiedades hermosas: una sola key por cliente, operaciones O(1), sin ráfagas en los bordes, enforcement de tasa suave. Es lo que usa [Cloudflare](<https://cloudflare.com/>) internamente.\n\n## El race condition distribuido\n\nUn bug común y peligroso en rate limiting distribuido involucra un race condition:\n\n```python\n# ROTO: race condition entre check y decrement\ndef check_rate_limit(key, limit, window):\n count = redis.get(key) or 0\n if int(count) >= limit:\n return False\n # Entre este GET y el INCR, otros requests pueden pasar!\n redis.incr(key)\n return True\n```\n\nCon 100 requests concurrentes, todos leen count=0, todos pasan el check, todos incrementan. El límite no sirve para nada.\n\nLa solución: operaciones atómicas via scripts Lua. Cada script Lua en Redis se ejecuta atómicamente. Todas las implementaciones Lua de arriba son atómicas por diseño. Cargalas una vez con ` SCRIPT LOAD `, después llamá con ` EVALSHA `:\n\n```python\nclass DistributedRateLimiter:\n def __init__(self, redis_client):\n self.r = redis_client\n self._script_sha = None\n\n def _load_script(self):\n self._script_sha = self.r.script_load(GCRA_SCRIPT)\n\n def allow(self, key: str, rate: float = 100.0, burst: int = 10) -> tuple[bool, int]:\n if self._script_sha is None:\n self._load_script()\n emission_interval = int(1000 / rate)\n burst_offset = emission_interval * burst\n now_ms = int(time.time() * 1000)\n try:\n result = self.r.evalsha(self._script_sha, 1, key,\n emission_interval, burst_offset, now_ms)\n except redis.exceptions.NoScriptError:\n self._load_script()\n result = self.r.evalsha(self._script_sha, 1, key,\n emission_interval, burst_offset, now_ms)\n return bool(result[0]), int(result[1])\n```\n\n## Rate limiting multi-capa\n\nEn producción, un enfoque de tres capas funciona bien:\n\n1. **Edge (Cloudflare/nginx)**: Basado en IP, límites altos (1.000/min), atrapa DDoS y scrapers\n2. **API Gateway**: Basado en API key, límites por plan (free: 60/min, pro: 600/min)\n3. **Por endpoint**: Límites específicos para operaciones caras (search: 10/min, export: 5/min)\n\nCada capa corre independientemente con su propio namespace de keys de Redis. Un request tiene que pasar las tres para proceder.\n\nDespués de cambiar a scripts Lua atómicos, el rate limiter se vuelve a prueba de balas. Doce meses de uso en producción, cero incidentes, cero race conditions. La implementación son menos de 50 líneas de Lua. A veces el fix más simple es el mejor.\n\n## Preguntas frecuentes\n\n### ¿Qué algoritmo de rate limiting debería usar?\n\nPara la mayoría del rate limiting de APIs, GCRA o sliding window counter. Token bucket si se desea permitir ráfagas controladas. Fixed window solo para límites simples y no críticos.\n\n### ¿Cómo hago rate limiting en un sistema distribuido?\n\nUtilizar Redis con scripts Lua atómicos. Cada chequeo de rate limit debería ser una sola llamada EVALSHA que lea el estado actual, decida allow/deny, y actualice estado atómicamente.\n\n### ¿Debería limitar por IP o por API key?\n\nAmbos, en diferentes capas. Limitación por IP en el edge para DDoS y abuso no autenticado. Limitación por API key en la capa de aplicación para cuotas por cliente. Son complementarios.\n\n## Tips\n\n1. **Tip:** Utilizar GCRA sobre token bucket cuando necesités rate limiting suave. Token bucket permite ráfagas hasta el tamaño del bucket; GCRA espacia requests uniformemente, que es lo que realmente se desea para protección de APIs.\n2. **Tip:** Siempre incluir headers ` X-RateLimit-Limit `, ` X-RateLimit-Remaining `, y ` X-RateLimit-Reset ` en tus respuestas. Los clientes que los respetan reducen tu carga voluntariamente.\n3. **Tip:** En setups distribuidos, utilizar un solo ` EVALSHA ` de Redis para check-and-decrement. Dos comandos separados (GET y después SET) crean un race condition donde requests concurrentes pasan el check antes de que alguno decremente.\n4. **Tip:** Configurar el TTL de tu key de Redis a ` window_size + 1 segundo `, no exactamente ` window_size `. El clock skew entre servidores de aplicación y Redis puede causar que las keys expiren un request antes de tiempo.\n\n- rate-limiting\n- token-bucket\n- sliding-window\n- gcra\n- redis\n- lua\n- api-gateway\n- distributed-systems\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"Algoritmos de Rate Limiting: De Token Buckets a Scripts Lua Distribuidos","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#title"},{"level":2,"text":"Fixed window counter: el approach naive","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#fixed-window-counter-el-approach-naive"},{"level":2,"text":"Sliding window log: exacto pero caro","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#sliding-window-log-exacto-pero-caro"},{"level":2,"text":"Sliding window counter: el medio práctico","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#sliding-window-counter-el-medio-prctico"},{"level":2,"text":"Token bucket: permitiendo ráfagas controladas","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#token-bucket-permitiendo-rfagas-controladas"},{"level":2,"text":"GCRA: el algoritmo de tasa de celdas","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#gcra-el-algoritmo-de-tasa-de-celdas"},{"level":2,"text":"El race condition distribuido","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#el-race-condition-distribuido"},{"level":2,"text":"Rate limiting multi-capa","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#rate-limiting-multi-capa"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#questions"},{"level":3,"text":"¿Qué algoritmo de rate limiting debería usar?","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#question-1"},{"level":3,"text":"¿Cómo hago rate limiting en un sistema distribuido?","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#question-2"},{"level":3,"text":"¿Debería limitar por IP o por API key?","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#article-tips"}],"sections":[{"heading":"Algoritmos de Rate Limiting: De Token Buckets a Scripts Lua Distribuidos","level":1,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window","content":"Backend"},{"heading":"Algoritmos de Rate Limiting: De Token Buckets a Scripts Lua Distribuidos","level":1,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#title","content":"2025-10-215 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/rate-limiting-algorithms-token-bucket-sliding-window>)"},{"heading":"Fixed window counter: el approach naive","level":2,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#fixed-window-counter-el-approach-naive","content":"El rate limiter más simple. Dividí el tiempo en ventanas fijas (ej: intervalos de 1 minuto), contá requests por ventana, rechazá cuando el conteo excede el límite.\n\n```python\nimport time, redis\nr = redis.Redis()\n\ndef fixed_window(key: str, limit: int, window_seconds: int) -> bool:\n window = int(time.time() / window_seconds)\n redis_key = f\"ratelimit:{key}:{window}\"\n current = r.incr(redis_key)\n if current == 1:\n r.expire(redis_key, window_seconds + 1)\n return current <= limit\n```\n\nEl problema: ráfaga en el borde. En el límite de dos ventanas, un cliente puede hacer ` 2 * limit ` requests en un lapso de ` window_seconds `."},{"heading":"Sliding window log: exacto pero caro","level":2,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#sliding-window-log-exacto-pero-caro","content":"Manteners un sorted set de cada timestamp de request:\n\n```lua\n-- sliding_window_log.lua\nlocal key = KEYS[1]\nlocal now = tonumber(ARGV[1])\nlocal window = tonumber(ARGV[2])\nlocal limit = tonumber(ARGV[3])\n\nredis.call('ZREMRANGEBYSCORE', key, 0, now - window)\nlocal count = redis.call('ZCARD', key)\n\nif count < limit then\n redis.call('ZADD', key, now, now .. ':' .. math.random(1000000))\n redis.call('PEXPIRE', key, window)\n return {1, limit - count - 1}\nelse\n return {0, 0}\nend\n```\n\nConteo exacto, sin problema de ráfaga en el borde. Pero guarda cada timestamp de request. A 1.000 req/s con una ventana de 60 segundos, son 60.000 entradas por key."},{"heading":"Sliding window counter: el medio práctico","level":2,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#sliding-window-counter-el-medio-prctico","content":"Combiná dos ventanas fijas con conteo pesado:\n\n```lua\n-- sliding_window_counter.lua\nlocal key = KEYS[1]\nlocal limit = tonumber(ARGV[1])\nlocal window = tonumber(ARGV[2])\nlocal now = tonumber(ARGV[3])\n\nlocal current_window = math.floor(now / window)\nlocal previous_window = current_window - 1\nlocal elapsed = (now % window) / window\n\nlocal current_count = tonumber(redis.call('GET', key .. ':' .. current_window) or '0')\nlocal previous_count = tonumber(redis.call('GET', key .. ':' .. previous_window) or '0')\n\nlocal estimated = previous_count * (1 - elapsed) + current_count\n\nif estimated < limit then\n redis.call('INCR', key .. ':' .. current_window)\n redis.call('PEXPIRE', key .. ':' .. current_window, window * 2)\n return {1, math.floor(limit - estimated - 1)}\nelse\n return {0, 0}\nend\n```"},{"heading":"Token bucket: permitiendo ráfagas controladas","level":2,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#token-bucket-permitiendo-rfagas-controladas","content":"```lua\n-- token_bucket.lua\nlocal key = KEYS[1]\nlocal capacity = tonumber(ARGV[1])\nlocal rate = tonumber(ARGV[2])\nlocal now = tonumber(ARGV[3])\nlocal requested = tonumber(ARGV[4])\n\nlocal data = redis.call('HMGET', key, 'tokens', 'last_refill')\nlocal tokens = tonumber(data[1])\nlocal last_refill = tonumber(data[2])\n\nif tokens == nil then\n tokens = capacity\n last_refill = now\nend\n\nlocal elapsed = (now - last_refill) / 1000.0\ntokens = math.min(capacity, tokens + elapsed * rate)\n\nlocal allowed = 0\nif tokens >= requested then\n tokens = tokens - requested\n allowed = 1\nend\n\nredis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)\nredis.call('PEXPIRE', key, math.ceil(capacity / rate * 1000) + 1000)\nreturn {allowed, math.floor(tokens)}\n```"},{"heading":"GCRA: el algoritmo de tasa de celdas","level":2,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#gcra-el-algoritmo-de-tasa-de-celdas","content":"GCRA viene del traffic shaping de redes ATM. En vez de trackear tokens, trackea cuándo debería permitirse el próximo request:\n\n```lua\n-- gcra.lua\nlocal key = KEYS[1]\nlocal emission_interval = tonumber(ARGV[1])\nlocal burst_offset = tonumber(ARGV[2])\nlocal now = tonumber(ARGV[3])\n\nlocal tat = tonumber(redis.call('GET', key))\n\nif tat == nil then\n tat = now + emission_interval\n redis.call('SET', key, tat)\n redis.call('PEXPIRE', key, burst_offset + emission_interval)\n return {1, 0}\nend\n\nlocal allow_at = tat - burst_offset\nif now < allow_at then\n return {0, allow_at - now}\nelse\n local new_tat = math.max(now, tat) + emission_interval\n redis.call('SET', key, new_tat)\n redis.call('PEXPIRE', key, burst_offset + emission_interval)\n return {1, 0}\nend\n```\n\nGCRA tiene propiedades hermosas: una sola key por cliente, operaciones O(1), sin ráfagas en los bordes, enforcement de tasa suave. Es lo que usa [Cloudflare](<https://cloudflare.com/>) internamente."},{"heading":"El race condition distribuido","level":2,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#el-race-condition-distribuido","content":"Un bug común y peligroso en rate limiting distribuido involucra un race condition:\n\n```python\n# ROTO: race condition entre check y decrement\ndef check_rate_limit(key, limit, window):\n count = redis.get(key) or 0\n if int(count) >= limit:\n return False\n # Entre este GET y el INCR, otros requests pueden pasar!\n redis.incr(key)\n return True\n```\n\nCon 100 requests concurrentes, todos leen count=0, todos pasan el check, todos incrementan. El límite no sirve para nada.\n\nLa solución: operaciones atómicas via scripts Lua. Cada script Lua en Redis se ejecuta atómicamente. Todas las implementaciones Lua de arriba son atómicas por diseño. Cargalas una vez con ` SCRIPT LOAD `, después llamá con ` EVALSHA `:\n\n```python\nclass DistributedRateLimiter:\n def __init__(self, redis_client):\n self.r = redis_client\n self._script_sha = None\n\n def _load_script(self):\n self._script_sha = self.r.script_load(GCRA_SCRIPT)\n\n def allow(self, key: str, rate: float = 100.0, burst: int = 10) -> tuple[bool, int]:\n if self._script_sha is None:\n self._load_script()\n emission_interval = int(1000 / rate)\n burst_offset = emission_interval * burst\n now_ms = int(time.time() * 1000)\n try:\n result = self.r.evalsha(self._script_sha, 1, key,\n emission_interval, burst_offset, now_ms)\n except redis.exceptions.NoScriptError:\n self._load_script()\n result = self.r.evalsha(self._script_sha, 1, key,\n emission_interval, burst_offset, now_ms)\n return bool(result[0]), int(result[1])\n```"},{"heading":"Rate limiting multi-capa","level":2,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#rate-limiting-multi-capa","content":"En producción, un enfoque de tres capas funciona bien:\n\n1. **Edge (Cloudflare/nginx)**: Basado en IP, límites altos (1.000/min), atrapa DDoS y scrapers\n2. **API Gateway**: Basado en API key, límites por plan (free: 60/min, pro: 600/min)\n3. **Por endpoint**: Límites específicos para operaciones caras (search: 10/min, export: 5/min)\n\nCada capa corre independientemente con su propio namespace de keys de Redis. Un request tiene que pasar las tres para proceder.\n\nDespués de cambiar a scripts Lua atómicos, el rate limiter se vuelve a prueba de balas. Doce meses de uso en producción, cero incidentes, cero race conditions. La implementación son menos de 50 líneas de Lua. A veces el fix más simple es el mejor."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#questions","content":""},{"heading":"¿Qué algoritmo de rate limiting debería usar?","level":3,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#question-1","content":"Para la mayoría del rate limiting de APIs, GCRA o sliding window counter. Token bucket si se desea permitir ráfagas controladas. Fixed window solo para límites simples y no críticos."},{"heading":"¿Cómo hago rate limiting en un sistema distribuido?","level":3,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#question-2","content":"Utilizar Redis con scripts Lua atómicos. Cada chequeo de rate limit debería ser una sola llamada EVALSHA que lea el estado actual, decida allow/deny, y actualice estado atómicamente."},{"heading":"¿Debería limitar por IP o por API key?","level":3,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#question-3","content":"Ambos, en diferentes capas. Limitación por IP en el edge para DDoS y abuso no autenticado. Limitación por API key en la capa de aplicación para cuotas por cliente. Son complementarios."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#article-tips","content":"1. **Tip:** Utilizar GCRA sobre token bucket cuando necesités rate limiting suave. Token bucket permite ráfagas hasta el tamaño del bucket; GCRA espacia requests uniformemente, que es lo que realmente se desea para protección de APIs.\n2. **Tip:** Siempre incluir headers ` X-RateLimit-Limit `, ` X-RateLimit-Remaining `, y ` X-RateLimit-Reset ` en tus respuestas. Los clientes que los respetan reducen tu carga voluntariamente.\n3. **Tip:** En setups distribuidos, utilizar un solo ` EVALSHA ` de Redis para check-and-decrement. Dos comandos separados (GET y después SET) crean un race condition donde requests concurrentes pasan el check antes de que alguno decremente.\n4. **Tip:** Configurar el TTL de tu key de Redis a ` window_size + 1 segundo `, no exactamente ` window_size `. El clock skew entre servidores de aplicación y Redis puede causar que las keys expiren un request antes de tiempo.\n\n- rate-limiting\n- token-bucket\n- sliding-window\n- gcra\n- redis\n- lua\n- api-gateway\n- distributed-systems\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/rate-limiting-algorithms-token-bucket-sliding-window"},{"text":"Cloudflare","url":"https://cloudflare.com/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window"},"headline":"Algoritmos de Rate Limiting: De Token Buckets a Scripts Lua Distribuidos","description":"Token bucket, sliding window log, GCRA, rate limiting distribuido con Redis, los algoritmos detrás de cada API gateway, con implementaciones reales en Lua listas para producción.","datePublished":"2025-10-21","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://statics.lucioduran.com/images/lucioduran-social-card.jpg","url":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"Backend","keywords":"rate-limiting, token-bucket, sliding-window, gcra, redis, lua, api-gateway, distributed-systems","wordCount":849,"timeRequired":"PT5M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/rate-limiting-algorithms-token-bucket-sliding-window"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"Algoritmos de Rate Limiting: De Token Buckets a Scripts Lua Distribuidos","item":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Qué algoritmo de rate limiting debería usar?","acceptedAnswer":{"@type":"Answer","text":"Para la mayoría del rate limiting de APIs, GCRA o sliding window counter. Token bucket si se desea permitir ráfagas controladas. Fixed window solo para límites simples y no críticos."}},{"@type":"Question","name":"¿Cómo hago rate limiting en un sistema distribuido?","acceptedAnswer":{"@type":"Answer","text":"Utilizar Redis con scripts Lua atómicos. Cada chequeo de rate limit debería ser una sola llamada EVALSHA que lea el estado actual, decida allow/deny, y actualice estado atómicamente."}},{"@type":"Question","name":"¿Debería limitar por IP o por API key?","acceptedAnswer":{"@type":"Answer","text":"Ambos, en diferentes capas. Limitación por IP en el edge para DDoS y abuso no autenticado. Limitación por API key en la capa de aplicación para cuotas por cliente. Son complementarios."}}]}]}],"contentHash":"a7788b24de38eca4ac6d83c086dcbb2f0506fef0e77bb599241bd8a883f5459b","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/rate-limiting-algorithms-token-bucket-sliding-window","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":55352,"contentBytes":8361}}
