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

Platform Engineer + CNCF Ambassador, AWS community builder. I design scalable cloud-native platforms and love making teams faster and safer.
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)
# 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
# Llegaron 10K nuevas transacciones
# El modelo PUEDE aprender algo nuevo
if new_data.count >= 10000:
trigger_training()
2. Degradación detectada
# El modelo está fallando en prod
# El accuracy bajó 5%
if current_accuracy < baseline - 0.05:
trigger_emergency_training()
3. Código/features cambiaron
# 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)
# 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
# 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 (>$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
@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 > 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)
# 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:
Streaming (segundos) → Event-driven
Batch diario → Daily schedule
Batch semanal → Weekly schedule
Batch mensual → Monthly schedule
Irregular → Event-driven
Costo de training:
<$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:
Alta (fraude, spam) → Hybrid con monitoring
Media (recomendaciones) → Time-based + events
Baja (analytics) → Time-based conservador
Ejemplo real de arquitectura:
┌─────────────────────────────────────┐
│ 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
@schedule("@weekly")
def train():
# Simple, predecible, suficiente para empezar
Fase 2 - Añade monitoring:
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
@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?


