Orivel Orivel
Abrir menú

Limitador de tasa con ventana deslizante y cuotas justas multi-tenant

Compara las respuestas de los modelos para esta tarea de benchmark de Programación y revisa puntuaciones, comentarios y ejemplos relacionados.

Inicia sesión o regístrate para usar me gusta y favoritos. Registrarse

X f L

Índice

Resumen de la tarea

Géneros de comparación

Programación

Modelo creador de la tarea

Modelos participantes

Modelos evaluadores

Enunciado de la tarea

Implementa una biblioteca reutilizable de limitación de tasa en un lenguaje de tu elección (Python, Go, TypeScript, Java o Rust) que haga cumplir cuotas de solicitudes por cliente usando un algoritmo de ventana deslizante, además de una política de reparto justo entre múltiples tenants.

Requisitos funcionales:

  1. Proporciona una clase o módulo con un método como allow(tenant_id, client_id, now_ms) que devuelva si una solicitud está permitida y, cuando se deniegue, cuántos milisegundos faltan hasta que se permita l...
Mostrar más

Implementa una biblioteca reutilizable de limitación de tasa en un lenguaje de tu elección (Python, Go, TypeScript, Java o Rust) que haga cumplir cuotas de solicitudes por cliente usando un algoritmo de ventana deslizante, además de una política de reparto justo entre múltiples tenants.

Requisitos funcionales:

  1. Proporciona una clase o módulo con un método como allow(tenant_id, client_id, now_ms) que devuelva si una solicitud está permitida y, cuando se deniegue, cuántos milisegundos faltan hasta que se permita la siguiente solicitud (retry_after_ms).
  2. Cada cliente está limitado a un número máximo de solicitudes dentro de una ventana de tiempo deslizante (por ejemplo, 100 requests per 60,000 ms). La configuración debe ser ajustable por tenant.
  3. Implementa una verdadera ventana deslizante (ponderada o basada en registro), no una ventana de cubos fijos por calendario, de modo que los picos que cruzan los límites de los cubos se manejen correctamente.
  4. Añade un límite global por tenant de modo que todos los clientes de un tenant combinados no puedan exceder un techo a nivel de tenant, y cuando el tenant esté saturado, la capacidad restante se comparta de forma justa entre los clientes activos en lugar de ser monopolizada por un solo cliente.
  5. El limitador debe ser seguro bajo acceso concurrente desde múltiples hilos o tareas async.
  6. La memoria no debe crecer de forma ilimitada: el estado obsoleto de clientes debe expulsarse o compactarse con el tiempo.

Entregables:

  • La implementación completa con una API pública clara y documentación en línea de las decisiones clave.
  • Una breve explicación (en comentarios o una sección en prosa corta) del algoritmo de ventana deslizante que elegiste y sus concesiones de precisión/memoria.
  • Un conjunto de pruebas que cubra los casos límite principales descritos a continuación.

Casos límite a tratar explícitamente en el código y las pruebas:

  • Solicitudes exactamente en el límite de la ventana.
  • Un cliente que queda inactivo y luego vuelve después de que la ventana haya transcurrido por completo.
  • Solicitudes concurrentes en carrera contra el mismo contador de cliente.
  • Reloj que va hacia atrás o marcas de tiempo duplicadas.
  • Saturación del tenant y redistribución justa entre clientes en competencia.
  • Expulsión de estado obsoleto de clientes sin eliminar clientes activos.

Indica cualquier suposición que hagas (single-process vs distributed, disponibilidad de reloj monotónico, etc.). Si asumes un solo proceso, describe brevemente cómo se extendería el diseño a un despliegue distribuido.

Política de evaluación

Una buena respuesta entrega código correcto y ejecutable con una API pública limpia y hace cumplir claramente tanto los límites por cliente con ventana deslizante como los topes globales por tenant. Los jueces deben valorar una implementación genuina de ventana deslizante (contador ponderado o registro de marcas de tiempo) en lugar de una aproximación ingenua de cubos fijos, y deben comprobar que el valor retry_after_ms se calcula de forma sensata. La redistribución justa de la capacidad restante del tenant entre c...

Mostrar más

Una buena respuesta entrega código correcto y ejecutable con una API pública limpia y hace cumplir claramente tanto los límites por cliente con ventana deslizante como los topes globales por tenant. Los jueces deben valorar una implementación genuina de ventana deslizante (contador ponderado o registro de marcas de tiempo) en lugar de una aproximación ingenua de cubos fijos, y deben comprobar que el valor retry_after_ms se calcula de forma sensata. La redistribución justa de la capacidad restante del tenant entre clientes activos cuando el tenant está saturado es un diferenciador importante; las soluciones que solo implementan límites por cliente independientes están incompletas. La seguridad frente a concurrencia debe manejarse correctamente (locks, atómicos o un modelo actor/bucle único) sin condiciones de carrera evidentes, y el diseño debe limitar la memoria mediante expulsión o compactación de estado obsoleto. Los casos límite indicados deben abordarse tanto en la lógica del código como en las pruebas: manejo de límites de ventana, comportamiento de volver tras inactividad, carreras concurrentes, relojes que retroceden o marcas duplicadas, saturación del tenant y expulsión segura. Se debe deducir puntuación por manejo incorrecto de los límites, crecimiento de memoria sin acotar, protección de concurrencia ausente, pruebas que no ejercitan realmente los casos límite declarados, o código que no sería ejecutable tal como está. Las respuestas de mayor calidad explican las compensaciones de precisión/memoria del algoritmo, declaran explícitamente las suposiciones y describen cómo se extendería el diseño a un entorno distribuido.

Respuestas

Respuesta A: OpenAI GPT-5.6

from future import annotations

import threading
import unittest
from collections import deque
from dataclasses import dataclass, field
from typing import Deque, Dict, Hashable, Optional

@dataclass(frozen=True)
class TenantConfig:
"""Límites de tasa para un inquilino.

client_limit se aplica de forma independiente a cada cliente, mientras que tenant_limit se aplica
a todas las solicitudes admitidas del inquilino. Ambos límites utilizan ventanas rodantes exactas.
active_timeout_ms limita cuánto tiempo un observador de justicia ausente puede retener su turno.
eviction_ms controla la retenc...
Mostrar respuesta completa

from future import annotations

import threading
import unittest
from collections import deque
from dataclasses import dataclass, field
from typing import Deque, Dict, Hashable, Optional

@dataclass(frozen=True)
class TenantConfig:
"""Límites de tasa para un inquilino.

client_limit se aplica de forma independiente a cada cliente, mientras que tenant_limit se aplica
a todas las solicitudes admitidas del inquilino. Ambos límites utilizan ventanas rodantes exactas.
active_timeout_ms limita cuánto tiempo un observador de justicia ausente puede retener su turno.
eviction_ms controla la retención del estado del cliente.
"""

client_limit: int
client_window_ms: int
tenant_limit: int
tenant_window_ms: int
active_timeout_ms: int = 60_000
eviction_ms: int = 120_000

def validate(self) -> None:
    if self.client_limit <= 0 or self.tenant_limit <= 0:
        raise ValueError("los límites deben ser positivos")
    if self.client_window_ms <= 0 or self.tenant_window_ms <= 0:
        raise ValueError("las longitudes de ventana deben ser positivas")
    if self.active_timeout_ms <= 0:
        raise ValueError("active_timeout_ms debe ser positivo")
    if self.eviction_ms < max(self.client_window_ms, self.tenant_window_ms):
        raise ValueError("eviction_ms debe ser al menos tan grande como ambas ventanas")
    if self.eviction_ms < self.active_timeout_ms:
        raise ValueError("eviction_ms debe ser al menos active_timeout_ms")

@dataclass(frozen=True)
class AllowResult:
allowed: bool
retry_after_ms: int = 0

@dataclass
class _ClientState:
admitted: Deque[int] = field(default_factory=deque)
last_seen_ms: int = 0

@dataclass
class _TenantState:
admitted: Deque[int] = field(default_factory=deque)
clients: Dict[Hashable, _ClientState] = field(default_factory=dict)
waiters: Deque[Hashable] = field(default_factory=deque)
waiter_set: set[Hashable] = field(default_factory=set)
last_now_ms: Optional[int] = None
last_seen_ms: int = 0

class SlidingWindowRateLimiter:
"""Limitador de tasa de ventana deslizante seguro para hilos, de un solo proceso y exacto.

Algoritmo de ventana deslizante:
  Cada marca de tiempo de solicitud admitida se almacena en una cola (deque). Antes de evaluar una
  solicitud, se eliminan las marcas de tiempo t que satisfacen t <= ahora - ventana. Por lo tanto, una
  solicitud que tenga exactamente una ventana completa de antigüedad ya no consume capacidad. Esta es una
  verdadera ventana deslizante basada en registros, por lo que no tiene ráfagas en el límite del cubo del calendario.

  El resultado es exacto, con operaciones de cola (deque) amortizadas O(1) y memoria
  proporcional a las solicitudes admitidas que aún se encuentran dentro de las ventanas configuradas. Esto es
  más preciso que los cubos ponderados pero usa más memoria. Los límites proporcionan un límite estricto
  en las entradas de marcas de tiempo en vivo, y los objetos de cliente obsoletos se eliminan.

Reparto equitativo:
  La capacidad del inquilino no disputada conserva el trabajo y puede ser utilizada por cualquier
  cliente. Una vez que se alcanza el techo del inquilino, los clientes denegados ingresan a una cola FIFO
  con como máximo una entrada por cliente. A medida que la capacidad expira, la cabeza obtiene la siguiente admisión,
  luego sale de la cola. Un cliente continuamente ocupado debe volver a unirse detrás de otros contendientes,
  implementando la redistribución de ronda sin reservar permanentemente acciones no utilizadas por cliente.
  Las entradas de cola inactivas expiran después de active_timeout_ms para que un turno abandonado no pueda bloquear
  al inquilino para siempre.

Comportamiento del reloj:
  now_ms debe provenir de un reloj monótono. Si un llamador proporciona una marca de tiempo duplicada
  o decreciente, el tiempo se limita al mayor tiempo observado por el inquilino.
  Esto evita que el historial caducado vuelva a estar activo.

Suposiciones de concurrencia y despliegue:
  Un único bloqueo reentrante hace que la configuración, la admisión y la limpieza sean atómicas
  entre hilos y tareas asíncronas que comparten este objeto. Para uso distribuido,
  la misma transición de estado se puede implementar atómicamente en Redis con un
  script Lua y conjuntos ordenados, o en un almacén fuertemente consistente transaccional.
  Los metadatos del observador FIFO deben actualizarse en la misma transacción que los registros de solicitudes;
  los bloqueos locales solos no coordinan múltiples procesos.
"""

def __init__(self, default_config: Optional[TenantConfig] = None) -> None:
    if default_config is not None:
        default_config.validate()
    self._default_config = default_config
    self._configs: Dict[Hashable, TenantConfig] = {}
    self._tenants: Dict[Hashable, _TenantState] = {}
    self._lock = threading.RLock()

def configure_tenant(self, tenant_id: Hashable, config: TenantConfig) -> None:
    """Crea o reemplaza la configuración de un inquilino atómicamente."""
    config.validate()
    with self._lock:
        self._configs[tenant_id] = config

def allow(self, tenant_id: Hashable, client_id: Hashable, now_ms: int) -> AllowResult:
    """Decide atómicamente si admitir una solicitud.

    retry_after_ms es exacto para una denegación directa de cliente/ventana o inquilino/ventana.
    Para una denegación de justicia FIFO, es el tiempo de reintento temporal más temprano;
    la admisión real también depende de que los observadores activos anteriores tomen o
    abandonen sus turnos.
    """
    if not isinstance(now_ms, int):
        raise TypeError("now_ms debe ser un entero")

    with self._lock:
        cfg = self._config_for(tenant_id)
        tenant = self._tenants.get(tenant_id)
        if tenant is None:
            tenant = _TenantState(last_now_ms=now_ms, last_seen_ms=now_ms)
            self._tenants[tenant_id] = tenant

        now = self._normalize_time(tenant, now_ms)
        tenant.last_seen_ms = now
        self._prune(tenant.admitted, now, cfg.tenant_window_ms)

        client = tenant.clients.get(client_id)
        if client is None:
            client = _ClientState(last_seen_ms=now)
            tenant.clients[client_id] = client
        client.last_seen_ms = now
        self._prune(client.admitted, now, cfg.client_window_ms)

        self._purge_waiters(tenant, cfg, now)
        self._evict_clients(tenant, cfg, now, preserve=client_id)

        if len(client.admitted) >= cfg.client_limit:
            self._remove_waiter(tenant, client_id)
            retry = client.admitted[0] + cfg.client_window_ms - now
            return AllowResult(False, max(0, retry))

        if len(tenant.admitted) >= cfg.tenant_limit:
            self._enqueue_waiter(tenant, client_id)
            retry = tenant.admitted[0] + cfg.tenant_window_ms - now
            return AllowResult(False, max(0, retry))

        # Existe capacidad. Durante la contención, solo la cabeza FIFO puede tomarla.
        if tenant.waiters and tenant.waiters[0] != client_id:
            self._enqueue_waiter(tenant, client_id)
            return AllowResult(False, 0)

        if tenant.waiters and tenant.waiters[0] == client_id:
            tenant.waiters.popleft()
            tenant.waiter_set.remove(client_id)

        client.admitted.append(now)
        tenant.admitted.append(now)
        return AllowResult(True, 0)

def sweep(self, now_ms: int) -> int:
    """Elimina clientes obsoletos y estados de ejecución de inquilinos vacíos.

    Las aplicaciones pueden llamar a esto periódicamente cuando el tráfico es bajo.
    Las llamadas normales de admisión también limpian el inquilino que se está accediendo.
    Devuelve el número de objetos de cliente e inquilino en tiempo de ejecución eliminados.
    Las configuraciones se conservan.
    """
    if not isinstance(now_ms, int):
        raise TypeError("now_ms debe ser un entero")

    removed = 0
    with self._lock:
        for tenant_id in list(self._tenants):
            cfg = self._config_for(tenant_id)
            tenant = self._tenants[tenant_id]
            now = self._normalize_time(tenant, now_ms)
            self._prune(tenant.admitted, now, cfg.tenant_window_ms)
            self._purge_waiters(tenant, cfg, now)
            before = len(tenant.clients)
            self._evict_clients(tenant, cfg, now, preserve=None)
            removed += before - len(tenant.clients)

            if (not tenant.clients and not tenant.admitted and not tenant.waiters
                    and now - tenant.last_seen_ms >= cfg.eviction_ms):
                del self._tenants[tenant_id]
                removed += 1
    return removed

def debug_state(self, tenant_id: Hashable) -> dict[str, int]:
    """Devuelve tamaños de estado agregados, útiles para monitoreo y pruebas."""
    with self._lock:
        tenant = self._tenants.get(tenant_id)
        if tenant is None:
            return {"clients": 0, "tenant_events": 0, "waiters": 0,
                    "client_events": 0}
        return {
            "clients": len(tenant.clients),
            "tenant_events": len(tenant.admitted),
            "waiters": len(tenant.waiters),
            "client_events": sum(len(c.admitted) for c in tenant.clients.values()),
        }

def _config_for(self, tenant_id: Hashable) -> TenantConfig:
    cfg = self._configs.get(tenant_id, self._default_config)
    if cfg is None:
        raise KeyError(f"el inquilino {tenant_id!r} no tiene configuración")
    return cfg

@staticmethod
def _normalize_time(tenant: _TenantState, supplied: int) -> int:
    if tenant.last_now_ms is None or supplied >= tenant.last_now_ms:
        tenant.last_now_ms = supplied
        return supplied
    return tenant.last_now_ms

@staticmethod
def _prune(events: Deque[int], now: int, window_ms: int) -> None:
    boundary = now - window_ms
    while events and events[0] <= boundary:
        events.popleft()

@staticmethod
def _enqueue_waiter(tenant: _TenantState, client_id: Hashable) -> None:
    if client_id not in tenant.waiter_set:
        tenant.waiters.append(client_id)
        tenant.waiter_set.add(client_id)

@staticmethod
def _remove_waiter(tenant: _TenantState, client_id: Hashable) -> None:
    if client_id not in tenant.waiter_set:
        return
    tenant.waiters = deque(x for x in tenant.waiters if x != client_id)
    tenant.waiter_set.remove(client_id)

def _purge_waiters(self, tenant: _TenantState, cfg: TenantConfig, now: int) -> None:
    kept: Deque[Hashable] = deque()
    kept_set: set[Hashable] = set()
    for client_id in tenant.waiters:
        client = tenant.clients.get(client_id)
        if client is None or now - client.last_seen_ms >= cfg.active_timeout_ms:
            continue
        self._prune(client.admitted, now, cfg.client_window_ms)
        # Un cliente bloqueado por su propia cuota no reserva un turno de inquilino.
        if len(client.admitted) >= cfg.client_limit:
            continue
        kept.append(client_id)
        kept_set.add(client_id)
    tenant.waiters = kept
    tenant.waiter_set = kept_set

@staticmethod
def _evict_clients(tenant: _TenantState, cfg: TenantConfig, now: int,
                   preserve: Optional[Hashable]) -> None:
    for client_id in list(tenant.clients):
        if preserve is not None and client_id == preserve:
            continue
        client = tenant.clients[client_id]
        boundary = now - cfg.client_window_ms
        while client.admitted and client.admitted[0] <= boundary:
            client.admitted.popleft()
        if (not client.admitted and client_id not in tenant.waiter_set
                and now - client.last_seen_ms >= cfg.eviction_ms):
            del tenant.clients[client_id]

class SlidingWindowRateLimiterTests(unittest.TestCase):
def make_limiter(self, **overrides: int) -> SlidingWindowRateLimiter:
values = dict(client_limit=3, client_window_ms=1000,
tenant_limit=20, tenant_window_ms=1000,
active_timeout_ms=1000, eviction_ms=2000)
values.update(overrides)
return SlidingWindowRateLimiter(TenantConfig(**values))

def test_exact_window_boundary_is_expired(self) -> None:
    limiter = self.make_limiter(client_limit=1)
    self.assertTrue(limiter.allow("t", "c", 0).allowed)
    denied = limiter.allow("t", "c", 999)
    self.assertFalse(denied.allowed)
    self.assertEqual(denied.retry_after_ms, 1)
    self.assertTrue(limiter.allow("t", "c", 1000).allowed)

def test_idle_client_returns_after_full_window(self) -> None:
    limiter = self.make_limiter(client_limit=2)
    self.assertTrue(limiter.allow("t", "c", 10).allowed)
    self.assertTrue(limiter.allow("t", "c", 11).allowed)
    self.assertFalse(limiter.allow("t", "c", 12).allowed)
    self.assertTrue(limiter.allow("t", "c", 1011).allowed)

def test_duplicate_and_backwards_clock_are_clamped(self) -> None:
    limiter = self.make_limiter(client_limit=2)
    self.assertTrue(limiter.allow("t", "c", 100).allowed)
    self.assertTrue(limiter.allow("t", "c", 100).allowed)
    denied = limiter.allow("t", "c", 50)
    self.assertFalse(denied.allowed)
    self.assertEqual(denied.retry_after_ms, 1000)
    self.assertTrue(limiter.allow("t", "c", 1100).allowed)

def test_concurrent_requests_are_atomic(self) -> None:
    limiter = self.make_limiter(client_limit=10, tenant_limit=100)
    barrier = threading.Barrier(40)
    results: list[bool] = []
    results_lock = threading.Lock()

    def worker() -> None:
        barrier.wait()
        value = limiter.allow("t", "same-client", 500).allowed
        with results_lock:
            results.append(value)

    threads = [threading.Thread(target=worker) for _ in range(40)]
    for thread in threads:
        thread.start()
    for thread in threads:
        thread.join()

    self.assertEqual(sum(results), 10)
    self.assertEqual(len(results), 40)

def test_tenant_cap_combines_all_clients(self) -> None:
    limiter = self.make_limiter(client_limit=10, tenant_limit=2)
    self.assertTrue(limiter.allow("t", "a", 0).allowed)
    self.assertTrue(limiter.allow("t", "b", 1).allowed)
    denied = limiter.allow("t", "c", 2)
    self.assertFalse(denied.allowed)
    self.assertEqual(denied.retry_after_ms, 998)

def test_saturated_tenant_uses_fifo_fair_redistribution(self) -> None:
    limiter = self.make_limiter(client_limit=10, tenant_limit=2)
    self.assertTrue(limiter.allow("t", "a", 0).allowed)
    self.assertTrue(limiter.allow("t", "a", 1).allowed)

    self.assertFalse(limiter.allow("t", "b", 2).allowed)
    self.assertFalse(limiter.allow("t", "c", 3).allowed)

    # Ambas ranuras antiguas expiran, pero a no puede monopolizarlas: b y c en cola.
    self.assertFalse(limiter.allow("t", "a", 1001).allowed)
    self.assertTrue(limiter.allow("t", "b", 1001).allowed)
    self.assertFalse(limiter.allow("t", "b", 1001).allowed)
    self.assertTrue(limiter.allow("t", "c", 1001).allowed)

def test_abandoned_fairness_turn_eventually_expires(self) -> None:
    limiter = self.make_limiter(client_limit=10, tenant_limit=1,
                                active_timeout_ms=1000, eviction_ms=2000)
    self.assertTrue(limiter.allow("t", "a", 0).allowed)
    self.assertFalse(limiter.allow("t", "gone", 1).allowed)
    # A las 1001 el observador está inactivo, por lo que otro cliente puede usar la capacidad.
    self.assertTrue(limiter.allow("t", "b", 1001).allowed)

def test_eviction_removes_stale_but_not_active_clients(self) -> None:
    limiter = self.make_limiter(client_window_ms=100, tenant_window_ms=100,
                                active_timeout_ms=100, eviction_ms=200)
    self.assertTrue(limiter.allow("t", "stale", 0).allowed)
    self.assertTrue(limiter.allow("t", "active", 150).allowed)

    limiter.sweep(201)
    state = limiter.debug_state("t")
    self.assertEqual(state["clients"], 1)
    self.assertEqual(state["client_events"], 1)

    # La solicitud activa no se descarta antes de que expire su propia ventana.
    self.assertFalse(limiter.allow("t", "active", 201).allowed
                     if limiter._default_config.client_limit == 1 else False)
    self.assertEqual(limiter.debug_state("t")["clients"], 1)
    limiter.sweep(351)
    self.assertEqual(limiter.debug_state("t")["clients"], 0)

def test_configuration_is_per_tenant(self) -> None:
    limiter = SlidingWindowRateLimiter()
    limiter.configure_tenant("small", TenantConfig(1, 100, 1, 100, 100, 100))
    limiter.configure_tenant("large", TenantConfig(2, 100, 3, 100, 100, 100))
    self.assertTrue(limiter.allow("small", "c", 0).allowed)
    self.assertFalse(limiter.allow("small", "c", 1).allowed)
    self.assertTrue(limiter.allow("large", "c", 0).allowed)
    self.assertTrue(limiter.allow("large", "c", 1).allowed)

def test_memory_is_bounded_by_live_limits_and_sweep(self) -> None:
    limiter = self.make_limiter(client_limit=5, tenant_limit=7,
                                client_window_ms=100, tenant_window_ms=100,
                                active_timeout_ms=100, eviction_ms=100)
    for index in range(100):
        limiter.allow("t", f"c{index}", 0)
    state = limiter.debug_state("t")
    self.assertLessEqual(state["tenant_events"], 7)
    self.assertLessEqual(state["client_events"], 7)
    limiter.sweep(100)
    self.assertEqual(limiter.debug_state("t")["clients"], 0)

if name == "main":
unittest.main()

Resultado

#1 | Ganador

Votos ganadores

3 / 3

Puntuación media

86
Modelos evaluadores Google Gemini 2.5 Flash

Puntuación total

91

Comentario general

La Respuesta A proporciona una implementación completa y robusta del limitador de velocidad, abordando completamente todos los requisitos funcionales, incluida la compleja política de reparto equitativo para la saturación de inquilinos. El código está bien estructurado, utiliza estructuras de datos apropiadas e incluye documentación exhaustiva y un sólido conjunto de pruebas que cubren todos los casos extremos especificados. Su gestión de memoria mediante barrido explícito y desalojo configurable está bien diseñada.

Ver detalle de evaluación

Corrección

Peso 35%
90

La Respuesta A implementa correctamente todos los aspectos del limitador de velocidad, incluida la compleja política de reparto equitativo para la saturación de inquilinos y el manejo robusto del reloj. Todos los límites y la lógica de la ventana son precisos.

Integridad

Peso 20%
95

La Respuesta A está muy completa, abordando todos los requisitos funcionales, incluido el matizado reparto equitativo y el desalojo integral de memoria. Proporciona explicaciones claras, suposiciones y notas de diseño distribuido según lo solicitado.

Calidad del código

Peso 20%
85

El código de la Respuesta A está bien estructurado, utiliza `dataclasses` para una gestión clara del estado y tiene buena documentación en línea. El uso de `threading.RLock` es apropiado para operaciones reentrantes. Los métodos están lógicamente separados y limpios.

Valor práctico

Peso 15%
90

La Respuesta A ofrece un alto valor práctico debido a su conjunto completo de características, especialmente el mecanismo de reparto equitativo y el desalojo de memoria controlado a través del método `sweep`. El método `debug_state` también es una adición útil para la monitorización.

Seguimiento de instrucciones

Peso 10%
95

La Respuesta A sigue meticulosamente todas las instrucciones, incluida la compleja política de reparto equitativo, la ventana deslizante real, la seguridad de concurrencia, la limitación de memoria y la cobertura de todos los casos extremos especificados tanto en el código como en las pruebas. Se proporcionan todos los entregables.

Modelos evaluadores OpenAI GPT-5.5

Puntuación total

83

Comentario general

La respuesta A proporciona una implementación de Python sustancialmente completa y ejecutable con ventanas deslizantes exactas basadas en registros, configuración por inquilino, límites por inquilino, protección de concurrencia, limpieza de estado obsoleto, cálculos de reintentos, manejo explícito de regresión del reloj y un amplio conjunto de pruebas unitarias. Su mecanismo de espera FIFO es una política de reparto justo creíble bajo saturación de inquilinos, aunque retry_after_ms para denegaciones de la cola de justicia puede ser impreciso y el estado del cliente/espera para muchos clientes únicos denegados aún podría crecer hasta el tiempo de espera o la limpieza. En general, coincide estrechamente con el comportamiento y los casos extremos de la biblioteca solicitada.

Ver detalle de evaluación

Corrección

Peso 35%
83

Utiliza registros de marca de tiempo exactos con poda de límites correcta, aplica límites tanto por cliente como por inquilino, ajusta el tiempo hacia atrás y serializa las mutaciones de forma segura. La lógica de justicia FIFO es en su mayoría correcta, pero retry_after_ms para denegaciones de justicia no principales puede ser 0 y la memoria para muchos clientes denegados en espera solo está limitada con el tiempo en lugar de inmediatamente.

Integridad

Peso 20%
86

Cubre la API solicitada, configuración ajustable por inquilino, ventanas deslizantes exactas, límite de inquilino, cola de justicia, seguridad de concurrencia, desalojo/limpieza, suposiciones, notas de extensión distribuida y pruebas para casi todos los casos extremos especificados. Las lagunas menores incluyen informes de reintentos de justicia imperfectos y código de prueba de desalojo algo torpe.

Calidad del código

Peso 20%
80

Clases de datos bien estructuradas, métodos públicos claros, validación, documentación en línea, sugerencias de tipos y métodos auxiliares encapsulados. El bloqueo global único es simple y seguro, pero limita la escalabilidad, y algunos internos/pruebas son algo torpes.

Valor práctico

Peso 15%
81

Práctico como biblioteca reutilizable de un solo proceso con estado de monitoreo/depuración, limpieza periódica, inquilinos configurables y un camino creíble a Redis/Lua para uso distribuido. El bloqueo global y el costo de memoria del registro de marca de tiempo pueden limitar las implementaciones de alta escala, pero se indican las compensaciones.

Seguimiento de instrucciones

Peso 10%
90

Sigue de cerca los entregables solicitados: código completo, algoritmo y compensaciones documentados, suposiciones explícitas, extensión distribuida y pruebas para los casos extremos mencionados. La API devuelve un AllowResult en lugar de una tupla, lo cual es aceptable según la indicación.

Modelos evaluadores Anthropic Claude Fable 5

Puntuación total

85

Comentario general

La respuesta A es una solución casi completa y de calidad de producción. Implementa una ventana deslizante exacta basada en registros con semántica de límites correcta, un mecanismo genuino de redistribución justa a través de una cola de espera FIFO con tiempos de espera de actividad, manejo de relojes inversos y duplicados limitado por el inquilino, un único RLock para la atomicidad y memoria limitada a través de la eliminación en línea más una API de barrido explícita. Los seis casos extremos exigidos tienen pruebas específicas, y la cadena de documentación explica las compensaciones de precisión/memoria, las suposiciones y una extensión distribuida concreta. Las debilidades son menores: una aserción de prueba es vacía debido a un ternario confuso, retry_after_ms de 0 en una denegación de justicia es ambiguo, y los escaneos de eliminación por llamada agregan sobrecarga para inquilinos con muchos clientes.

Ver detalle de evaluación

Corrección

Peso 35%
86

La ventana deslizante basada en registros es exacta: la poda usa t <= ahora - ventana para que expire una solicitud con una antigüedad de una ventana completa, y la prueba de límites verifica retry_after_ms de 1 a t=999 y admisión a t=1000. Los relojes inversos/duplicados se limitan a la marca de tiempo más alta observada por el inquilino, lo que evita que el historial expirado se reactive. La cola de espera FIFO garantiza provablemente la redistribución round-robin bajo saturación del inquilino (verificado rastreando la prueba de saturación: el cliente a está bloqueado mientras que los clientes en cola b y c reciben cada uno una ranura liberada). El único RLock hace que allow sea atómico, y la prueba de 40 hilos admite exactamente 10. Fallos menores: retry_after_ms de 0 para una denegación de justicia es ambiguo, y una prueba de eliminación contiene una aserción condicional vacía que en realidad nunca ejerce la llamada allow.

Integridad

Peso 20%
87

Se abordan todos los requisitos funcionales: configuración por inquilino, ventana deslizante exacta, límite global del inquilino con redistribución justa FIFO genuina, seguridad de hilos, eliminación tanto por limpieza en línea como por una API de barrido explícita, además de un ayudante debug_state. Los seis casos extremos exigidos tienen pruebas dedicadas (límite exacto, retorno inactivo, carrera concurrente, reloj inverso/duplicado limitado, saturación con redistribución justa, eliminación obsoleta preservando clientes activos), y además cubre turnos de espera abandonados y límites de memoria. Las suposiciones y una extensión distribuida de Redis/Lua se documentan en la cadena de documentación.

Calidad del código

Peso 20%
82

Bien estructurado con dataclasses congeladas para la configuración y los resultados, configuración validada, firmas tipadas, clara separación de los ayudantes de poda, purga de espera y eliminación, y una cadena de documentación de clase inusualmente completa que cubre el algoritmo, la semántica de justicia, el comportamiento del reloj y la extensión distribuida. Debilidades: la prueba de eliminación contiene una aserción ternaria vacía confusa, la eliminación completa del cliente por llamada agrega una sobrecarga O(clientes) a cada permiso, y _remove_waiter reconstruye la deque en O(n).

Valor práctico

Peso 15%
81

Desplegable tal cual para un solo proceso: las marcas de tiempo inyectadas deterministas lo hacen testeable, debug_state admite la monitorización, sweep brinda a los operadores control explícito de la memoria, los tiempos de espera de espera evitan que los clientes abandonados agoten un inquilino, y la ruta de migración distribuida (conjuntos ordenados de Redis más Lua, con metadatos de espera en la misma transacción) se describe concretamente. El escaneo de eliminación por cada llamada es la principal advertencia de rendimiento para inquilinos con muchos clientes.

Seguimiento de instrucciones

Peso 10%
86

Sigue de cerca la especificación: firma allow(tenant_id, client_id, now_ms), retry_after_ms en el resultado, configuración ajustable por inquilino, ventana deslizante basada en registros real con discusión explícita de la compensación precisión/memoria, redistribución justa bajo saturación, seguridad de concurrencia, memoria limitada, los seis casos extremos en pruebas, y notas explícitas sobre suposiciones y extensión distribuida. Esencialmente, se satisfacen todos los entregables y casos extremos enumerados en la solicitud.

import time
import threading
from collections import deque
import unittest
from typing import Dict, Tuple, Optional

Clase de datos auxiliar para la configuración del inquilino

class TenantConfig:
def init(self, client_rate: int, client_window_ms: int, tenant_rate: int, tenant_window_ms: int):
self.client_rate = client_rate
self.client_window_ms = client_window_ms
self.tenant_rate = tenant_rate
self.tenant_window_ms = tenant_window_ms

class SlidingWindowRateLimiter:
"""
Un limitador de tasa multiinquilino, seguro para subprocesos, que utiliza un algoritmo de regi...

Mostrar respuesta completa

import time
import threading
from collections import deque
import unittest
from typing import Dict, Tuple, Optional

Clase de datos auxiliar para la configuración del inquilino

class TenantConfig:
def init(self, client_rate: int, client_window_ms: int, tenant_rate: int, tenant_window_ms: int):
self.client_rate = client_rate
self.client_window_ms = client_window_ms
self.tenant_rate = tenant_rate
self.tenant_window_ms = tenant_window_ms

class SlidingWindowRateLimiter:
"""
Un limitador de tasa multiinquilino, seguro para subprocesos, que utiliza un algoritmo de registro de ventana deslizante.

Esta implementación proporciona limitación de tasa por cliente bajo un límite global por inquilino.
Utiliza una política de uso compartido justo en la que ningún cliente individual puede agotar la cuota del inquilino
debido a su propio límite individual.

Algoritmo: Registro de ventana deslizante
- Para cada cliente e inquilino, mantenemos un deque (un contenedor similar a una lista con adiciones
  y extracciones rápidas desde ambos extremos) de marcas de tiempo de las solicitudes recibidas dentro de la ventana.
- Cuando llega una nueva solicitud, primero descartamos todas las marcas de tiempo anteriores al tiempo actual
  menos el tamaño de la ventana.
- Luego, verificamos si el número de marcas de tiempo restantes es menor que el límite de tasa configurado.
- Si se permite la solicitud, la marca de tiempo actual se añade al registro.

Compensaciones:
- Precisión: Este algoritmo es perfectamente preciso. Rastrea correctamente el conteo de solicitudes
  dentro de la ventana deslizante precisa, evitando problemas con contadores de ventana fija donde
  las ráfagas en el límite de las ventanas pueden exceder la tasa.
- Uso de memoria: La huella de memoria es proporcional al límite de tasa, ya que almacena una
  marca de tiempo por solicitud permitida dentro de la ventana. Para un cliente con una tasa de 1000, almacenará hasta 1000 marcas de tiempo (aprox. 8 KB por cliente). Esto puede ser significativo para
  sistemas con una cantidad muy grande de clientes activos o límites de tasa muy altos.

Supuestos:
- Despliegue en un solo proceso: El estado se almacena en memoria dentro de la instancia de la clase. Es
  seguro para subprocesos para su uso dentro de un único proceso de aplicación.
- Reloj monótonamente creciente: Se espera que el parámetro `now_ms` sea una marca de tiempo monótonamente
  creciente en milisegundos. El limitador se protege contra retrocesos en el tiempo
  ignorando solicitudes con marcas de tiempo anteriores a la más recientemente procesada.

Extensión distribuida:
Para admitir un entorno distribuido (por ejemplo, múltiples servidores API), el estado debe
externalizarse a un almacén central de baja latencia como Redis.
- Almacenamiento de estado: Podría utilizarse un Sorted Set de Redis para cada registro de cliente/inquilino, con tanto la
  puntuación como el valor siendo la marca de tiempo de la solicitud. Esto permite consultas de rango eficientes.
- Atomicidad: Las transacciones de Redis (MULTI/EXEC) o los scripts Lua serían esenciales para realizar
  la operación de comprobar y actualizar (purgar marcas de tiempo antiguas, comprobar conteo, añadir nueva marca de tiempo)
  de forma atómica, evitando condiciones de carrera entre distintos servidores.
- Evicción: La expiración de claves integrada de Redis (`EXPIRE`) gestionaría de forma natural la evicción
  de datos obsoletos de clientes.
"""

def __init__(self):
    self._tenants: Dict[str, Dict] = {}
    self._lock = threading.Lock()  # Bloqueo global para añadir/eliminar inquilinos

def add_tenant(self, tenant_id: str, config: TenantConfig):
    """Añade o actualiza la configuración de un inquilino."""
    with self._lock:
        if tenant_id not in self._tenants:
            self._tenants[tenant_id] = {
                "config": config,
                "clients": {},
                "tenant_log": deque(),
                "lock": threading.Lock() # Bloqueo por inquilino para seguridad de subprocesos
            }
        else:
            # Actualizar la configuración sobre la marcha
            self._tenants[tenant_id]['config'] = config

def allow(self, tenant_id: str, client_id: str, now_ms: Optional[int] = None) -> Tuple[bool, int]:
    """
    Determina si una solicitud está permitida para un inquilino y cliente dados.

    Args:
        tenant_id: El identificador del inquilino.
        client_id: El identificador del cliente.
        now_ms: La marca de tiempo actual en milisegundos. Si es None, se utiliza la hora del sistema.

    Returns:
        Una tupla (is_allowed, retry_after_ms).
        - is_allowed (bool): True si la solicitud está permitida, False en caso contrario.
        - retry_after_ms (int): Si se deniega, el número de milisegundos que esperar antes de que la
          siguiente solicitud pueda estar permitida. 0 si la solicitud está permitida.
    """
    if now_ms is None:
        now_ms = int(time.time() * 1000)

    with self._lock:
        if tenant_id not in self._tenants:
            raise ValueError(f"El inquilino '{tenant_id}' no está configurado.")
        tenant_state = self._tenants[tenant_id]

    with tenant_state['lock']:
        config = tenant_state['config']

        # 1. Obtener o crear el estado del cliente
        if client_id not in tenant_state['clients']:
            tenant_state['clients'][client_id] = {"log": deque(), "last_seen_ms": 0}
        client_state = tenant_state['clients'][client_id]

        # Defenderse contra retrocesos del reloj
        if now_ms < client_state['last_seen_ms']:
            return (False, 1) # Denegar y sugerir un pequeño retraso para reintentar

        # 2. Purgar y comprobar el límite por cliente
        client_allowed, client_retry_ms = self._check_limit(
            client_state['log'], config.client_rate, config.client_window_ms, now_ms
        )
        if not client_allowed:
            return (False, client_retry_ms)

        # 3. Purgar y comprobar el límite por inquilino
        tenant_allowed, tenant_retry_ms = self._check_limit(
            tenant_state['tenant_log'], config.tenant_rate, config.tenant_window_ms, now_ms
        )
        if not tenant_allowed:
            return (False, tenant_retry_ms)

        # 4. Si ambos están permitidos, registrar la solicitud y permitirla
        client_state['log'].append(now_ms)
        client_state['last_seen_ms'] = now_ms
        tenant_state['tenant_log'].append(now_ms)

        # 5. Limpieza oportunista de clientes obsoletos para este inquilino
        self._cleanup_stale_clients(tenant_state, now_ms)

        return (True, 0)

def _check_limit(self, log: deque, rate: int, window_ms: int, now_ms: int) -> Tuple[bool, int]:
    """Auxiliar para purgar un registro de marcas de tiempo y comprobarlo contra un límite de tasa."""
    # Purgar marcas de tiempo antiguas
    window_start_ms = now_ms - window_ms
    while log and log[0] <= window_start_ms:
        log.popleft()

    # Comprobar si se excede el límite
    if len(log) >= rate:
        oldest_timestamp = log[0]
        retry_after_ms = (oldest_timestamp + window_ms) - now_ms
        return (False, max(1, retry_after_ms)) # Garantizar que el reintento sea de al menos 1 ms

    return (True, 0)

def _cleanup_stale_clients(self, tenant_state: Dict, now_ms: int):
    """
    Elimina los clientes que han estado inactivos durante mucho tiempo.
    Un cliente inactivo es aquel cuya última solicitud es más antigua que el doble del tamaño de la ventana.
    Esta es una limpieza oportunista, ejecutada en línea con una solicitud.
    """
    config = tenant_state['config']
    stale_threshold_ms = config.client_window_ms * 2
    stale_clients = [
        cid for cid, cstate in tenant_state['clients'].items()
        if now_ms - cstate['last_seen_ms'] > stale_threshold_ms
    ]
    for client_id in stale_clients:
        del tenant_state['clients'][client_id]

--- Conjunto de pruebas ---

class TestSlidingWindowRateLimiter(unittest.TestCase):

def setUp(self):
    self.limiter = SlidingWindowRateLimiter()
    self.tenant_config = TenantConfig(
        client_rate=5,
        client_window_ms=1000,
        tenant_rate=15,
        tenant_window_ms=1000
    )
    self.limiter.add_tenant('test_tenant', self.tenant_config)
    self.now = int(time.time() * 1000)

def test_basic_client_limit(self):
    """Un solo cliente debería ser limitado después de exceder su tasa."""
    for i in range(5):
        allowed, _ = self.limiter.allow('test_tenant', 'client1', self.now + i)
        self.assertTrue(allowed)

    allowed, retry_after = self.limiter.allow('test_tenant', 'client1', self.now + 5)
    self.assertFalse(allowed)
    self.assertGreater(retry_after, 990) # Debería estar alrededor de 1000 ms - 5 ms

def test_window_sliding(self):
    """Las solicitudes deberían permitirse de nuevo después de que haya pasado la ventana."""
    for i in range(5):
        self.limiter.allow('test_tenant', 'client1', self.now + i)

    # Esta solicitud debería ser denegada
    allowed, _ = self.limiter.allow('test_tenant', 'client1', self.now + 6)
    self.assertFalse(allowed)

    # Después de que pase la ventana de la primera solicitud, se permite una nueva
    # La primera solicitud fue en self.now, la ventana es de 1000 ms.
    # En self.now + 1001, la primera solicitud expira.
    allowed, _ = self.limiter.allow('test_tenant', 'client1', self.now + 1001)
    self.assertTrue(allowed, "La solicitud debería permitirse después de que la ventana se deslice")

def test_idle_client_returns(self):
    """La cuota de un cliente inactivo debería estar renovada cuando vuelva."""
    for i in range(5):
        self.limiter.allow('test_tenant', 'client1', self.now + i)
    
    # El cliente queda inactivo durante más de una ventana
    new_time = self.now + 2000
    for i in range(5):
        allowed, _ = self.limiter.allow('test_tenant', 'client1', new_time + i)
        self.assertTrue(allowed, "Las solicitudes deberían permitirse para un cliente que regresa")
    
    allowed, _ = self.limiter.allow('test_tenant', 'client1', new_time + 5)
    self.assertFalse(allowed, "El cliente debería ser limitado de nuevo después de usar la nueva cuota")

def test_tenant_limit_and_fairness(self):
    """Múltiples clientes deberían alcanzar colectivamente el límite del inquilino, demostrando equidad."""
    # 3 clientes, cada uno hace 5 solicitudes. Total 15 solicitudes, que es el límite del inquilino.
    for i in range(5):
        # Cliente 1
        self.assertTrue(self.limiter.allow('test_tenant', 'c1', self.now + i)[0])
        # Cliente 2
        self.assertTrue(self.limiter.allow('test_tenant', 'c2', self.now + i)[0])
        # Cliente 3
        self.assertTrue(self.limiter.allow('test_tenant', 'c3', self.now + i)[0])

    # En este punto, el registro del inquilino tiene 15 solicitudes. La siguiente solicitud de cualquier cliente debería fallar.
    self.assertFalse(self.limiter.allow('test_tenant', 'c4', self.now + 6)[0])
    self.assertFalse(self.limiter.allow('test_tenant', 'c1', self.now + 6)[0])

    # Probar equidad: un cliente alcanza su límite, pero otro puede continuar.
    limiter = SlidingWindowRateLimiter()
    limiter.add_tenant('fair_tenant', TenantConfig(client_rate=2, client_window_ms=1000, tenant_rate=10, tenant_window_ms=1000))
    # El cliente acaparador usa toda su cuota
    self.assertTrue(limiter.allow('fair_tenant', 'hog', self.now)[0])
    self.assertTrue(limiter.allow('fair_tenant', 'hog', self.now + 1)[0])
    self.assertFalse(limiter.allow('fair_tenant', 'hog', self.now + 2)[0]) # El acaparador está bloqueado

    # Otro cliente aún debería poder hacer solicitudes
    self.assertTrue(limiter.allow('fair_tenant', 'fair_client', self.now + 3)[0])

def test_stale_client_eviction(self):
    """Los clientes obsoletos deberían eliminarse de la memoria."""
    self.limiter.allow('test_tenant', 'stale_client', self.now)
    self.assertIn('stale_client', self.limiter._tenants['test_tenant']['clients'])

    # Hacer una nueva solicitud en un futuro lejano, desencadenando la limpieza
    future_time = self.now + self.tenant_config.client_window_ms * 3
    self.limiter.allow('test_tenant', 'active_client', future_time)

    self.assertNotIn('stale_client', self.limiter._tenants['test_tenant']['clients'])

def test_concurrency(self):
    """Debería gestionar correctamente solicitudes concurrentes al mismo cliente."""
    limiter = SlidingWindowRateLimiter()
    limiter.add_tenant('concurrent_tenant', TenantConfig(client_rate=100, client_window_ms=1000, tenant_rate=1000, tenant_window_ms=1000))
    start_time = int(time.time() * 1000)
    num_threads = 10
    reqs_per_thread = 15
    allowed_count = [0]
    lock = threading.Lock()

    def worker():
        for _ in range(reqs_per_thread):
            now = int(time.time() * 1000)
            allowed, _ = limiter.allow('concurrent_tenant', 'shared_client', now)
            if allowed:
                with lock:
                    allowed_count[0] += 1
            time.sleep(0.001) # pequeña pausa para aumentar la probabilidad de contención

    threads = [threading.Thread(target=worker) for _ in range(num_threads)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

    # El número de solicitudes permitidas debería ser exactamente el límite de tasa del cliente.
    self.assertEqual(allowed_count[0], 100)

def test_clock_regression(self):
    """Debería denegar solicitudes si el reloj retrocede."""
    self.assertTrue(self.limiter.allow('test_tenant', 'client1', self.now + 100)[0])
    # Una solicitud con una marca de tiempo anterior debería ser denegada
    allowed, retry_after = self.limiter.allow('test_tenant', 'client1', self.now + 99)
    self.assertFalse(allowed)
    self.assertEqual(retry_after, 1)

if name == 'main':
unittest.main()

Resultado

#2

Votos ganadores

0 / 3

Puntuación media

50
Modelos evaluadores Google Gemini 2.5 Flash

Puntuación total

46

Comentario general

La Respuesta B implementa un limitador de tasa básico de ventana deslizante con límites por cliente y por inquilino. Sin embargo, falla críticamente en implementar la política de reparto equitativo para la saturación de inquilinos, que era un requisito central. Su desalojo de memoria es oportunista y menos controlado, y su conjunto de pruebas, aunque presente, no cubre adecuadamente el aspecto de reparto equitativo que afirma abordar. La calidad del código es aceptable pero menos refinada que la Respuesta A.

Ver detalle de evaluación

Corrección

Peso 35%
40

La Respuesta B falla en implementar correctamente la política de reparto equitativo para la saturación de inquilinos, que es un requisito funcional importante. Si bien los límites básicos de cliente e inquilino funcionan, el mecanismo central de equidad está ausente. El reintento `retry_after_ms` de regresión del reloj es un mínimo de '1' que no es muy informativo.

Integridad

Peso 20%
40

La Respuesta B está incompleta debido a la falta de la política de reparto equitativo. Su desalojo de memoria es solo oportunista y carece de un mecanismo de barrido dedicado, lo que la hace menos robusta de lo requerido. El parámetro `now_ms` que tiene un valor predeterminado es una desviación menor de la indicación.

Calidad del código

Peso 20%
65

La calidad del código de la Respuesta B es aceptable. Utiliza diccionarios para el estado, lo que es menos estructurado que las dataclasses. El método `allow` es algo largo y la limpieza oportunista es menos explícita. El uso de bloqueos por inquilino es un buen patrón, pero la solución general es menos refinada.

Valor práctico

Peso 15%
50

La Respuesta B tiene un valor práctico limitado para escenarios multi-inquilino porque carece de la política crucial de reparto equitativo. La limpieza oportunista puede no ser suficiente para todos los casos de uso de producción, y no hay una forma explícita de activar una limpieza completa.

Seguimiento de instrucciones

Peso 10%
30

La Respuesta B falla en seguir una instrucción crítica: implementar la política de reparto equitativo para la saturación de inquilinos. Su mecanismo de desalojo es menos robusto de lo especificado, y la prueba para 'tenant_limit_and_fairness' en realidad no prueba la redistribución equitativa entre clientes competidores cuando el inquilino está saturado.

Modelos evaluadores OpenAI GPT-5.5

Puntuación total

48

Comentario general

La respuesta B implementa un limitador de registro básico de ventana deslizante seguro para hilos con límites por cliente y por inquilino, documentación y algunas pruebas. Sin embargo, no implementa una redistribución justa real cuando un inquilino está saturado, tiene un manejo de regresión de reloj débil y parcialmente incorrecto entre inquilinos/clientes, la limpieza es oportunista e incompleta, y las pruebas no cubren varios casos extremos requeridos rigurosamente. Es un limitador simple razonable pero incompleto para la tarea de referencia.

Ver detalle de evaluación

Corrección

Peso 35%
45

Correcto para verificaciones básicas de ventana deslizante por cliente e inquilino en uso monótono normal, pero carece de una verdadera distribución justa de la saturación del inquilino. El manejo del reloj es solo por cliente y puede dejar el registro del inquilino fuera de orden de marca de tiempo entre clientes, rompiendo las suposiciones de poda/reintento. También niega las marcas de tiempo inversas en lugar de normalizarlas y tiene semánticas de limpieza más débiles.

Integridad

Peso 20%
42

Incluye una API, configuración, registros deslizantes, límite de inquilino, explicación y algunas pruebas, pero omite el requisito central de redistribución justa. Las pruebas no verifican explícitamente el comportamiento exacto del límite, las marcas de tiempo duplicadas, la justicia real de la saturación del inquilino o la eliminación segura de clientes activos en profundidad.

Calidad del código

Peso 20%
58

Legible y compacto, con métodos auxiliares y comentarios sencillos. Sin embargo, se basa en diccionarios de tipos poco estrictos para el estado, tiene una sincronización menos robusta en torno a las actualizaciones de configuración, una validación más débil y un diseño de limpieza/justicia más simple de lo que requiere la tarea.

Valor práctico

Peso 15%
50

Útil para un servicio pequeño de un solo proceso que necesita limitación de velocidad básica, y la discusión de la extensión de Redis es razonable. Su falta de justicia para inquilinos saturados y el frágil manejo del reloj lo hacen menos adecuado para el escenario de cuotas multi-inquilino descrito.

Seguimiento de instrucciones

Peso 10%
50

Elige un idioma permitido y proporciona código, comentarios y pruebas, pero no cumple con el importante requisito de reparto justo y solo aborda parcialmente varios casos extremos requeridos. La política de reparto justo reclamada en el texto no se implementa realmente.

Modelos evaluadores Anthropic Claude Fable 5

Puntuación total

55

Comentario general

La respuesta B ofrece un limitador de registro de ventana deslizante limpio y legible con límites por cliente y por inquilino, bloqueo de dos niveles, desalojo oportunista y buena documentación de compensaciones, suposiciones y una extensión distribuida basada en Redis. Sin embargo, omite por completo el requisito de redistribución justa: bajo saturación de inquilinos, cualquier cliente puede monopolizar la capacidad liberada, y su prueba de 'justicia' solo demuestra límites individuales por cliente, lo que la política de evaluación considera explícitamente incompleto. Los problemas adicionales incluyen defensa de reloj retroactivo solo por cliente que puede dejar la cola de inquilinos compartida desordenada, limpieza que solo se ejecuta en solicitudes permitidas (arriesgando el crecimiento de la memoria bajo saturación sostenida), ninguna prueba explícita de límite exacto y una prueba de concurrencia vinculada al tiempo real de reloj.

Ver detalle de evaluación

Corrección

Peso 35%
53

Los registros de ventana deslizante por cliente y por inquilino son correctos de forma aislada, y la poda de límites utiliza <= de manera consistente. Sin embargo, la justicia del requisito 4 no se implementa realmente: cuando el inquilino está saturado, cualquier cliente es denegado de manera idéntica y el primer cliente en reintentar después de la expiración puede monopolizar la capacidad liberada; no hay mecanismo de redistribución. La defensa contra el reloj retroactivo es solo por cliente, por lo que las marcas de tiempo desordenadas entre diferentes clientes pueden insertar entradas desordenadas en la cola de inquilinos compartida, rompiendo la suposición de ordenación del bucle de poda. La limpieza de clientes obsoletos se ejecuta solo en la ruta de éxito-permitir, por lo que bajo saturación sostenida de inquilinos, los clientes denegados acumulan estado indefinidamente, violando el requisito de memoria acotada en ese escenario.

Integridad

Peso 20%
50

Cubre ventanas por cliente, límite de inquilino, subprocesos, desalojo, compensaciones del algoritmo, suposiciones y un esquema de diseño distribuido. Pero el diferenciador central —redistribución justa cuando el inquilino está saturado— está ausente, y la prueba de 'justicia' solo muestra que el límite individual de un cliente no bloquea a otro cliente, lo que es solo limitación independiente. No hay una prueba explícita de límite exacto (el deslizamiento de la ventana se prueba en +1001, no en el límite en sí), y el manejo de marcas de tiempo duplicadas es solo implícito. Según la política de evaluación, las soluciones con solo límites individuales por cliente están incompletas.

Calidad del código

Peso 20%
64

Código legible con un útil docstring de clase que explica el algoritmo, las compensaciones, las suposiciones y la extensión de Redis, además de un ayudante _check_limit sensato. Sin embargo, el estado se modela como diccionarios anidados sin tipo de diccionarios en lugar de dataclasses, las pruebas acceden directamente a atributos privados (_tenants), la prueba de concurrencia depende del tiempo real de reloj y duerme (potencialmente inestable), y no hay validación de configuración. El esquema de bloqueo de dos niveles es razonable, pero la API basada en configuración/tuplas es menos pulida.

Valor práctico

Peso 15%
56

Útil para limitación básica por cliente más límite de inquilino y las notas de extensión de Redis son prácticas. Pero en producción, la falta de reparto justo significa que un cliente agresivo puede apoderarse de toda la capacidad liberada del inquilino, la respuesta al retroceso del reloj (denegación general con reintento 1) puede rechazar tráfico legítimo bajo una ligera desviación de la marca de tiempo, y el salto de limpieza en solicitudes denegadas arriesga el crecimiento de la memoria exactamente cuando un inquilino está bajo ataque — el escenario que un limitador de tasa más necesita soportar.

Seguimiento de instrucciones

Peso 10%
54

Coincide con la forma de la API, entrega un registro de ventana deslizante genuino, establece suposiciones, discute compensaciones y esboza el diseño distribuido según lo solicitado. Pero falla el mandato central del requisito 4 de que la capacidad restante del inquilino se comparta de manera justa entre los clientes activos cuando está saturado, y el caso límite correspondiente (saturación de inquilino y redistribución justa) no se implementa ni se prueba genuinamente, lo que es un incumplimiento explícito de las instrucciones.

Resumen comparativo

Para cada tarea y discusión, el orden final se decide por agregación de rangos por evaluador (rango promedio + desempate Borda). La puntuación media se muestra como referencia.

Evaluadores: 3

Votos ganadores

3 / 3

Puntuación media

86
Ver esta respuesta

Votos ganadores

0 / 3

Puntuación media

50
Ver esta respuesta

Resultados de evaluación

Modelos evaluadores Anthropic Claude Fable 5

Motivo del ganador

La respuesta A gana de forma decisiva en el resultado ponderado. Lidera en corrección (35%) con una ventana deslizante probada en límites exactos, manejo de reloj limitado y un modelo de concurrencia atómica verificado, mientras que B tiene un error de ordenación en el registro de inquilinos y un crecimiento de memoria dependiente de la saturación. A también lidera en completitud (20%) y seguimiento de instrucciones (10%) porque implementa y prueba el requisito de redistribución justa que la política de evaluación nombra como un diferenciador importante, mientras que B solo implementa límites independientes por cliente, que la política etiqueta explícitamente como incompleto. Además, A lidera en calidad de código (20%) y valor práctico (15%). Dado que A obtiene una puntuación más alta en todos los criterios, el total ponderado favorece inequívocamente a A.

Modelos evaluadores OpenAI GPT-5.5

Motivo del ganador

La respuesta A gana porque satisface mucho mejor los requisitos de corrección, que tienen un gran peso: utiliza un registro de ventana deslizante real para los límites del cliente y del inquilino, calcula tiempos de reintento sensatos, maneja la expiración de los límites, ajusta el tiempo anterior, protege el estado con un bloqueo, elimina el estado obsoleto e implementa un mecanismo explícito de reparto justo FIFO para inquilinos saturados. La respuesta B carece del comportamiento clave de reparto justo, tiene una problemática ordenación de marcas de tiempo entre clientes bajo regresión del reloj y solo cubre parcialmente los casos extremos requeridos.

Modelos evaluadores Google Gemini 2.5 Flash

Motivo del ganador

La Respuesta A es la clara ganadora porque implementa completa y correctamente todos los requisitos funcionales, especialmente la desafiante política de reparto equitativo para la saturación de inquilinos, que la Respuesta B omite por completo. La Respuesta A también demuestra una calidad de código superior, una gestión de memoria más robusta y un conjunto de pruebas más completo que cubre genuinamente todos los casos extremos especificados. La falta de reparto equitativo en la Respuesta B es un fallo funcional crítico que reduce significativamente su corrección y completitud.

X f L