lección 7
Día 5 -- Circuit breaker y documentación
Un GPS defectuoso inunda el stream con 50 eventos/segundo de basura. Implementas un circuit breaker que detecta anomalias y aisla dispositivos problematicos sin afectar al resto del pipeline.
⏱ 40 min
### Viernes, 9:30 AM -- Un GPS enloquecido
El standup del viernes es rápido. Todos están cansados después de Black Friday. Laura menciona que facturación se resintió ayer con tanto volumen, Diego celebra que su algoritmo de rutas funcionó bien bajo presión. Cuando te toca, cuentas el incidente de ayer y la solución. Marcos asiente: "Buen trabajo. Hoy quiero dos cosas: un circuit breaker para GPSs que se vuelven locos, y documentación de la arquitectura. El lunes tu pipeline debe poder sobrevivir sin ti."
Como si el universo quisiera darte contexto, a las 10:15 Carlos te escribe: "Ey, el DRV-0312 está enviando 50 eventos por segundo en vez de 1 cada 5 segundos. Su smartphone se ha vuelto loco -- está en un bucle de retry infinito. Está generando el 25% del tráfico total del topic. Puedo matar su sesión desde el backend pero tarda 5 minutos en propagarse. Necesitamos algo downstream que lo detecte y lo ignore automáticamente."
### El problema: un dispositivo roto satura el pipeline
Un solo smartphone defectuoso enviando 50 ev/s (vs 0.2 ev/s normal) puede consumir el 25% de la capacidad del pipeline. Si 3 smartphones se vuelven locos simultáneamente, el pipeline se cae. Y lo peor: son datos VÁLIDOS (pasan los filtros de sanidad) porque son coordenadas reales a velocidad real -- simplemente llegan 250 veces más rápido de lo esperado.
- Tasa normal: 1 evento cada 5 segundos = 0.2 ev/s por driver
- DRV-0312 roto: 50 ev/s = 250x la tasa normal
- Impacto: consume 50 de los 200 ev/s de capacidad normal (25%)
- Los filtros de sanidad NO lo detectan (datos validos, solo demasiado frecuentes)
- Carlos puede matar sesiones pero tarda 5 minutos -- necesitas protección inmediata
### Solución: circuit breaker por tasa de eventos
Un circuit breaker es un patrón de ingeniería de software que "corta el circuito" cuando detecta una anomalía. En tu caso: si un driver_id envía más de N eventos en una ventana de T segundos, lo marcas como "tripped" y descartas sus eventos hasta que su tasa vuelva a la normalidad. Es como un fusible que se funde para proteger el circuito.
1from datetime import datetime, timedelta2from collections import defaultdict34class GPSCircuitBreaker:5 """6 Circuit breaker para detectar y aislar GPSs defectuosos.78 Estados por driver:9 - CLOSED: funcionamiento normal, eventos pasan10 - OPEN: driver aislado, eventos descartados11 - HALF_OPEN: período de prueba, si la tasa baja vuelve a CLOSED1213 Umbrales:14 - max_events_per_window: máximo eventos permitidos en la ventana15 - window_seconds: tamaño de la ventana de conteo16 - cooldown_seconds: tiempo que un driver permanece en OPEN17 """1819 def __init__(20 self,21 max_events_per_window: int = 10, # 10 ev en 30s = ~0.33/s (normal es 0.2/s)22 window_seconds: int = 30,23 cooldown_seconds: int = 300, # 5 minutos de cuarentena24 ):25 self.max_events = max_events_per_window26 self.window_seconds = window_seconds27 self.cooldown_seconds = cooldown_seconds2829 # Estado por driver30 self.event_counts: dict[str, list[datetime]] = defaultdict(list)31 self.tripped_drivers: dict[str, datetime] = {} # driver -> trip time32 self.stats = {"passed": 0, "blocked": 0, "trips": 0}3334 def should_allow(self, driver_id: str, event_time: datetime) -> bool:35 """36 Decide si un evento debe pasar o ser bloqueado.37 Retorna True si el evento es aceptado, False si bloqueado.38 """39 # Si está en cuarentena, verificar si el cooldown expiro40 if driver_id in self.tripped_drivers:41 trip_time = self.tripped_drivers[driver_id]42 if (event_time - trip_time).total_seconds() < self.cooldown_seconds:43 self.stats["blocked"] += 144 return False # Sigue en cuarentena45 else:46 # Cooldown expirado -> half-open -> reset47 del self.tripped_drivers[driver_id]48 self.event_counts[driver_id] = []4950 # Limpiar eventos fuera de la ventana51 window_start = event_time - timedelta(seconds=self.window_seconds)52 self.event_counts[driver_id] = [53 t for t in self.event_counts[driver_id] if t >= window_start54 ]5556 # Anadir evento actual57 self.event_counts[driver_id].append(event_time)5859 # Verificar si excede el umbral60 if len(self.event_counts[driver_id]) > self.max_events:61 # TRIP! Aislar este driver62 self.tripped_drivers[driver_id] = event_time63 self.stats["trips"] += 164 self.stats["blocked"] += 165 return False6667 self.stats["passed"] += 168 return True6970 def get_tripped_drivers(self) -> list[str]:71 """Retorna lista de drivers actualmente en cuarentena."""72 return list(self.tripped_drivers.keys())7374 def get_stats(self) -> dict:75 """Estadisticas del circuit breaker."""76 return {77 **self.stats,78 "active_trips": len(self.tripped_drivers),79 "block_rate_pct": (80 self.stats["blocked"] /81 (self.stats["passed"] + self.stats["blocked"]) * 10082 if (self.stats["passed"] + self.stats["blocked"]) > 0 else 083 ),84 }
Circuit breaker: detecta GPSs con tasa anomala y los aisla automaticamente
El umbral del circuit breaker (10 eventos en 30 segundos) se elige así: un GPS normal envia 1 evento cada 5 segundos = 6 eventos en 30s. Con 10 como umbral, permites algo de variabilidad (reintentos legitimos, burst post-tunel) sin disparar falsos positivos. Si un GPS envia 11+ en 30s, algo está mal.
### Integrar el circuit breaker en Spark Streaming
El circuit breaker funciona dentro del foreachBatch: antes de procesar el micro-batch, filtras los eventos de drivers que están en cuarentena. El estado del circuit breaker se mantiene en el driver de Spark (no en executors) porque necesitas una vista global de todos los drivers.
1# Integrar circuit breaker en el pipeline de Spark23# El CB se inicializa una vez en el driver4circuit_breaker = GPSCircuitBreaker(5 max_events_per_window=10,6 window_seconds=30,7 cooldown_seconds=3008)910def process_with_circuit_breaker(batch_df, batch_id):11 """12 foreachBatch que aplica el circuit breaker antes de procesar.13 """14 if batch_df.isEmpty():15 return1617 rows = batch_df.collect()1819 # Filtrar con circuit breaker20 allowed_rows = []21 for row in rows:22 event_time = row['event_time']23 if circuit_breaker.should_allow(row['driver_id'], event_time):24 allowed_rows.append(row)2526 # Loggear si hay drivers aislados27 tripped = circuit_breaker.get_tripped_drivers()28 if tripped:29 stats = circuit_breaker.get_stats()30 print(f"[Batch {batch_id}] Circuit breaker: "31 f"{len(tripped)} drivers aislados, "32 f"{stats['block_rate_pct']:.1f}% eventos bloqueados")33 # Alertar a Carlos para que investigue los dispositivos34 # alert_slack(f"GPS defectuosos detectados: {tripped}")3536 # Procesar solo eventos permitidos37 if allowed_rows:38 allowed_df = spark.createDataFrame(allowed_rows, batch_df.schema)39 # ... calcular ETA, escribir a DynamoDB ...4041# Uso en el pipeline42query = clean_gps_stream.writeStream \43 .foreachBatch(process_with_circuit_breaker) \44 .trigger(processingTime="30 seconds") \45 .start()
Integración del circuit breaker en el foreachBatch de Spark
El circuit breaker tiene un trade-off: si es demasiado agresivo (umbral bajo), puede bloquear repartidores legitimos que salen de un tunel con un burst. Si es demasiado permisivo (umbral alto), un GPS defectuoso causa dano antes de ser aislado. El valor de 10 en 30s es conservador -- un burst post-tunel envia 6-8 eventos de golpe y no dispara el CB.
### Documentación: Architecture Decision Record
Marcos te pidió documentación. En FlashRoute la documentación técnica se hace con ADRs (Architecture Decision Records) -- documentos cortos que explican QUÉ decisión tomaste, POR QUÉ, qué alternativas consideraste y cuáles son las consecuencias. Es el formato que aprendiste en NeoBank con Alberto.
1# ADR-007: Pipeline de ETAs en tiempo real2# Status: Accepted3# Date: 2024-11-224# Author: tú56## Contexto7FlashRoute necesita ETAs actualizados cada 30 segundos para 1000+8repartidores activos. Ana y su equipo de coordinadores toman9decisiones de reasignacion basadas en estos datos.1011## Decision12Implementar pipeline con Spark Structured Streaming:13- Fuente: Kafka topic driver.location14- Calculo: Formula Haversine + factor urbano 1.4x15- Sink: DynamoDB (tabla delivery_etas, TTL 2h)16- Trigger: cada 30 segundos (processingTime)17- Proteccion: watermark 30s + dedup + circuit breaker1819## Alternativas consideradas2021## Consecuencias22- Positivas: latencia <30s, escalable (probado 3x en Black Friday)23- Negativas: complejidad operativa, requiere EMR cluster activo 24/724- Riesgos: single point of failure (mitigado con checkpoints)2526## Metricas de éxito27- Latencia dashboard: < 30 segundos (p99)28- Precision ETA: error < 5 minutos para distancias < 5km29- Disponibilidad: 99.9% uptime (max 8 min downtime/semana)
ADR-007: documenta la arquitectura del pipeline de ETAs
### La reunión con Ana -- validar el dashboard
A las 12:00 tienes la reunión que Marcos te programó el lunes: enseñar a Ana el dashboard de SLAs. Llevas datos de toda la semana (incluyendo Black Friday). Ana mira la pantalla y va directa a lo que importa: "El SLA global está en 94.8% esta semana. Estamos por debajo del 95% contractual. Qué zonas están peor?" Le muestras el desglose por zona. "Madrid-sur: 91.2%. Ahí es donde tenemos menos repartidores. Necesito contratar 50 más para esa zona." Dato -> decisión -> acción. Exactamente para lo que existe tu pipeline.
Ana te pregunta algo más: "Puedo ver esto en mi móvil?" Le dices que Grafana tiene app móvil. "Perfecto. Confígurámelo." Lo anotas. No es tu responsabilidad pero es buena relación con tu stakeholder principal. 5 minutos de configuración que te dan credibilidad con la persona que más usa tu sistema.
## ejercicios
Implementar circuit breaker para GPS
Implementa un circuit breaker completo que detecte GPSs con tasa anomala, los aisle y los libere después de un cooldown. Prueba con un escenario mixto de drivers normales y defectuosos.
Deteccion de anomalias en tasas GPS
Implementa un sistema que analice la tasa de eventos por driver en una ventana y detecte anomalias usando estadisticas simples (media + desviacion estandar). Genera alertas cuando un driver está significativamente por encima de lo normal.
Generar ADR para decisiones de arquitectura
Implementa un generador de Architecture Decision Records (ADR) que tome los parametros clave de una decisión técnica y produzca un documento estructurado. Usa los datos de tu pipeline de FlashRoute como ejemplo.
Regístrate para guardar tu progreso.
## comentarios
Reporta erratas, ayuda a otros o comparte tu opinión. Sé constructivo.
Inicia sesión para comentar y responder.
cargando comentarios...