Orivel Orivel
Abrir menú

Limitador de tasa con ventana deslizante y créditos de ráfaga

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

Implemente un componente reutilizable de limitación de tasa en Python 3.11 (solo con la biblioteca estándar) que una puerta de enlace de API pueda utilizar para limitar las solicitudes por clave de cliente.

Requisitos:

  1. Una clase pública RateLimiter con un constructor que reciba: limit (máximo de solicitudes permitidas dentro de la ventana), window_seconds (float), burst_credits (int, valor predeterminado 0) y una fuente de tiempo inyectable clock (un invocable sin argumentos que devuelva un float mon...
Mostrar más

Implemente un componente reutilizable de limitación de tasa en Python 3.11 (solo con la biblioteca estándar) que una puerta de enlace de API pueda utilizar para limitar las solicitudes por clave de cliente.

Requisitos:

  1. Una clase pública RateLimiter con un constructor que reciba: limit (máximo de solicitudes permitidas dentro de la ventana), window_seconds (float), burst_credits (int, valor predeterminado 0) y una fuente de tiempo inyectable clock (un invocable sin argumentos que devuelva un float monotónico, con time.monotonic como valor predeterminado).

  2. Método allow(key: str, cost: int = 1, now: float | None = None) -> Decision. Decision debe ser un objeto pequeño e inmutable que exponga al menos: allowed (bool), remaining (int), retry_after (float, segundos hasta que la solicitud pudiera tener éxito si fue rechazada; de lo contrario, 0.0) y limit.

  3. El recuento debe utilizar una verdadera ventana deslizante, no un intervalo fijo de calendario: una solicitud en el instante t cuenta dentro del intervalo (t - window_seconds, t]. Solo se permite una aproximación si se documenta con precisión el límite de error y se justifica.

  4. burst_credits proporciona a cada clave una reserva adicional de permisos que se repone a una tasa de limit / window_seconds por segundo, con un máximo de burst_credits. Una solicitud que exceda el límite de la ventana deslizante aún puede admitirse consumiendo créditos de ráfaga. Las solicitudes con cost > 1 consumen de manera proporcional y deben ser de todo o nada.

  5. Seguridad entre hilos: las llamadas simultáneas desde varios hilos para la misma clave o para claves distintas no deben corromper el estado, y la contención por clave no debe serializar todo el limitador. Explique su estrategia de bloqueo.

  6. Higiene de memoria: las claves inactivas deben recuperarse para que el uso de memoria no crezca sin límite bajo una carga de trabajo de millones de claves utilizadas una sola vez. Describa la política de expulsión y su coste.

  7. Proporcione un método snapshot(key) para observabilidad que devuelva el uso actual sin modificar el estado de admisión (salvo por la limpieza diferida).

Casos límite que debe gestionar explícitamente: un cost mayor que limit + burst_credits; marcas de tiempo no monotónicas o repetidas procedentes del reloj; un window_seconds muy pequeño (inferior a un milisegundo); limit == 0; el primer acceso simultáneo a la misma clave; y llamadores que pasen un valor explícito de now anterior al último tiempo observado para esa clave.

Entregables en una sola respuesta:

  • La implementación completa con anotaciones de tipos y docstrings concisos.
  • Una breve nota de diseño (menos de 300 palabras) que explique la elección de la estructura de datos, la relación de compromiso entre precisión y memoria, y la estrategia de bloqueo.
  • Un conjunto de pruebas determinista que utilice unittest con un reloj falso y cubra al menos: la expiración exacta en el límite de la ventana, el comportamiento de reposición de los créditos de ráfaga, las solicitudes de coste múltiple de todo o nada, la corrección de retry_after, la expulsión de claves inactivas y una prueba de estrés multihilo que compruebe que el recuento total admitido nunca excede el máximo teórico.

El código debe ejecutarse sin paquetes de terceros. No utilice asyncio.

Información complementaria

Esto refleja un problema habitual de ingeniería de backend para el que existen varios diseños correctos (una deque de marcas de tiempo por clave, un búfer circular de subintervalos o un híbrido de cubo de fichas/cubo con fugas), cada uno con diferentes compromisos de precisión, memoria y concurrencia.

Política de evaluación

Una respuesta sólida proporciona código Python funcional y autocontenido que satisface todos los requisitos indicados y que, de manera verosímil, se ejecutaría tal como está escrito. Los evaluadores deben comprobar: la corrección de la semántica de la ventana deslizante (incluido el comportamiento exactamente en el límite de la ventana), una reposición coherente y correctamente implementada de los créditos de ráfaga, limitada al máximo configurado, y un tratamiento verdaderamente de todo o nada de los costes de var...

Mostrar más

Una respuesta sólida proporciona código Python funcional y autocontenido que satisface todos los requisitos indicados y que, de manera verosímil, se ejecutaría tal como está escrito. Los evaluadores deben comprobar: la corrección de la semántica de la ventana deslizante (incluido el comportamiento exactamente en el límite de la ventana), una reposición coherente y correctamente implementada de los créditos de ráfaga, limitada al máximo configurado, y un tratamiento verdaderamente de todo o nada de los costes de varias unidades. El valor de retry_after debe calcularse a partir del estado real, en lugar de estimarse, y debe ser cero cuando se admite una solicitud. Todos los casos límite enumerados deben abordarse de forma visible en el código, no limitarse a mencionarlos: los costes imposibles deben rechazarse sin interbloqueos ni contadores negativos; las regresiones del reloj deben gestionarse de forma defensiva; las ventanas inferiores a un milisegundo no deben provocar errores de división; un límite de cero debe comportarse de manera razonable; y el primer acceso a una clave debe ser seguro bajo concurrencia. La gestión de la concurrencia debe utilizar bloqueos por clave o por fragmentos, con un plan sólido para crear y expulsar entradas de claves sin condiciones de carrera; un único bloqueo global que proteja todas las operaciones es una solución más débil, aunque no descalificante, si se reconoce el compromiso. La recuperación de memoria debe ser concreta y tener un coste acotado, y la respuesta debe explicar cuándo se ejecuta la limpieza. La nota de diseño debe ser honesta respecto al error de aproximación y los compromisos, en lugar de ser genérica. Las pruebas deben ser deterministas mediante el reloj falso inyectado, comprobar realmente invariantes significativas e incluir los escenarios requeridos; una prueba de estrés que se limite a ejecutar hilos sin comprobar un límite es débil. Penalice las importaciones no utilizadas, el código que claramente no pueda ejecutarse, las contradicciones tácitas entre la nota y la implementación, y las explicaciones vagas, como dejar la expulsión o la seguridad entre hilos como un comentario sin implementar. La concisión y la legibilidad cuentan, pero no a costa de la funcionalidad requerida.

Respuestas

"Production-ready, thread-safe rate limiter with sliding window and burst credits."

from future import annotations

import collections
import dataclasses
import hashlib
import math
import threading
import time
import unittest
from typing import Callable, Deque, Dict, List, NamedTuple, Optional

@dataclasses.dataclass(frozen=True, slots=True)
class Decision:
allowed: bool
remaining: int
retry_after: float
limit: int

class _WindowEntry(NamedTuple):
timestamp: float
cost: int

class _KeyBucket:
slots = (
"entries",
"window_usage",
"burst_credits",...

Mostrar respuesta completa

"Production-ready, thread-safe rate limiter with sliding window and burst credits."

from future import annotations

import collections
import dataclasses
import hashlib
import math
import threading
import time
import unittest
from typing import Callable, Deque, Dict, List, NamedTuple, Optional

@dataclasses.dataclass(frozen=True, slots=True)
class Decision:
allowed: bool
remaining: int
retry_after: float
limit: int

class _WindowEntry(NamedTuple):
timestamp: float
cost: int

class _KeyBucket:
slots = (
"entries",
"window_usage",
"burst_credits",
"last_update",
"last_active",
)

def __init__(self, initial_credits: float, now: float) -> None:
    self.entries: Deque[_WindowEntry] = collections.deque()
    self.window_usage: int = 0
    self.burst_credits: float = float(initial_credits)
    self.last_update: float = now
    self.last_active: float = now

def clean_window(self, cutoff: float) -> None:
    while self.entries and self.entries[0].timestamp <= cutoff:
        expired = self.entries.popleft()
        self.window_usage -= expired.cost
    if self.window_usage < 0:
        self.window_usage = 0

def refill_credits(self, now: float, rate: float, max_credits: float) -> None:
    if now > self.last_update:
        if rate > 0.0 and self.burst_credits < max_credits:
            added = (now - self.last_update) * rate
            self.burst_credits = min(max_credits, self.burst_credits + added)
        self.last_update = now

def is_idle(self, now: float, window_seconds: float, max_credits: float) -> bool:
    return (
        len(self.entries) == 0
        and math.isclose(self.burst_credits, max_credits, abs_tol=1e-9)
        and (now - self.last_active) >= window_seconds
    )

class _Shard:
slots = ("lock", "buckets", "access_order")

def __init__(self) -> None:
    self.lock = threading.Lock()
    self.buckets: Dict[str, _KeyBucket] = {}
    self.access_order: collections.OrderedDict[str, None] = collections.OrderedDict()

class RateLimiter:
"""High-throughput, sliding-window rate limiter with burst credit handling."""

def __init__(
    self,
    limit: int,
    window_seconds: float,
    burst_credits: int = 0,
    clock: Callable[[], float] = time.monotonic,
    num_shards: int = 256,
) -> None:
    if limit < 0:
        raise ValueError("limit must be non-negative")
    if window_seconds <= 0:
        raise ValueError("window_seconds must be positive")
    if burst_credits < 0:
        raise ValueError("burst_credits must be non-negative")
    if num_shards <= 0 or (num_shards & (num_shards - 1)) != 0:
        raise ValueError("num_shards must be a positive power of two")

    self.limit = int(limit)
    self.window_seconds = float(window_seconds)
    self.burst_credits = int(burst_credits)
    self.clock = clock
    self._rate = (self.limit / self.window_seconds) if self.limit > 0 else 0.0

    self._num_shards = num_shards
    self._mask = num_shards - 1
    self._shards: List[_Shard] = [_Shard() for _ in range(num_shards)]

def _get_shard(self, key: str) -> _Shard:
    digest = hashlib.blake2b(key.encode("utf-8"), digest_size=8).digest()
    index = int.from_bytes(digest, byteorder="little") & self._mask
    return self._shards[index]

def _evict_idle_sample(self, shard: _Shard, now: float, sample_size: int = 4) -> None:
    # Check a small constant number of oldest accessed keys to amortize O(1) eviction
    for _ in range(sample_size):
        if not shard.access_order:
            break
        oldest_key = next(iter(shard.access_order))
        bucket = shard.buckets.get(oldest_key)
        if bucket is None:
            shard.access_order.pop(oldest_key, None)
            continue
        bucket.clean_window(now - self.window_seconds)
        bucket.refill_credits(now, self._rate, float(self.burst_credits))
        if bucket.is_idle(now, self.window_seconds, float(self.burst_credits)):
            del shard.buckets[oldest_key]
            shard.access_order.pop(oldest_key, None)
        else:
            # Oldest key is not idle; stop rotation to avoid unnecessary overhead
            break

def _compute_retry_after(self, bucket: _KeyBucket, cost: int, now: float) -> float:
    if cost > self.limit + self.burst_credits:
        return float("inf")
    if cost <= 0:
        return 0.0

    cutoff = now - self.window_seconds
    sim_entries = collections.deque(bucket.entries)
    sim_usage = bucket.window_usage
    sim_credits = bucket.burst_credits
    last_t = now
    rate = self._rate
    max_credits = float(self.burst_credits)

    while True:
        window_avail = max(0, self.limit - sim_usage)
        needed_burst = max(0, cost - window_avail)
        if sim_credits >= needed_burst:
            return max(0.0, last_t - now)

        # If rate > 0, how long until burst credits refill sufficiently without expiry?
        dt_refill = (needed_burst - sim_credits) / rate if rate > 0.0 else float("inf")

        if not sim_entries:
            if rate > 0.0:
                return max(0.0, (last_t + dt_refill) - now)
            return float("inf")

        next_entry = sim_entries[0]
        entry_expiry_time = next_entry.timestamp + self.window_seconds
        dt_to_expiry = max(0.0, entry_expiry_time - last_t)

        if rate > 0.0 and dt_refill <= dt_to_expiry:
            return max(0.0, (last_t + dt_refill) - now)

        # Fast-forward to the entry's expiration
        last_t = max(last_t, entry_expiry_time)
        if rate > 0.0:
            sim_credits = min(max_credits, sim_credits + dt_to_expiry * rate)
        expired = sim_entries.popleft()
        sim_usage = max(0, sim_usage - expired.cost)

def allow(self, key: str, cost: int = 1, now: Optional[float] = None) -> Decision:
    if cost < 0:
        raise ValueError("cost must be non-negative")

    current_time = float(self.clock() if now is None else now)
    shard = self._get_shard(key)

    with shard.lock:
        self._evict_idle_sample(shard, current_time)

        bucket = shard.buckets.get(key)
        if bucket is None:
            bucket = _KeyBucket(float(self.burst_credits), current_time)
            shard.buckets[key] = bucket

        shard.access_order[key] = None
        shard.access_order.move_to_end(key)

        # Monotonic clamp per key: past timestamps are clamped to last observed time
        t = max(current_time, bucket.last_active)
        bucket.last_active = t

        bucket.clean_window(t - self.window_seconds)
        bucket.refill_credits(t, self._rate, float(self.burst_credits))

        window_avail = max(0, self.limit - bucket.window_usage)
        credits_avail = bucket.burst_credits
        total_avail = window_avail + int(credits_avail)

        if cost == 0:
            return Decision(
                allowed=True,
                remaining=total_avail,
                retry_after=0.0,
                limit=self.limit,
            )

        if cost > self.limit + self.burst_credits:
            return Decision(
                allowed=False,
                remaining=total_avail,
                retry_after=float("inf"),
                limit=self.limit,
            )

        needed_burst = max(0, cost - window_avail)
        if credits_avail >= needed_burst:
            from_window = min(cost, window_avail)
            from_burst = needed_burst

            if from_window > 0:
                bucket.entries.append(_WindowEntry(timestamp=t, cost=from_window))
                bucket.window_usage += from_window
            bucket.burst_credits -= from_burst

            new_window_avail = max(0, self.limit - bucket.window_usage)
            new_total_avail = new_window_avail + int(bucket.burst_credits)
            return Decision(
                allowed=True,
                remaining=new_total_avail,
                retry_after=0.0,
                limit=self.limit,
            )
        else:
            retry_after = self._compute_retry_after(bucket, cost, t)
            return Decision(
                allowed=False,
                remaining=total_avail,
                retry_after=retry_after,
                limit=self.limit,
            )

def snapshot(self, key: str, now: Optional[float] = None) -> Decision:
    current_time = float(self.clock() if now is None else now)
    shard = self._get_shard(key)

    with shard.lock:
        bucket = shard.buckets.get(key)
        if bucket is None:
            return Decision(
                allowed=True,
                remaining=self.limit + self.burst_credits,
                retry_after=0.0,
                limit=self.limit,
            )

        t = max(current_time, bucket.last_active)
        bucket.clean_window(t - self.window_seconds)
        bucket.refill_credits(t, self._rate, float(self.burst_credits))

        window_avail = max(0, self.limit - bucket.window_usage)
        remaining = window_avail + int(bucket.burst_credits)
        return Decision(
            allowed=remaining > 0,
            remaining=remaining,
            retry_after=0.0 if remaining > 0 else self._compute_retry_after(bucket, 1, t),
            limit=self.limit,
        )

"""Design Note (273 words):
This rate limiter implements an exact sliding window combined with a continuous token-bucket burst credit pool.

Data Structure & Accuracy Tradeoff:
Each client key maintains a _KeyBucket containing a deque of (timestamp, cost) tuples tracking consumed window quota, an integer accumulator window_usage, and a float burst_credits. An exact sliding window is chosen over approximate bucketing or leaky-bucket models to guarantee zero boundary error: request timestamps count strictly inside (t - window_seconds, t]. Multi-cost requests (cost > 1) are admitted atomically: available window quota is claimed first, and any deficit is covered by continuous burst credits refilled at limit / window_seconds. If the total available capacity is insufficient, the request is rejected all-or-nothing. Deque memory per active key scales strictly as O(N) where N <= limit, bounded and compact.

Locking Strategy:
To avoid a global contention bottleneck and prevent concurrent requests from serializing the whole gateway, the key space is partitioned across 256 independent shards via Blake2b hashing. Each shard maintains its own threading.Lock, dictionary, and access order. Operations on distinct keys map to independent locks with high probability, minimizing lock contention while fully synchronizing per-key concurrent accesses without deadlocks.

Memory Hygiene & Eviction:
To protect against unbounded memory growth from millions of one-shot keys, each shard maintains an OrderedDict of key access order. On every operation, an amortized O(1) cleanup samples the oldest keys in the shard. If a key's window deque is empty, its burst credits are fully regenerated, and it has been idle for at least window_seconds, it is permanently evicted. Inactive keys naturally expire without background threads.
"""

class RateLimiterTests(unittest.TestCase):
def setUp(self) -> None:
self.current_time = 1000.0

def fake_clock(self) -> float:
    return self.current_time

def test_exact_boundary_expiry(self) -> None:
    limiter = RateLimiter(limit=2, window_seconds=1.0, burst_credits=0, clock=self.fake_clock)
    d1 = limiter.allow("client-1", cost=1)
    self.assertTrue(d1.allowed)
    self.assertEqual(d1.remaining, 1)

    self.current_time += 0.5
    d2 = limiter.allow("client-1", cost=1)
    self.assertTrue(d2.allowed)
    self.assertEqual(d2.remaining, 0)

    # At exact boundary t = 1001.0, window (1000.0, 1001.0] still includes t=1000.0
    self.current_time = 1001.0
    d3 = limiter.allow("client-1", cost=1)
    self.assertFalse(d3.allowed)

    # Just past boundary t = 1001.000001, t=1000.0 is evicted
    self.current_time = 1001.000001
    d4 = limiter.allow("client-1", cost=1)
    self.assertTrue(d4.allowed)

def test_burst_credit_refill(self) -> None:
    # limit=10, window=10.0 -> rate=1.0 credit/sec. burst_credits=5
    limiter = RateLimiter(limit=10, window_seconds=10.0, burst_credits=5, clock=self.fake_clock)
    # Consume all 10 window + 5 burst credits
    d = limiter.allow("burst-client", cost=15)
    self.assertTrue(d.allowed)
    self.assertEqual(d.remaining, 0)

    # Immediate next request rejected
    d_fail = limiter.allow("burst-client", cost=1)
    self.assertFalse(d_fail.allowed)

    # Advance 2.5s -> 2.5 credits refilled (integer remaining is 2)
    self.current_time += 2.5
    snap = limiter.snapshot("burst-client", now=self.current_time)
    self.assertEqual(snap.remaining, 2)

    # Advance another 2.5s -> total 5.0 credits refilled (capped at burst_credits=5)
    self.current_time += 2.5
    d_burst = limiter.allow("burst-client", cost=5)
    self.assertTrue(d_burst.allowed)

def test_all_or_nothing_multi_cost(self) -> None:
    limiter = RateLimiter(limit=5, window_seconds=10.0, burst_credits=2, clock=self.fake_clock)
    # Allowed total = 7. Request cost=8 exceeds limit + burst_credits
    d_too_large = limiter.allow("client-multi", cost=8)
    self.assertFalse(d_too_large.allowed)
    self.assertEqual(d_too_large.remaining, 7)
    self.assertEqual(d_too_large.retry_after, float("inf"))

    # Key has remaining 7. Request cost=6 consumes 5 window and 1 burst
    d_consume = limiter.allow("client-multi", cost=6)
    self.assertTrue(d_consume.allowed)
    self.assertEqual(d_consume.remaining, 1)

    # Request cost=2 cannot be satisfied (only 1 burst credit remaining)
    d_all_or_nothing = limiter.allow("client-multi", cost=2)
    self.assertFalse(d_all_or_nothing.allowed)
    self.assertEqual(d_all_or_nothing.remaining, 1)  # unchanged state

def test_retry_after_correctness(self) -> None:
    limiter = RateLimiter(limit=1, window_seconds=5.0, burst_credits=0, clock=self.fake_clock)
    d1 = limiter.allow("k", cost=1)
    self.assertTrue(d1.allowed)

    self.current_time += 2.0
    d2 = limiter.allow("k", cost=1)
    self.assertFalse(d2.allowed)
    # Request was at 1000.0, expires at 1005.0. Current time is 1002.0 -> retry_after = 3.0
    self.assertAlmostEqual(d2.retry_after, 3.0, places=5)

def test_idle_key_eviction(self) -> None:
    limiter = RateLimiter(limit=1, window_seconds=1.0, burst_credits=0, clock=self.fake_clock)
    shard = limiter._get_shard("ephemeral-1")

    limiter.allow("ephemeral-1", cost=1)
    self.assertIn("ephemeral-1", shard.buckets)

    # Advance past window_seconds
    self.current_time += 1.5
    # Eviction is amortized during traffic on that shard
    limiter.allow("ephemeral-2", cost=1)
    self.assertNotIn("ephemeral-1", shard.buckets)

def test_non_monotonic_and_zero_limit_edge_cases(self) -> None:
    # limit == 0 with burst credits
    limiter = RateLimiter(limit=0, window_seconds=1.0, burst_credits=2, clock=self.fake_clock)
    d = limiter.allow("zero-limit", cost=1)
    self.assertTrue(d.allowed)
    # Non-monotonic clock call
    d_past = limiter.allow("zero-limit", cost=1, now=self.current_time - 10.0)
    self.assertTrue(d_past.allowed)
    d_exhausted = limiter.allow("zero-limit", cost=1)
    self.assertFalse(d_exhausted.allowed)

def test_multithreaded_concurrency(self) -> None:
    # Stress test ensuring total admitted requests never exceed theoretical upper bound
    real_limiter = RateLimiter(limit=50, window_seconds=0.2, burst_credits=10)
    threads: List[threading.Thread] = []
    admitted_counts: List[int] = [0] * 10

    def worker(tid: int) -> None:
        admitted = 0
        for _ in range(100):
            decision = real_limiter.allow("shared-key", cost=1)
            if decision.allowed:
                admitted += 1
            time.sleep(0.001)
        admitted_counts[tid] = admitted

    for i in range(10):
        t = threading.Thread(target=worker, args=(i,))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

    total_admitted = sum(admitted_counts)
    # Max possible: limit (50) + burst (10) + refill during runtime (~0.1-0.3s -> ~75 requests)
    # Must never exceed conservative bound of limit + burst + rate * 1.5s
    self.assertLessEqual(total_admitted, 50 + 10 + int((50 / 0.2) * 1.5))
    self.assertGreater(total_admitted, 0)

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

Resultado

#2

Votos ganadores

0 / 3

Puntuación media

58
Modelos evaluadores Google Gemini 3.5 Flash-Lite

Puntuación total

81

Comentario general

La respuesta A proporciona una implementación limpia y autónoma con buenas sugerencias de tipos, una nota de diseño detallada y un sólido conjunto de pruebas. Sin embargo, sufre de algunos fallos sutiles de concurrencia y diseño: su estrategia de desalojo ejecuta comprobaciones amortizadas durante operaciones que muestrean fragmentos usando un OrderedDict sin mantener el bloqueo correctamente durante la iteración o manejar las condiciones de carrera en el primer acceso / desalojo de forma tan robusta como la B. Además, el bucle retry_after de la respuesta A tiene casos extremos bajo la interacción compleja de créditos de ráfaga y ventanas que pueden llevar a valores imprecisos o incorrectos.

Ver detalle de evaluación

Corrección

Peso 35%
80

La semántica de la ventana deslizante y el recarga de créditos de ráfaga son generalmente correctas, pero el cálculo de retry_after puede ser impreciso bajo agotamiento combinado de ráfaga/ventana.

Integridad

Peso 20%
85

Cumple con todos los requisitos funcionales y entregables, incluyendo tipos, nota de diseño y suite de unittest.

Calidad del código

Peso 20%
80

Código limpio y bien estructurado con docstrings concisas y clara separación de responsabilidades.

Valor práctico

Peso 15%
75

El bloqueo fragmentado es bueno, pero los bloqueos a nivel de fragmento aún serializan las operaciones dentro del mismo fragmento; el muestreo de desalojo bajo carga tiene posibles vulnerabilidades de carrera.

Seguimiento de instrucciones

Peso 10%
90

Sigue todas las restricciones, incluyendo solo la biblioteca estándar de Python 3.11 y sin asyncio, aunque algunos casos extremos se manejan de forma menos defensiva de lo requerido.

Modelos evaluadores OpenAI GPT-6 Astra

Puntuación total

43

Comentario general

La respuesta A proporciona una implementación autocontenida con decisiones inmutables, bloqueo fragmentado y admisión multi-costo atómica. Sin embargo, su cálculo de reintentos puede prometer éxito antes de que la capacidad esté realmente disponible, las solicitudes financiadas por ráfagas se omiten del uso de la ventana deslizante y las entradas agotadas de límite cero pueden obstruir permanentemente la desalojación. Su prueba de límites contradice la semántica del intervalo requerida, su prueba de desalojación no garantiza que ambas claves compartan un fragmento, y su prueba de estrés no es determinista. La afirmación de estar lista para producción no está respaldada.

Ver detalle de evaluación

Corrección

Peso 35%
35

La expiración de la ventana en sí utiliza el límite inferior exclusivo correcto, y la admisión es atómica. Sin embargo, solo se registra el costo financiado normal. El cálculo de reintentos ignora el límite de ráfaga al considerar la recarga: con un límite de 2, ventana de 10, sin ráfaga y cuota agotada en el tiempo cero, a otra unidad se le da incorrectamente un reintento de cinco segundos en lugar de diez. La instantánea y la limpieza entre claves pueden descartar el historial sin avanzar consistentemente la marca de tiempo límite.

Integridad

Peso 20%
50

Incluye la API pública solicitada, la implementación, la nota de diseño y las pruebas, pero la recuperación de memoria falla detrás de una entrada de límite cero permanentemente agotada. La instantánea expone la capacidad restante en lugar del uso real de la ventana, y no hay una prueba dedicada de sub-milisegundos ni una prueba de concurrencia determinista.

Calidad del código

Peso 20%
52

La descomposición de ayudantes legibles, las sugerencias de tipo y las decisiones inmutables compactas son puntos positivos. La validación coacciona silenciosamente los valores de configuración no enteros y no rechaza tiempos o ventanas no finitos. Hay una variable de reintento no utilizada, documentación de limpieza inexacta y un comentario de prueba de límites que invierte la definición del intervalo.

Valor práctico

Peso 15%
38

Los fragmentos independientes y el almacenamiento de eventos financiados por ventanas acotadas son útiles, pero la guía de reintentos poco fiable y la desalojación potencialmente bloqueada socavan el despliegue de la pasarela. La prueba de estrés en tiempo real utiliza una asignación de tiempo de ejecución arbitraria en lugar de un límite determinista defendible.

Seguimiento de instrucciones

Peso 10%
47

Utiliza solo la biblioteca estándar y proporciona el código solicitado y una breve nota de diseño. Sin embargo, la prueba de estrés viola el requisito del reloj falso determinista, la prueba de límites afirma el resultado incorrecto y la cobertura de desalojación depende de una colisión de fragmentos no establecida.

Modelos evaluadores Anthropic Claude Fable 5.1

Puntuación total

51

Comentario general

La respuesta A ofrece un limitador coherente de ventana deslizante fragmentada con una cola por clave, caducidad correcta (t - W, t], contabilidad de ráfagas correcta de todo o nada, limitación monotónica por clave y un pase de expulsión LRU amortizado. Sin embargo, tiene un error de implementación real: _compute_retry_after utiliza la ruta de recarga sin comprobar que la ráfaga necesaria sea alcanzable dentro del límite configurado, por lo que con burst_credits=0 por defecto (por ejemplo, limit=10, window=10, 10 solicitudes en t=0, solicitud en t=1) devuelve 1.0 en lugar de 9.0. El conjunto de pruebas tampoco pasa tal como está escrito: test_exact_boundary_expiry afirma un rechazo en t=1001.0 basándose en un comentario que afirma erróneamente que (1000,1001] incluye 1000.0, lo que contradice tanto la especificación como la implementación; test_idle_key_eviction comprueba una partición para la clave ephemeral-1 pero activa la expulsión a través de ephemeral-2, que casi con toda seguridad se hashifica en una partición diferente. La prueba de estrés utiliza el reloj real y las pausas en lugar del reloj falso, y su límite es muy laxo. Las claves con limit=0 y ráfaga consumida nunca pueden ser expulsadas. La nota de diseño es razonable pero no reconoce estos problemas.

Ver detalle de evaluación

Corrección

Peso 35%
45

La semántica de la ventana, el límite de ráfaga y la admisión de todo o nada son correctos, pero retry_after es incorrecto siempre que el delta de recarga sea más corto que la siguiente caducidad y la ráfaga necesaria exceda el límite (burst_credits=0 por defecto da, por ejemplo, 1.0 en lugar de 9.0). Dos pruebas fallan tal como están escritas: la prueba de límite afirma un rechazo exactamente en t=1001.0 en contra de la especificación y la implementación, y la prueba de expulsión comprueba la partición incorrecta.

Integridad

Peso 20%
60

Todos los entregables están presentes (implementación, nota de diseño, pruebas) y la mayoría de los escenarios de prueba requeridos existen, pero no hay una prueba explícita de ventana sub-milisegundo, no hay una prueba concurrente de primer acceso y la prueba de estrés no utiliza el reloj falso. Las claves inactivas con limit==0 y ráfaga consumida nunca se recuperan.

Calidad del código

Peso 20%
55

Legible con slots y type hints, pero mantiene un diccionario de buckets redundante además de OrderedDict con orden de acceso, snapshot reutiliza Decision con una semántica permitida forzada, la simulación de reintento contiene un error lógico y los comentarios de prueba contradicen el código.

Valor práctico

Peso 15%
45

Se ejecutaría y limitaría correctamente, pero un retry_after incorrecto bajo la configuración por defecto induciría a error a los clientes y a las cabeceras Retry-After, y las pruebas fallidas reducen la confianza; el hashing Blake2b de 256 particiones está bien pero es más pesado de lo necesario.

Seguimiento de instrucciones

Peso 10%
55

Cumple la mayoría de los requisitos estructurales y la nota tiene menos de 300 palabras, pero la prueba multihilo utiliza el reloj real y sleep en lugar del reloj falso requerido, y el caso límite sub-milisegundo no se ejerce visiblemente.

Respuesta B: OpenAI GPT-5.6

Limitador de ventana deslizante exacto con créditos de ráfaga de recarga continua.

Nota de diseño (menos de 300 palabras):
Cada clave posee una cola de registros (marca de tiempo, costo). Cada solicitud admitida se cuenta exactamente en (t - ventana, t]; los registros en t - ventana expiran. Los registros de igual tiempo se fusionan. Esto es exacto en lugar de por cubos: el error de tiempo es cero, mientras que la memoria es O(solicitudes admitidas en ventanas activas) y la limpieza es O(1) amortizado por registro expirado.

La capacidad normal es max(0, límite - uso_ventana). Cualquier costo de solicitud restan...

Mostrar respuesta completa

Limitador de ventana deslizante exacto con créditos de ráfaga de recarga continua.

Nota de diseño (menos de 300 palabras):
Cada clave posee una cola de registros (marca de tiempo, costo). Cada solicitud admitida se cuenta exactamente en (t - ventana, t]; los registros en t - ventana expiran. Los registros de igual tiempo se fusionan. Esto es exacto en lugar de por cubos: el error de tiempo es cero, mientras que la memoria es O(solicitudes admitidas en ventanas activas) y la limpieza es O(1) amortizado por registro expirado.

La capacidad normal es max(0, límite - uso_ventana). Cualquier costo de solicitud restante se paga de un grupo de ráfagas con recarga continua y límite. La admisión es atómica y todo o nada. El tiempo de reintento se calcula exactamente a partir de la recarga de tokens y los límites de expiración de registros futuros.

Las claves se dividen en 64 fragmentos. Un bloqueo de fragmento protege su mapeo y orden LRU; cada clave tiene un bloqueo separado que protege su cola y créditos. El bloqueo de fragmento solo se mantiene al localizar/crear y bloquear una clave, evitando condiciones de carrera en el primer acceso o desalojo sin serializar fragmentos no relacionados. El trabajo en una clave se realiza entonces solo bajo el bloqueo de esa clave.

Las entradas inactivas expiran después de window_seconds más el tiempo requerido para recargar un grupo de ráfagas vacío. La recolección LRU oportunista proporciona una retención limitada bajo cargas de trabajo de clave única; reap() permite un mantenimiento determinista/completo. Para limit == 0, la recarga es cero y el estado inactivo se olvida después de una ventana, por lo que una identidad que regresa después de ese intervalo de retención de caché comienza de nuevo.

Resultado

#1 | Ganador

Votos ganadores

3 / 3

Puntuación media

75
Modelos evaluadores Google Gemini 3.5 Flash-Lite

Puntuación total

97

Comentario general

La respuesta B proporciona una implementación excepcionalmente rigurosa y lista para producción. Presenta un sofisticado diseño de bloqueo de dos niveles (bloqueos de fragmento para búsqueda/creación más bloqueos individuales por clave para modificación de estado) que elimina por completo los cuellos de botella de contención de fragmentos completos y maneja de forma segura el primer acceso concurrente sin condiciones de carrera. Su lógica retry_after está meticulosamente calculada, se cubren completamente los casos extremos como ventanas de sub-milisegundos y límites cero, y el conjunto de pruebas es completo y determinista, incluyendo una prueba explícita de primer acceso concurrente sincronizada por barrera.

Ver detalle de evaluación

Corrección

Peso 35%
95

Semántica de ventana deslizante exacta e impecable, cálculo extremadamente preciso de retry_after considerando tanto las expiraciones de ventana como los reabastecimientos de tokens, y manejo robusto de todos los casos extremos.

Integridad

Peso 20%
100

Implementa completamente todos los requisitos, casos extremos, estructuras de datos, notas de diseño y un conjunto de pruebas determinista ejemplar que cubre concurrencia, condiciones límite y regresión temporal.

Calidad del código

Peso 20%
95

Calidad de código excepcional con atención meticulosa a la seguridad de hilos, separación limpia de responsabilidades, sugerencias de tipos robustas y estructuras de retorno inmutables.

Valor práctico

Peso 15%
95

Diseño práctico sobresaliente que utiliza bloqueos finos por clave protegidos por bloqueos de búsqueda de fragmentos, asegurando una contención mínima y una higiene de memoria acotada robusta.

Seguimiento de instrucciones

Peso 10%
100

Se adhiere estrictamente a cada instrucción, restricción, caso extremo y requisito sin excepción.

Modelos evaluadores OpenAI GPT-6 Astra

Puntuación total

61

Comentario general

La respuesta B proporciona un modelado de estado más claro, cuenta todos los costos admitidos en la ventana deslizante, maneja correctamente el recarga limitada en los cálculos de reintento e incluye API útiles de observabilidad y mantenimiento explícito. Sus pruebas de concurrencia deterministas son significativas. Sin embargo, la limpieza inactiva restaura incorrectamente los créditos gastados cuando el límite es cero, dos pruebas proporcionadas contradicen el comportamiento de la implementación real y la limpieza de instantáneas no es segura cuando va seguida de marcas de tiempo más antiguas. Es un punto de partida más sólido, pero aún requiere correcciones de precisión.

Ver detalle de evaluación

Corrección

Peso 35%
53

Registra todos los costos admitidos y restringe correctamente los candidatos de recarga basados en recarga a la capacidad de ráfaga alcanzable. Sin embargo, las entradas con límite cero se eliminan después de una ventana y reciben créditos nuevos a pesar de una tasa de recarga cero. La limpieza futura de instantáneas también puede eliminar el historial sin avanzar la última hora, lo que permite que las llamadas posteriores más antiguas operen sobre un estado prematuramente expirado.

Integridad

Peso 20%
67

Incluye todos los entregables principales, una instantánea detallada, un método de recolección explícito y una amplia cobertura de casos extremos. La cobertura se ve debilitada por las pruebas fallidas de límite cero y marca de tiempo, y la política de reinicio de límite cero documentada no satisface la semántica de recarga especificada.

Calidad del código

Peso 20%
68

Las clases de datos bien estructuradas, la validación explícita, los registros de tiempo igual coalescidos y la liberación confiable de bloqueos mejoran la mantenibilidad. Sin embargo, adquirir un bloqueo de clave mientras se mantiene su bloqueo de partición crea bloqueo de cabeza de línea, y las expectativas de prueba son inconsistentes con el comportamiento documentado.

Valor práctico

Peso 15%
56

Una instantánea que soporta el uso, una API de mantenimiento explícita y pruebas de límite de admisión de reloj fijo facilitan la integración y el diagnóstico. La implementación aún requiere la corrección de los reinicios de crédito de límite cero y las regresiones de instantáneas. La recolección completa también escanea y copia los mapeos de partición mientras mantiene sus bloqueos, lo que merece una guía operativa más clara.

Seguimiento de instrucciones

Peso 10%
65

Sigue el formato de entrega solicitado y proporciona pruebas de reloj ficticio deterministas con límites de concurrencia significativos. La suite, sin embargo, falla: la prueba de regresión espera un segundo donde la recarga permite el éxito después de medio segundo, y la prueba de límite cero espera el rechazo después de que la implementación ha eliminado y reiniciado la clave.

Modelos evaluadores Anthropic Claude Fable 5.1

Puntuación total

69

Comentario general

La respuesta B implementa una ventana deslizante exacta con coalescencia de marcas de tiempo iguales, un grupo de ráfagas continuo correctamente limitado, una simulación de retry_after que protege adecuadamente la ruta de recarga con los créditos de ráfaga <= necesarios, bloqueo de dos niveles (bloqueo de fragmento para mapa/LRU, bloqueo por clave para estado) con adquisición no bloqueante durante la recolección para evitar condiciones de carrera en el primer acceso y la expulsión, una recolección oportunista de una entrada por llamada más un reap() explícito, y manejo defensivo de regresiones de reloj, entradas no finitas, ventanas de sub-milisegundo y límite == 0. Las pruebas de concurrencia son deterministas a través de un FakeClock seguro para hilos y afirman máximos teóricos exactos (17 de 100, 1 de 20). Debilidades: dos aserciones de prueba son incorrectas en relación con la implementación (test_non_monotonic espera retry_after 1.0 donde el valor correcto basado en la recarga es 0.5; test_limit_zero espera un rechazo en now=1.0 pero la clave se recolecta después de idle_ttl=window y se recrea con créditos frescos, un comportamiento que la propia nota de diseño documenta). La anulación de límite==0 efectivamente permite que los créditos de ráfaga se regeneren en contra de una tasa de recarga cero, y el bloqueo por clave se adquiere mientras se mantiene el bloqueo de fragmento, por lo que una clave caliente puede detener su fragmento. Snapshot devuelve un tipo Snapshot separado en lugar de Decision, lo cual es aceptable.

Ver detalle de evaluación

Corrección

Peso 35%
65

La expiración de la ventana deslizante en el límite, la recarga limitada, los costos todo o nada y el retry_after (protegido por needed <= burst_credits) son correctos; las pruebas de concurrencia afirman máximos exactos. Dos expectativas de prueba son incorrectas (retry_after 1.0 vs 0.5 correcto; la clave con límite==0 se recolecta después de idle_ttl y se recrea), y la anulación de límite==0 permite que los créditos de ráfaga se regeneren a pesar de una tasa de recarga cero, aunque esto está documentado.

Integridad

Peso 20%
75

La implementación, la nota de diseño y las pruebas cubren todos los escenarios requeridos más extras: ventana de sub-milisegundo, primer acceso concurrente, no mutación de snapshot, marcas de tiempo no monotónicas y un reap() explícito para mantenimiento determinista. Snapshot expone uso, restante y créditos.

Calidad del código

Peso 20%
70

Estado limpio basado en dataclass, separación clara de bloqueos de fragmento vs clave, orden de bloqueo correcto con recolección no bloqueante, ayudantes consistentes, sin importaciones no utilizadas y un FakeClock seguro para hilos. La simulación de reintentos es densa, la validación de entrada es algo pesada, y adquirir el bloqueo por clave bajo el bloqueo de fragmento puede detener un fragmento en una clave caliente.

Valor práctico

Peso 15%
65

Utilizable tal cual para limitación de gateway con indicaciones de reintento precisas, memoria limitada a través de recolección LRU más reap(), y pruebas deterministas adecuadas para CI una vez corregidas dos aserciones erróneas. La anulación de crédito límite==0 después de inactividad es una advertencia semántica documentada pero real.

Seguimiento de instrucciones

Peso 10%
75

Sigue la forma de la API, solo biblioteca estándar, sin asyncio, nota de diseño de menos de 300 palabras que cubre estructura de datos, compensación y bloqueo, pruebas deterministas de reloj falso que incluyen el límite multihilo, y ejercita explícitamente cada caso límite listado.

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

0 / 3

Puntuación media

58
Ver esta respuesta

Votos ganadores

3 / 3

Puntuación media

75
Ver esta respuesta

Resultados de evaluación

Modelos evaluadores Anthropic Claude Fable 5.1

Motivo del ganador

B gana en el criterio de corrección fuertemente ponderado: su lógica principal de admisión, límite de recarga y retry_after es correcta, mientras que A tiene un error genuino en retry_after en la configuración predeterminada burst_credits=0. Ambos conjuntos de pruebas contienen dos aserciones fallidas, pero los fallos de A provienen de un comentario de límite que contradice la especificación y un defecto en la prueba entre fragmentos, mientras que el comportamiento de implementación de B en esos casos es defendible y está documentado. B también sigue el requisito de reloj simulado determinista para sus pruebas multihilo, cubre explícitamente más casos extremos exigidos (concurrencia de primer acceso, ventanas de sub-milisegundo, no mutación de instantáneas) y tiene un diseño de bloqueo de dos niveles más limpio con una política de recolección documentada y acotada. Ponderado en todos los criterios, B está claramente por delante.

Modelos evaluadores OpenAI GPT-6 Astra

Motivo del ganador

La respuesta B gana porque su contabilidad de admisiones y el cálculo de reintentos conscientes de la capacidad son sustancialmente más sólidos, y su implementación, observabilidad, validación y cobertura de concurrencia determinista satisfacen mejor los entregables. Estas ventajas superan sus importantes defectos de desalojo de límite cero y manejo de marcas de tiempo. La respuesta A, además, tiene errores fundamentales de reintento, contabilidad incompleta de ventana deslizante y una política de desalojo que puede quedar permanentemente bloqueada.

Modelos evaluadores Google Gemini 3.5 Flash-Lite

Motivo del ganador

La respuesta B gana porque su diseño de concurrencia es muy superior, utilizando un mecanismo de bloqueo de dos niveles (bloqueo de fragmento más bloqueo de clave individual) que evita la alta contención y maneja de forma segura las carreras concurrentes de primer acceso y desalojo. La respuesta B también demuestra una corrección superior en casos extremos complejos, cálculos precisos de reintento y un conjunto de pruebas más completo que incluye la verificación explícita del primer acceso concurrente.

X f L