Harden sqlite writers and add shared lock
This commit is contained in:
53
ai/tasks/done/TASK-091-sqlite-writer-hardening.md
Normal file
53
ai/tasks/done/TASK-091-sqlite-writer-hardening.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# TASK-091-sqlite-writer-hardening
|
||||
|
||||
## Goal
|
||||
Unificar y endurecer la politica de conexion SQLite para todas las rutas de escritura del backend que comparten la misma base de datos, reduciendo contencion evitable y haciendo el comportamiento consistente entre historico base, player-events y captura prospectiva RCON.
|
||||
|
||||
## Context
|
||||
La repo ya tiene varias rutas de escritura sobre el mismo SQLite:
|
||||
- historico base CRCON
|
||||
- player-event ledger V2
|
||||
- captura prospectiva RCON
|
||||
- snapshots y runners asociados
|
||||
|
||||
Parte del storage ya usa timeout/WAL/busy_timeout, pero no toda la capa de persistencia lo hace de forma uniforme. Antes de cualquier politica de locking entre procesos, hace falta que todas las conexiones writer-capable usen una configuracion SQLite coherente.
|
||||
|
||||
## Scope
|
||||
Backend solamente. Sin cambios en frontend.
|
||||
|
||||
## Steps
|
||||
1. Auditar todas las funciones `_connect()` o equivalentes que abren SQLite en:
|
||||
- `backend/app/historical_storage.py`
|
||||
- `backend/app/player_event_storage.py`
|
||||
- `backend/app/rcon_historical_storage.py`
|
||||
- cualquier otro storage writer-capable relacionado
|
||||
2. Crear una utilidad compartida y pequena para abrir conexiones SQLite con politica comun de escritura.
|
||||
3. La politica comun debe incluir como minimo:
|
||||
- `timeout` explicito
|
||||
- `PRAGMA foreign_keys = ON`
|
||||
- `PRAGMA journal_mode = WAL`
|
||||
- `PRAGMA busy_timeout`
|
||||
- `row_factory = sqlite3.Row`
|
||||
4. Reusar esa utilidad en todas las capas de persistencia con escritura que comparten el mismo DB.
|
||||
5. Mantener compatibilidad con la ruta actual del storage y sin cambiar contratos HTTP.
|
||||
6. Actualizar README/runbook backend con una nota breve sobre la politica SQLite usada por writers.
|
||||
|
||||
## Constraints
|
||||
- No tocar frontend.
|
||||
- No cambiar la semantica funcional de los endpoints.
|
||||
- No cambiar el proveedor historico por defecto.
|
||||
- No meter dependencias nuevas salvo necesidad extrema.
|
||||
- Mantener la utilidad pequeña y facil de leer.
|
||||
|
||||
## Validation
|
||||
- Todas las capas writer-capable relevantes comparten la misma politica SQLite.
|
||||
- `python -m compileall app` pasa.
|
||||
- Las CLIs principales siguen arrancando con `--help`.
|
||||
- La repo queda consistente.
|
||||
|
||||
## Expected Files
|
||||
- `backend/app/historical_storage.py`
|
||||
- `backend/app/player_event_storage.py`
|
||||
- `backend/app/rcon_historical_storage.py`
|
||||
- un util compartido nuevo bajo `backend/app/` si hace falta
|
||||
- `backend/README.md`
|
||||
@@ -0,0 +1,81 @@
|
||||
# TASK-092-single-writer-locking-for-automation-workers
|
||||
|
||||
## Goal
|
||||
Imponer una coordinacion de single-writer entre todos los procesos del backend que escriben sobre el mismo SQLite compartido, evitando colisiones entre automatizaciones y ejecuciones manuales y sustituyendo errores opacos de `database is locked` por una coordinacion controlada y mensajes claros.
|
||||
|
||||
## Context
|
||||
Actualmente pueden coincidir sobre el mismo volumen `/app/data`:
|
||||
- `historical-runner`
|
||||
- `player_event_worker`
|
||||
- `rcon-historical-worker`
|
||||
- ejecuciones manuales via `docker compose exec backend ...`
|
||||
|
||||
Aunque WAL y busy_timeout ayudan, no garantizan una operativa limpia si varios writers largos arrancan a la vez. Hace falta una politica de exclusion mutua a nivel de proceso/job, no solo a nivel SQLite.
|
||||
|
||||
## Scope
|
||||
Backend y orquestacion minima/documentacion. Sin cambios en frontend.
|
||||
|
||||
## Desired Design
|
||||
Implementar un unico lock compartido para todos los writers que tocan el mismo SQLite.
|
||||
|
||||
Ese lock debe:
|
||||
- vivir bajo el storage compartido en `/app/data` o una ruta derivada del `storage_path`
|
||||
- ser comun para:
|
||||
- `app.historical_runner`
|
||||
- `app.historical_ingestion`
|
||||
- `app.player_event_worker`
|
||||
- `app.rcon_historical_worker`
|
||||
- adquirirse al principio de cada run writer-oriented
|
||||
- liberarse siempre aunque haya excepcion
|
||||
- incluir metadata visible del holder:
|
||||
- proceso/comando
|
||||
- started_at
|
||||
- host/container si es viable
|
||||
- tener espera configurable con timeout y poll interval
|
||||
- fallar con error claro cuando no pueda adquirir el lock dentro del timeout
|
||||
- evitar dependencias nuevas si es posible
|
||||
|
||||
## Steps
|
||||
1. Crear una utilidad comun de writer lock compartido para el backend.
|
||||
2. Aplicarla a:
|
||||
- `historical_ingestion` manual
|
||||
- `historical_runner`
|
||||
- `player_event_worker`
|
||||
- `rcon_historical_worker`
|
||||
3. Asegurarte de que el lock cubre la ejecucion writer-oriented completa, no solo una sentencia aislada.
|
||||
4. Exponer configuracion/env para:
|
||||
- lock timeout
|
||||
- poll interval
|
||||
5. Añadir mensajes de error/estado legibles cuando el lock este ocupado.
|
||||
6. Mantener las rutas HTTP read-only fuera de este lock.
|
||||
7. Actualizar `docker-compose.yml` solo si es necesario para dejar la operativa alineada.
|
||||
8. Actualizar `backend/README.md` con runbook claro:
|
||||
- que writers comparten lock
|
||||
- como hacer una pasada manual
|
||||
- que pasa si el lock esta ocupado
|
||||
- como convivir con automatizaciones sin parar contenedores salvo necesidad excepcional
|
||||
|
||||
## Constraints
|
||||
- No tocar frontend.
|
||||
- No convertir esto en un rediseño total del scheduler.
|
||||
- No romper los workers ya existentes.
|
||||
- No bloquear las rutas HTTP de lectura.
|
||||
- No ampliar el alcance funcional del read model RCON.
|
||||
- Debe quedar claro que la solucion principal es single-writer coordination, no solo subir retries.
|
||||
|
||||
## Validation
|
||||
- Existe un lock compartido real para los writers.
|
||||
- `historical_runner`, `historical_ingestion`, `player_event_worker` y `rcon_historical_worker` lo usan.
|
||||
- Si dos writers coinciden, no se produce un fallo opaco de SQLite; hay espera controlada o error claro de lock ocupado.
|
||||
- `python -m compileall app` pasa.
|
||||
- Las CLIs principales siguen respondiendo con `--help`.
|
||||
- La repo queda consistente.
|
||||
|
||||
## Expected Files
|
||||
- uno o varios utilitarios nuevos bajo `backend/app/`
|
||||
- `backend/app/historical_runner.py`
|
||||
- `backend/app/historical_ingestion.py`
|
||||
- `backend/app/player_event_worker.py`
|
||||
- `backend/app/rcon_historical_worker.py`
|
||||
- `docker-compose.yml` si hace falta
|
||||
- `backend/README.md`
|
||||
Reference in New Issue
Block a user