Recovery y operaciones
Guía umbrella para recuperación de datos, failover, DLQ, backfills y housekeeping operativo de Relo.
Recovery y operaciones
Esta guía agrupa los procedimientos que usamos cuando algo sale mal o hay que re-procesar datos históricos. Cubre R2 WAL replay, DLQ recovery, failover entre ingest paths, backfills de reglas, limpieza de datos de QA y rotación de tokens.
Antes de empezar Si no estás seguro del alcance, revisa primero System health para ver el estado actual de pipelines, DLQ y componentes.
Chequeo de salud del pipeline
El servicio Go expone un endpoint único de salud. Llámalo desde cualquier lado a través del tunnel o directo en el servidor:
# A través del Cloudflare Tunnel
curl -s https://ingest.relo.mx/health | jq
# Directo en el servidor Hetzner
ssh root@178.156.195.219
curl -s http://localhost:4080/health | jq
Campos clave
| Campo | Sano cuando... | Rojo cuando... |
|---|---|---|
buffer_events | 0 (todo descargado a CH) | Número que sube sostenidamente. |
dlq_depth {clicks,pixel,s2s} | Todo en cero | Crece persistentemente. |
last_pull_at | Dentro de la última hora | Está viejo. |
last_pull_stats.errors | 0 | Errores > 0 o cero purchases en horario hábil. |
failover.active | false | true (hay failover activo). |
recovery.health_check | {clickhouse_ok,r2_wal_ok,supabase_ok,dragonfly_ok} todo true | Alguno es false. |
Baseline sano: buffer_events: 0, dlq_depth todo cero, last_pull_at reciente, last_pull_stats.errors: 0, failover.active: false y los cuatro flags de health check en true.
Redes de seguridad de la ingesta
Entender estas capas hace que cada acción de recovery sea segura de re-ejecutar:
- AppsFlyer Pull API cada hora. Deduplica por
sale_hash; re-jalar cualquier ventana nunca duplica conteos. - Write-ahead log en R2. Cada lote se escribe a R2 antes del insert en CH.
- ReplacingMergeTree. Re-insertar el mismo
event_idcolapsa en el siguiente merge. - Loops de DLQ. Las fallas en rutas vivas caen a DLQ y se re-procesan automáticamente.
R2 WAL replay
Relo escribe cada evento primero en un write-ahead log (WAL) en Cloudflare R2 antes de intentar insertarlo en ClickHouse. Esto significa que, si ClickHouse rechaza un batch o el buffer se desborda, los eventos originales siguen guardados en R2.
¿Cuándo usarlo?
- ClickHouse estuvo caído y eventos llegaron al backbone.
- El buffer de CH se desbordó (
WAL replay neededen System health). - Recibiste una alerta de R2 WAL recovery o ves faltante de eventos recientes.
Layout de R2 WAL
relo-event-wal/
├── wal/events/YYYY/MM/DD/HH/{ulid}.ndjson.gz
├── wal/clicks/YYYY/MM/DD/HH/{ulid}.ndjson.gz
└── receipts/{kind}/YYYY/MM/DD/{ulid}.json
Replay rápido por API (últimas horas)
El backbone expone un endpoint administrativo para re-encolar eventos del WAL al writer de ClickHouse:
curl -X POST "https://ingest.relo.mx/admin/wal-replay?hours=6" \
-H "Authorization: Bearer $ADMIN_TOKEN"
| Parámetro | Default | Máximo | Uso |
|---|---|---|---|
hours | 6 | 48 | Ventana hacia atrás desde ahora. |
La respuesta indica cuántos archivos escaneó, cuántos eventos re-encoló y errores por archivo. Es idempotente: la tabla events es ReplacingMergeTree con event_id, así que re-insertar eventos ya presentes colapsa en el merge.
Replay masivo por CLI (disaster recovery)
Para reconstruir ClickHouse desde cero o para un rango de días específico, usa el comando replay en backbone/cmd/replay:
cd backbone
go build -o bin/replay ./cmd/replay
export CF_ACCOUNT_ID="..."
export CF_API_TOKEN="..."
export CF_R2_WAL_BUCKET="relo-event-wal"
export CH_ADDR="127.0.0.1:9000"
export CH_DATABASE="relo"
# Listar archivos sin insertar (dry-run)
./bin/replay --from 2026-07-01 --to 2026-07-09 --dry-run
# Reproducir eventos de un cliente específico
./bin/replay --from 2026-07-01 --to 2026-07-09 --client 3
# Reproducir clicks
./bin/replay --from 2026-07-01 --to 2026-07-09 --prefix wal/clicks
Verificación diaria del WAL
El propio R2WAL puede verificar que los archivos recientes sean parseables. Úsalo en un cron operativo para detectar corrupción silenciosa:
AppsFlyer re-pull (Recovery A)
Usa esto cuando el hueco es de data originada en AppsFlyer (compras jaladas de AF). Como el pull deduplica por sale_hash, es seguro re-ejecutar cualquier ventana.
Detectar el hueco
ssh root@178.156.195.219 "clickhouse-client -d relo -q \
\"SELECT toDate(event_time) AS d, count() AS n
FROM relo.events
WHERE event_time >= now() - INTERVAL 14 DAY
GROUP BY d ORDER BY d\""
Una caída inesperada en el conteo diario vs días vecinos es la señal.
Disparar pull manual
curl -X POST https://ingest.relo.mx/pull/trigger \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"from":"2026-05-28T00:00:00Z","to":"2026-05-30T00:00:00Z"}'
Respuesta típica:
{"ok":true,"status":"started","note":"pull running in background — watch /health last_pull_at"}
Jala ventanas estrechas (2–3 días a la vez). Un rango enorme inunda el buffer de CH de golpe. Recorre huecos largos en pulls cortos y observa buffer_events bajar a 0 entre cada uno.
Leer ADMIN_TOKEN del servidor
Para no pegar el secreto en tu terminal local:
ssh root@178.156.195.219
ADMIN_TOKEN=$(systemctl show relo-ingest -p Environment \
| tr ' ' '\n' | grep '^ADMIN_TOKEN=' | cut -d= -f2-)
curl -X POST http://localhost:4080/pull/trigger \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"from":"2026-05-28T00:00:00Z","to":"2026-05-30T00:00:00Z"}'
Nota de escalación
La Pull API de AppsFlyer tiene una ventana de lookback limitada. Si un re-pull reporta cero filas para una fecha que sabes que tuvo ventas, la data ya salió del rango servible de AF. Exporta la ventana desde el dashboard de AppsFlyer y reconcilia manualmente.
DLQ recovery
Cuando el backbone no puede procesar un evento (ClickHouse caído, regla rota, payload inválido), lo escribe a un dead letter queue (DLQ) en R2 para no perderlo. Los contadores de profundidad de DLQ se ven en System health → Data pipelines.
Tipos de DLQ
| Tipo | Trigger típico | Ruta aproximada en R2 |
|---|---|---|
| Clicks | KV caído o write fallido | dlq/clicks/... |
| S2S postbacks | Postback inválido o CH caído | dlq/s2s/... |
| Pixel | Pixel sin cid o evento mal formado | dlq/pixel/... |
Flujo de recuperación
Re-procesar desde el backbone
El monitor de salud del backbone rastrea la profundidad de DLQ (dlqClicksPending, dlqS2SPending, dlqPixelPending) y decrementa el contador cuando se recuperan eventos. Si la causa fue una caída temporal, reiniciar o re-encolar el procesamiento suele ser suficiente.
Para re-proceso manual, consulta el equipo de infra; típicamente implica:
- Listar archivos del prefijo DLQ correspondiente en R2.
- Validar un sample para descartar payloads rotos.
- Re-enviar eventos sanos al handler correspondiente o insertarlos directamente a CH.
Failover
La plataforma tiene tres caminos de ingesta que se usan como respaldo mutuo:
Estados de failover
El cron externo del API Worker revisa cada hora:
| Estado | Significado | Acción |
|---|---|---|
| All operational | Backbone responde status: ok | Ninguna. |
| Backbone DOWN, VPS UP | Eventos se bufferizan en VPS | Investigar backbone; VPS mantiene ingest. |
| Both DOWN | Solo R2 DLQ recibe datos | CRITICAL: atención inmediata; nada se procesa en tiempo real. |
Cómo forzar un health check
curl "https://relo-api.quomx.workers.dev/api/health"
curl "https://ingest.relo.mx/health"
curl "https://upload.relo.mx/health"
Recuperación tras falla total
- Restaurar backbone o VPS según corresponda.
- Revisar DLQ depth en System health.
- Correr R2 WAL replay para los eventos que solo llegaron al WAL.
- Re-procesar DLQ si aplica.
- Verificar que
partner_daily_aggregatesse actualiza (commission mirror no vacío).
Deploy y rollback del backbone
Cuando necesites actualizar el servicio Go en Hetzner:
# 1) Build local para Linux AMD64
cd backbone
GOOS=linux GOARCH=amd64 go build -o relo-ingest-new ./cmd/ingest/
# 2) Subir al servidor
scp relo-ingest-new root@178.156.195.219:/opt/relo/relo-ingest-new
# 3) Swap con backup, reiniciar y verificar
ssh root@178.156.195.219 "systemctl stop relo-ingest \
&& cp /opt/relo-ingest/relo-ingest /opt/relo-ingest/relo-ingest.bak \
&& cp /opt/relo/relo-ingest-new /opt/relo-ingest/relo-ingest \
&& chmod +x /opt/relo-ingest/relo-ingest \
&& systemctl start relo-ingest"
# 4) Verificar salud
curl -s https://ingest.relo.mx/health | jq '.status'
ssh root@178.156.195.219 "journalctl -u relo-ingest -f"
Rollback
Si /health no regresa limpio:
ssh root@178.156.195.219 "systemctl stop relo-ingest \
&& cp /opt/relo-ingest/relo-ingest.bak /opt/relo-ingest/relo-ingest \
&& systemctl start relo-ingest"
Backfill de reglas
Cuando cambias o creas una regla de segmento/atribución y quieres que aplique retroactivamente sobre eventos históricos, usa el backfill de reglas.
Endpoint
curl -X POST "https://ingest.relo.mx/admin/rules/:id/backfill" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"from": "2026-06-01",
"to": "2026-07-09",
"dry_run": true,
"trigger_recalc": false,
"event_types": ["af_purchase"]
}'
| Campo | Default | Descripción |
|---|---|---|
from | Hace 30 días | Inicio del rango (YYYY-MM-DD o RFC3339). |
to | Hoy | Fin del rango. |
dry_run | true | Si true, solo cuenta matches y devuelve muestras; no escribe. |
trigger_recalc | false | Si true, dispara recálculo de comisiones afectadas. |
event_types | Todos | Filtro opcional de tipos de evento. |
Límites de seguridad
- Máximo 90 días de rango.
- Máximo 50,000 eventos evaluados.
- Siempre corre
dry_run=trueprimero.
Qué pasa durante el backfill
- Se fuerza la regla como
activey se ignoraneffective_from/topara evaluar el match. - Se escanean eventos de ClickHouse para el cliente y rango.
- Los matches se insertan como nueva versión (
_version + 1) de los eventos. - Si
trigger_recalc=true, se recalculan comisiones para los(partner, año, mes)afectados.
Limpieza de datos de QA
Después de pruebas de postbacks, imports o pixels, es común dejar eventos de prueba mezclados. Límpialos antes de que afecten reportes o pagos.
Identificar datos de prueba
Usa un prefijo consistente para todo QA, por ejemplo TEST_:
action_iden postbacks:TEST_12345order_iden CSVs:TEST_ORDER_001- Campañas de prueba: nombra con
[QA].
Borrar de ClickHouse
ALTER TABLE relo.events DELETE
WHERE client_id = <client_id>
AND event_properties['action_id'] LIKE 'TEST_%';
Borrar de Supabase
DELETE FROM network_action_statuses
WHERE client_id = <client_id>
AND action_id LIKE 'TEST_%';
Borrar import de prueba
Si el import quedó registrado en import_files, marca como cancelled o bórralo según política interna:
UPDATE import_files
SET status = 'cancelled', error = 'QA cleanup'
WHERE id = <import_id>;
Rotación de tokens
Rotar tokens cuando:
- Se compartió una URL con
?key=...por chat/email/público. - Un ex-empleado o partner tenía acceso.
- Hay sospecha de uso no autorizado.
- Es parte del housekeeping periódico.
Tokens de fuentes externas (Everflow / GA4 / Admitad)
- Identifica el
iddeclient_data_sources. - Genera un nuevo token:
openssl rand -hex 32 - Actualiza solo el campo
auth_tokendentro deextra_config:UPDATE client_data_sources SET extra_config = jsonb_set( extra_config, '{auth_token}', to_jsonb('NUEVO_TOKEN'::text) ) WHERE id = <source_id>; - Verifica:
curl "$URL?key=NUEVO_TOKEN" # 200 curl "$URL?key=TOKEN_VIEJO" # 401 - Actualiza postbacks/configuraciones del partner con la nueva URL.
- Documenta la rotación en el runbook del cliente.
Tokens de upload de partners
Para rotar el Bearer token que un partner usa para subir CSVs al ingest:
curl -X POST "https://relo-api.quomx.workers.dev/api/partners/:id/upload-credentials/rotate" \
-H "Authorization: Bearer $ADMIN_TOKEN"
API keys de sistema
Las variables de entorno del Worker (SUPABASE_SERVICE_ROLE_KEY, MAILGUN_API_KEY, ANTHROPIC_API_KEY, etc.) se rotan vía wrangler secret put. Después de rotar:
- Re-despliega el Worker.
- Verifica
/api/health. - Revisa que el backbone pueda seguir llamando al Worker (
/api/admin/system-alert).
Checklist operativo mensual
| Tarea | Herramienta / comando | Frecuencia |
|---|---|---|
| Revisar DLQ depth | System health → Data pipelines | Semanal |
| Verificar WAL integrity | VerifyRecent vía cron | Diaria |
| Revisar drifts de comisión | System health → Commission parity | Semanal |
| Rotar tokens expuestos | Runbooks de rotación | Según incidente |
| Limpiar datos de QA | SQL de limpieza | Después de cada QA |
| Revisar alerts del monitor | Logs de relo-ingest + Mailgun | Diaria |
| Revisar backups de ClickHouse | backbone/scripts/backup-clickhouse.sh | Semanal |
| Housekeeping de ramas | git branch --merged main | Quincenal |
Troubleshooting
| Síntoma | Causa probable | Acción |
|---|---|---|
| Eventos faltan en dashboard | CH buffer overflow | R2 WAL replay |
| DLQ clicks sube | KV caído o write fallido | Revisar DragonflyDB, re-procesar DLQ |
| Comisiones no cuadran tras regla nueva | No se recalcularon | Backfill + trigger_recalc |
Import quedó processing horas | Queue message stuck | POST /api/v3/imports/reset-stuck o POST /api/imports/reset-stuck |
| Mirror de gated commission vacío | Aggregator no corrió | Verificar partner_daily_aggregates; re-correr aggregator |
| Backbone alerta CRITICAL | Ambos paths caídos | Escalar infra; todo ingesta a R2 DLQ |
Relación con otras guías
- System health — Salud en tiempo real de todos los componentes.
- Pipeline — Arquitectura de ingesta y flujo de datos.
- Monitoring — Alertas y observabilidad.
- Runbooks de ops — Procedimientos recurrentes (migración de docs, housekeeping).
- API Reference — Endpoints de debug, imports y admin.
Necesitas ayuda?
Para failover total, restore de backups o rotación de credenciales críticas, contacta al equipo de infraestructura de Relo y documenta todo en el runbook correspondiente.