Obiettivo: sostituire l'esecuzione custom del worker con Celery + Redis, mantenendo Postgres come fonte ufficiale per stato, audit e tracciabilita dei job.
Fonti operative:
- Celery Redis broker/backend: https://docs.celeryq.dev/en/stable/getting-started/backends-and-brokers/redis.html
- Celery configuration: https://docs.celeryq.dev/en/stable/userguide/configuration.html
- Usare Redis solo come broker Celery.
- Valutare se usare Redis anche come result backend; preferenza iniziale: no, perche lo stato business resta in
parse_jobs. - Tenere Postgres come fonte ufficiale per:
-
parse_jobs.status -
parse_jobs.attempts -
parse_jobs.last_error -
audit_logs -
flight_logs.parse_status
-
- Rendere i task Celery idempotenti: una riesecuzione non deve duplicare track point, eventi o audit critici.
- Aggiungere
celery[redis]ocelery+redisinapi/requirements.txt. - Aggiungere variabili in
api/.env.example:-
CELERY_BROKER_URL=redis://redis:6379/0 -
CELERY_RESULT_BACKEND= -
CELERY_TASK_TIME_LIMIT=300 -
CELERY_TASK_SOFT_TIME_LIMIT=240 -
CELERY_REDIS_VISIBILITY_TIMEOUT=900
-
- Aggiungere servizio
redisindocker-compose.yml. - Aggiornare
workerindocker-compose.ymlper eseguire Celery:-
celery -A app.celery_app worker --loglevel=INFO --concurrency=2
-
- Valutare un container separato per
celery beatsolo se servono job periodici.
- Creare
api/app/celery_app.py. - Configurare Celery con:
- broker Redis
- task routing dedicato per parsing DJI
-
task_acks_late=True -
worker_prefetch_multiplier=1 -
task_reject_on_worker_lost=True - visibility timeout Redis coerente con il tempo massimo previsto del parser
- Creare
api/app/tasks/parse.py. - Implementare task
parse_flight_log_task(parse_job_id: int). - Il task deve:
- leggere
ParseJobda Postgres - verificare che il job sia ancora valido
- marcare
running - chiamare
parse_flight_log_or_raise - marcare
succeededofailed - scrivere
audit_logs - gestire retry con backoff
- leggere
- Modificare
enqueue_parse_jobper chiamareparse_flight_log_task.delay(job.id). - Tenere una modalita fallback senza Celery solo se utile per sviluppo locale.
- Prima del parsing, bloccare il job con lock DB o transizione atomica
queued -> running. - Evitare doppia esecuzione contemporanea dello stesso
flight_log_id. - Garantire che
_save_track_pointscontinui a cancellare e ricreare i punti del volo. - Verificare che
_extract_eventsnon accumuli duplicati a ogni retry. - Audit: evitare spam su retry ravvicinati o distinguere chiaramente
started,retry_scheduled,failed,succeeded. - Gestire task redelivered da Redis visibility timeout.
- Lasciare invariata la risposta upload:
flight_id,log_id,job_id,parse_status. - Esporre lo stato Celery solo se utile per debug; lo stato utente deve restare quello DB.
- Aggiornare polling frontend solo se cambia il formato di
/logs. - Aggiungere messaggio chiaro quando un job e in retry.
- Log strutturati nel worker Celery.
- Metriche minime:
- job queued/running/failed/succeeded
- durata parsing
- numero retry
- errori DJI API/subprocess
- Valutare Flower solo per ambiente admin/dev, non esposto pubblicamente.
- Monitorare crescita di
flights,flight_logs,flight_track_points,flight_events,parse_jobsper organizzazione. - Aggiungere indici composti mirati prima del partizionamento:
-
flights (organization_id, started_at DESC) -
flight_logs (flight_id, parse_status) -
flight_track_points (flight_id, id) -
flight_events (flight_id, severity, flight_time_s) -
parse_jobs (organization_id, status, next_run_at)
-
- Valutare partizionamento Postgres quando una tabella supera milioni di righe o query/report per org diventano lente.
- Opzione preferita per multi-tenant: partizionare per
organization_iddove il pattern query e sempre tenant-scoped. - Valutare partizionamento ibrido per tabelle molto grandi:
-
flights: partition/hash o list perorganization_id -
flight_track_points: partition perflight_idderivato o perorganization_iddenormalizzato -
flight_events: partition perorganization_iddenormalizzato o per periodo se report storici pesano
-
- Considerare una colonna
organization_iddenormalizzata suflight_track_pointseflight_eventsper evitare join pesanti quando si partiziona per tenant. - Prima di partizionare, misurare con
EXPLAIN ANALYZEle query reali di dashboard, report, mappe e PDF. - Se pochi tenant diventano enormi, valutare sharding/logical database per org enterprise invece di partizionare tutto subito.
- Documentare strategia retention/archiviazione:
- mantenere track point completi per N mesi
- mantenere downsample storico per mappe/report
- conservare PDF/audit/hash log secondo policy cliente
- Verificare che Alembic supporti bene la strategia scelta prima di migrare dati produzione.
- Unit test per
enqueue_parse_job. - Unit test task Celery in eager mode.
- Test idempotenza: eseguire due volte lo stesso job e verificare no duplicati.
- Test retry su errore temporaneo del parser.
- Test failure finale dopo
max_attempts. - Test Docker Compose: upload log -> job queued -> worker parsed -> UI vede
parsed.
- Prima fase: aggiungere Celery mantenendo il worker custom disabilitabile.
- Seconda fase: usare Celery come path default in Docker Compose.
- Terza fase: rimuovere
api/app/worker.pyse non serve piu. - Aggiornare README e documentazione deploy.
- Verificare
docker compose up --build -dda ambiente pulito.
- Redis non e fonte durevole come Postgres: non salvare solo su Redis stati importanti.
- Visibility timeout troppo basso puo causare riesecuzioni.
- Visibility timeout troppo alto ritarda il recupero di task persi.
- Parsing DJI deve restare idempotente per tollerare redelivery e retry.
- Celery aumenta complessita operativa: healthcheck, log, tuning concurrency, memory leak del subprocess.