# Time-based vs Event-driven: Por qué no es solo cron vs webhooks

Ayer: qué orquestador usar.  
Hoy: **cuándo ejecutar ese orquestador.**

**El setup más común que veo: ( No sean asi, xD quieran a su PE)**

```python
# En el orquestador de tu elección
@schedule("0 2 * * *")  # Todos los días 2am
def train_model():
    data = fetch_data()
    model = train(data)
    deploy(model)
```

**Pregunta simple:** ¿Por qué 2am? ¿Por qué todos los días?

**Respuesta común:** Porque... ¿ventana de bajo tráfico?

**Pero la pregunta real es:**

**"¿Qué evento justifica gastar $X en compute para re-entrenar?"**

Porque re-entrenar cuesta:

* Compute (GPUs no son gratis)
    
* Tiempo (tu pipeline bloquea recursos)
    
* Riesgo (cada deploy es un posible incidente)
    

**Si nada cambió, ¿para qué re-entrenar?**

---

## **Los 3 triggers legítimos:**

**1\. Datos nuevos**

```python
# Llegaron 10K nuevas transacciones
# El modelo PUEDE aprender algo nuevo
if new_data.count >= 10000:
    trigger_training()
```

**2\. Degradación detectada**

```python
# El modelo está fallando en prod
# El accuracy bajó 5%
if current_accuracy < baseline - 0.05:
    trigger_emergency_training()
```

**3\. Código/features cambiaron**

```python
# Agregaste un nuevo feature
# Cambiaste hiperparámetros
if code_changed() or features_changed():
    trigger_ci_training()
```

**Si ninguno de estos 3 pasó, probablemente no necesitas re-entrenar.**

---

### **Pattern 1: Time-based (Batch)**

```python
# Airflow, Prefect, Kubeflow, etc.
@schedule("@weekly")
def training_pipeline():
    train_model()
```

**Trade-offs:**

| Pros | Contras |
| --- | --- |
| Simple de implementar | Puede ser desperdicio |
| Predecible | No responde a cambios reales |
| Fácil de debuggear | Puede entrenar con datos malos |
| Conocido por equipos | Latencia hasta próximo run |

**Úsalo cuando:**

* Tus datos llegan en schedule predecible
    
* El costo de entrenar es bajo
    
* Prefieres simplicidad sobre optimización
    

**No lo uses cuando:**

* Los datos llegan irregularmente
    
* Re-entrenar es muy caro
    
* Necesitas respuesta inmediata a cambios
    

---

### **Pattern 2: Event-driven**

```python
# Trigger por evento externo
@trigger(bucket="s3://data", event="new_file")
def training_pipeline():
    if new_data_meets_threshold():
        train_model()
```

**Trade-offs:**

| Pros | Contras |
| --- | --- |
| Solo entrenas cuando necesitas | Más complejo de implementar |
| Optimiza cómputo | Debugging más difícil |
| Responde a eventos reales | Necesitas infraestructura de eventos |
| Escala mejor | Puede disparar demasiado |

**Úsalo cuando:**

* Los datos llegan irregularmente
    
* Re-entrenar es caro (&gt;$100/run)
    
* Tienes infraestructura de eventos (S3, Kafka, etc.)
    

**No lo uses cuando:**

* Tu equipo no tiene experiencia con eventos
    
* Los datos llegan consistentemente
    
* Prefieres simplicidad
    

---

### **Pattern 3: Metric-driven**

```python
@schedule("@hourly")  # Check frecuente
def monitor_and_train():
    metrics = get_production_metrics()
    
    if metrics.accuracy < THRESHOLD:
        trigger_emergency_retrain()
        alert_team()
```

**Trade-offs:**

| Pros | Contras |
| --- | --- |
| Responde a problemas reales | Requiere monitoring robusto |
| Optimiza para calidad | Puede tener falsos positivos |
| Proactivo ante drift | Latencia entre detección y fix |
| Justifica cada re-entreno | Complejo de implementar bien |

**Úsalo cuando:**

* Puedes medir performance en producción
    
* El costo de modelo malo &gt; costo de re-entrenar
    
* Tienes alerting robusto
    

**No lo uses cuando:**

* No tienes métricas confiables en prod
    
* El re-entrenamiento toma demasiado tiempo
    
* Tu modelo no degrada predeciblemente
    

---

### **Pattern 4: Hybrid (Real-world)**

```python
# Lo que REALMENTE usamos en producción

# Baseline: time-based conservador
@schedule("@weekly")
def baseline_training():
    train_model(priority="normal")

# Emergency: metric-driven
@monitor(metric="accuracy", threshold=0.85)
def emergency_training():
    train_model(priority="high")
    alert_oncall()

# CI/CD: code-driven
@on_merge(branch="main")
def ci_training():
    if features_changed():
        train_model(priority="normal")
```

**Esta es la realidad en prod:**

* 80% de los trainings → Time-based baseline
    
* 15% → Triggered por CI/CD
    
* 5% → Emergency por métricas
    

---

**Criterios de decisión:**

**Frecuencia de datos:**

```python
Streaming (segundos) → Event-driven
Batch diario → Daily schedule
Batch semanal → Weekly schedule
Batch mensual → Monthly schedule
Irregular → Event-driven
```

**Costo de training:**

```python
<$10/run → Time-based frecuente está OK
$10-100/run → Time-based + basic triggers
$100-1000/run → Event-driven + monitoring
>$1000/run → Solo cuando absolutamente necesario
```

**Criticidad de estar actualizado:**

```python
Alta (fraude, spam) → Hybrid con monitoring
Media (recomendaciones) → Time-based + events
Baja (analytics) → Time-based conservador
```

---

**Ejemplo real de arquitectura:**

```python
┌─────────────────────────────────────┐
│     Data Pipeline (Event-driven)     │
│   S3 → Trigger → Validation → ...   │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│  Training Orchestrator (Hybrid)     │
│                                     │
│  1. Baseline: @weekly              │
│  2. On data threshold: >10K rows   │
│  3. On metric drop: <85% accuracy  │
│  4. On code change: features/*     │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│    ML Pipeline (Orquestado)        │
│  ingest → train → evaluate → ...   │
└─────────────────────────────────────┘
```

---

**Los errores que he visto:**

**ERROR 1: "Entrenar cada hora por si acaso"**

> Costo mensual: $432 en compute Beneficio: Modelo 0.1% mejor ROI: Negativo

**ERROR 2: "Solo entrenar manualmente"**

> Resultado: Se olvidan durante semanas El modelo degrada sin que nadie note Cliente se queja primero

**ERROR 3: "Re-entrenar en cada commit"**

> 100 commits/día × $10/training = $1000/día 99% de esos trainings no cambian nada

**LO CORRECTO:**

> Baseline predecible + triggers inteligentes Monitoreo que justifica cada training Balance entre frescura y costo

**Mi recomendación pragmática:**

**Fase 1 - MVP:**

python

```python
@schedule("@weekly")
def train():
    # Simple, predecible, suficiente para empezar
```

**Fase 2 - Añade monitoring:**

python

```python
@schedule("@weekly")
def train():
    train_model()
    
@alert(metric="accuracy < 0.85")
def notify():
    # Solo alerta, no auto-retrain aún
```

**Fase 3 - Event-driven si lo necesitas:**

python

```python
@schedule("@weekly")
def baseline_train():
    pass

@trigger(on_data_threshold)
def event_train():
    pass
```

**No saltes a Fase 3 si Fase 1 es suficiente.**

---

**Mañana hablamos de environments:**

Dev, staging, producción... pero para ML.

Donde "environment" no es solo código diferente, sino:

* Datasets diferentes
    
* Modelos diferentes
    
* Métricas diferentes
    
* Infraestructura diferente
    

**Spoiler:** `if ENV == "prod"` no es suficiente.

---

**Pregunta:**

* ¿Qué pattern usas para scheduling?
    
* ¿Te has cuestionado si es el correcto?
