CreaRack-SL

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ámetroValor
Fichero.github/workflows/d1-cleanup.yml
Trigger programadocron: "30 2 * * *" (02:30 UTC / 04:30 CEST)
Trigger manualworkflow_dispatch habilitado
Timeout6 minutos (subido de 3 en PR#201 al añadir los pasos de activity_log)
Concurrenciagrupo d1-cleanup, cancel-in-progress: false
Toggle de pausavars.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 (sin ON DELETE CASCADE) hace que este paso deba ejecutarse primero: si se borran los runs antes, los change_log hué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 tienen change_log asociados (caso residual tras el paso 1).
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 filtro source = 'mcp' AND json_extract(details,'$.tool') IS NOT NULL deja fuera, por ejemplo, el recuento de código muerto que lee maintenance/status).
  • Idempotente: tras la primera pasada, las filas recortadas ya no superan length(details) > 4000 y 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 a summarizeArgs() (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:

TablaFilas borradas
bib_index_runs475
bib_change_log84
auth_codes4

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íntomaCausa probableAcción
Workflow en skip sin falloBIBLIOTECARIO_PAUSADO=truegh variable set BIBLIOTECARIO_PAUSADO --body 'false'
DELETE falla con FK errorOrden de pasos alterado o migración añadió FK nuevaRevisar schema D1, restaurar orden paso 1 → 2
0 filas borradas en bib_index_runsLas filas tienen change_log asociados (protección NOT IN)Normal si el paso 1 no borró nada tampoco
auth_codes crece sin pararUsuarios no completan el magic-link y el cron no purgaVerificar que created_at < -1h cubre el TTL real configurado
activity_log sigue pesando tras el recorteEl recorte solo toca source = 'mcp' con tool; llamadas web o de otros crons no se resumenNormal — 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 listaLa fila es antigua y aún no pasó por el paso 5, o summarizeArgs truncó el arg como string largo en vez de listaComprobar 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]]