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
Votos ganadores
3 / 3
Puntuación media
Puntuación total
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%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%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%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%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%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.
Puntuación total
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%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%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%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%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%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.
Puntuación total
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%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%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%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%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%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.