Workflow D1 Cleanup — purga diaria de bib_index_runs, bib_change_log y auth_codes
Workflow D1 Cleanup — purga diaria de bib_index_runs, bib_change_log, auth_codes y activity_log
Resumen
Workflow de GitHub Actions (D1 Cleanup) que purga cada noche las tablas D1 que acumulan filas sin control en crearacksl-workspace-db. Introducido en PR#49 (2026-05-18, s71) para bib_index_runs y bib_change_log; PR#201 (2026-09-27, #381) añade una cuarta tabla, activity_log, y un paso de recorte para las filas que ya llevaban los args del MCP sin resumir.
| Parámetro | Valor |
|---|---|
| Fichero | .github/workflows/d1-cleanup.yml |
| Trigger programado | cron: "30 2 * * *" (02:30 UTC / 04:30 CEST) |
| Trigger manual | workflow_dispatch habilitado |
| Timeout | 6 minutos (subido de 3 en PR#201 al añadir los pasos de activity_log) |
| Concurrencia | grupo d1-cleanup, cancel-in-progress: false |
| Toggle de pausa | vars.BIBLIOTECARIO_PAUSADO != 'true' |
Tablas purgadas
1. bib_change_log — cambios detectados entre indexaciones
DELETE FROM bib_change_log
WHERE changed_at < datetime('now', '-30 days')
- Retención: 30 días.
- Motivo de purga: tras 30 días pierde valor práctico para debugging. Además, la FK
bib_change_log.run_id → bib_index_runs.id(sinON DELETE CASCADE) hace que este paso deba ejecutarse primero: si se borran losrunsantes, loschange_loghuérfanos bloquean el delete con violación de FK.
2. bib_index_runs — historial de ejecuciones del Bibliotecario
DELETE FROM bib_index_runs
WHERE started_at < datetime('now', '-30 days')
AND id NOT IN (
SELECT run_id FROM bib_change_log WHERE run_id IS NOT NULL
)
- Retención: 30 días.
- Cadencia de ingesta: ~700 filas/día → ~21 000 filas/mes sin cleanup.
- Estado antes del PR: ~24 000 filas acumuladas en ~35 días.
- La condición
NOT IN (SELECT run_id FROM bib_change_log …)protege runs que aún tienenchange_logasociados (caso residual tras el paso 1).
3. auth_codes — nonces one-time de magic-link
DELETE FROM auth_codes
WHERE used_at IS NOT NULL
OR created_at < datetime('now', '-1 hour')
- Criterio 1 (
used_at IS NOT NULL): el nonce ya fue consumido. - Criterio 2 (
created_at < -1h): nonce caducado sin usarse (TTL de 1 hora). - No hay dependencias de FK; puede ejecutarse en cualquier orden.
4. activity_log — registro de actividad (retención 180 días + recorte de args del MCP)
Añadida en PR#201 (#381, decisión de Edu del 27-09-2026). Motivo: cada llamada del MCP se registraba con los args enteros en details.args (functions/api/mcp/index.ts), y las indexaciones periódicas de la Biblioteca (bib_index_docs/bib_index_chunks, cada 15 min) llevan como arg el texto completo de lo que indexan. El 27-09-2026 esto eran 1.975 filas y 93 de los 255 MB totales de la D1 — 93 de los 119 MB de la copia diaria de esa noche.
Paso 4 — retención de 180 días:
DELETE FROM activity_log WHERE timestamp < datetime('now', '-180 days')
La primera fila de activity_log es del 12-04-2026, así que este DELETE no borra nada real hasta mediados de octubre de 2026.
Paso 5 — recorte de filas antiguas ya guardadas enteras:
UPDATE activity_log
SET details = json_object('tool', json_extract(details, '$.tool'), 'recortado', 1)
WHERE source = 'mcp'
AND length(details) > 4000
AND json_valid(details)
AND json_extract(details, '$.tool') IS NOT NULL
- Solo toca llamadas del MCP con
tool(el filtrosource = 'mcp' AND json_extract(details,'$.tool') IS NOT NULLdeja fuera, por ejemplo, el recuento de código muerto que leemaintenance/status). - Idempotente: tras la primera pasada, las filas recortadas ya no superan
length(details) > 4000y el UPDATE no las vuelve a tocar. - Corre después de la copia nocturna de las 00:00 UTC, que conserva las filas tal como estaban antes del recorte.
- Desde el propio 27-09-2026, las filas nuevas ya no necesitan este recorte:
logActivity(functions/api/mcp/index.ts) llama asummarizeArgs()(functions/api/mcp/handlers/activity.ts) antes de guardar, que resume cada arg — listas como{items: N}, strings/objetos largos truncados a 200 caracteres, y el conjunto entero recortado a{keys: [...]}si supera 2 KB en JSON. El paso 5 de este workflow es solo para el histórico anterior a ese cambio. - El Worklog (
src/lib/activity-format.ts) entiende ambos formatos: además de listas literales, reconoce{items: N}como longitud de lista resumida.
Orden de ejecución y razón (FK)
Paso 1: bib_change_log > 30d ← primero (referencia run_id)
Paso 2: bib_index_runs > 30d ← segundo (referenciado por change_log)
Paso 3: auth_codes ← independiente
Paso 4: activity_log > 180d ← independiente (sin FK)
Paso 5: recorte activity_log MCP ← independiente, idempotente
Los pasos 3, 4 y 5 no tienen dependencias de FK entre sí ni con los pasos 1-2; su orden actual en el workflow es por convención (orden de introducción), no por necesidad.
Toggle de pausa global
if: ${{ vars.BIBLIOTECARIO_PAUSADO != 'true' }}
El mismo flag vars.BIBLIOTECARIO_PAUSADO que usan los demás crons del Bibliotecario (reindex-ts a las 00:15, curator a las 03:00). Si se activa el flag, este workflow queda en skip automático sin necesidad de deshabilitarlo manualmente — afecta a los 5 pasos por igual, incluidos los dos nuevos de activity_log.
Planificación horaria
00:15 UTC → reindex-ts (Bibliotecario)
02:30 UTC → D1 Cleanup ← este workflow
03:00 UTC → curator (Bibliotecario)
La ventana 02:30 fue elegida para evitar contención sobre crearacksl-workspace-db con los otros dos crons. Los pasos 4 y 5 (activity_log) se ejecutan dentro de la misma ventana, tras la copia diaria de las 00:00.
Cleanup manual ejecutado en s71 (2026-05-18)
Antes de activar el workflow programado, se ejecutó un one-shot manual con los siguientes resultados:
| Tabla | Filas borradas |
|---|---|
bib_index_runs | 475 |
bib_change_log | 84 |
auth_codes | 4 |
Tras el cleanup: 0 filas > 30 días pendientes en bib_index_runs.
Operación en producción
Verificar último run
gh run list --workflow 'D1 Cleanup' --limit 5
Ejecutar manualmente (one-shot)
gh workflow run d1-cleanup.yml
Pausar el workflow sin deshabilitarlo
# Activar flag global (pausa D1 Cleanup + reindex-ts + curator)
gh variable set BIBLIOTECARIO_PAUSADO --body 'true'
# Reactivar
gh variable set BIBLIOTECARIO_PAUSADO --body 'false'
Ver qué hay en la BD antes de purgar (diagnóstico)
# Filas acumuladas en bib_index_runs
wrangler d1 execute crearacksl-workspace-db --remote \
--command "SELECT COUNT(*) as total, MIN(started_at) as oldest FROM bib_index_runs"
# Filas > 30 días
wrangler d1 execute crearacksl-workspace-db --remote \
--command "SELECT COUNT(*) FROM bib_index_runs WHERE started_at < datetime('now', '-30 days')"
# Filas de activity_log aun sin resumir (details > 4000 bytes, llamadas MCP)
wrangler d1 execute crearacksl-workspace-db --remote \
--command "SELECT COUNT(*) FROM activity_log WHERE source = 'mcp' AND length(details) > 4000"
Alertas y posibles problemas
| Síntoma | Causa probable | Acción |
|---|---|---|
| Workflow en skip sin fallo | BIBLIOTECARIO_PAUSADO=true | gh variable set BIBLIOTECARIO_PAUSADO --body 'false' |
| DELETE falla con FK error | Orden de pasos alterado o migración añadió FK nueva | Revisar schema D1, restaurar orden paso 1 → 2 |
0 filas borradas en bib_index_runs | Las filas tienen change_log asociados (protección NOT IN) | Normal si el paso 1 no borró nada tampoco |
auth_codes crece sin parar | Usuarios no completan el magic-link y el cron no purga | Verificar que created_at < -1h cubre el TTL real configurado |
activity_log sigue pesando tras el recorte | El recorte solo toca source = 'mcp' con tool; llamadas web o de otros crons no se resumen | Normal — revisar de qué source son las filas grandes antes de tocar la query |
| El Worklog muestra “0 elementos” en una llamada que sí tenía lista | La fila es antigua y aún no pasó por el paso 5, o summarizeArgs truncó el arg como string largo en vez de lista | Comprobar details en crudo; no es un bug del Worklog si la fila es pre-27-09-2026 |
Véase también
- [[entity—biblioteca—service—bibliotecario]]
- [[concept—infra—d1-schema]]
- [[runbook—ci—bibliotecario-pausado]]