Skip to content

0017. Worker daemon su processes: dipendenza @my-ginkgo/noeva-shared

  • Stato: Superata da ADR-0019
  • Data: 2026-07-10
  • Decisori: Team BI/ETL

Contesto

noeva-etl esponeva finora un solo modo di essere invocato: la CLI batch etl run <pipeline> (ADR-0005, ADR-0006) — stateless, un container/processo = una esecuzione, esito su stdout. Qualcuno esterno (cron, scheduler, operatore) deve invocare il container per ogni run.

Il resto dell'infrastruttura Noeva (noeva-server-worker) usa invece un pattern di poller long-running sulla tabella processes di Supabase: un'istanza resta in polling, rivendica atomicamente (CAS) le righe pending con un dato worker_type, esegue il lavoro e scrive l'esito sulla stessa riga. Questo pattern è quello con cui il resto del prodotto (UI, scheduled jobs, workspace) crea e osserva job.

Serve permettere che le pipeline ETL vengano lanciate da dentro il sistema di job di Noeva (uno scheduled job, un'azione utente in workspace) invece che solo da un cron esterno che invoca etl run — con un vincolo che il worker generico non ha: il poller ETL deve occuparsi solo dei processi con worker_type='etl' (configurabile) eworkspace_id uguale a quello dell'istanza (NOEVA_WORKSPACE_ID), cioè ogni istanza è scoped a un singolo workspace.

Decisione

Aggiungere un secondo entrypoint, additivo e non sostitutivo del primo:

  • etl worker (src/worker/) — daemon che fa polling su processes, filtrato esclusivamente per worker_type+workspace_id, e dispatcha la pipeline il cui nome è nella colonna function (match esatto su RunnablePipeline.name nel registry esistente, nessun layer di traduzione). L'esito (RunReport/GraphRunReport) viene scritto in processes.output_data/error_message/status, non su stdout.
  • Nuova dipendenza: @my-ginkgo/noeva-shared (+ @supabase/supabase-js), per riusare ProcessManager.claimProcess/updateProcess (CAS pendingrunning) e getSupabaseService() — la stessa libreria e lo stesso contratto di claim usati da noeva-server-worker, invece di reimplementare la logica di lock.
  • Nessuna estensione della RPC condivisa claim_next_process: quella RPC non supporta filtro per workspace_id ed estenderla per un solo consumer (l'ETL) accoppierebbe un pezzo di infrastruttura condivisa a un caso d'uso specifico. Il claim ETL fa quindi una SELECT filtrata + ProcessManager.claimProcess(id) per riga candidata, invece della RPC.
  • etl run (batch, ADR-0005/0006) resta invariato: il nucleo di esecuzione della pipeline (pipeline.run(ctx) → RunReport) è condiviso e resta una funzione pura; cambia solo chi la invoca e dove finisce il report.

Alternative considerate

  • Estendere claim_next_process con un parametro workspace_id — scartata: tutti gli altri consumer della RPC (noeva-server-worker) dovrebbero convivere con un parametro che non usano, e la RPC diventerebbe accoppiata a un solo caso d'uso.
  • Scheduler esterno generico (cron per workspace che invoca etl run <pipeline>) — scartata: non permette il dispatch dinamico guidato dal sistema di job di Noeva (UI, scheduled jobs agent) e duplicherebbe la logica di polling/lock in cron esterni invece di riusare quella già pubblicata in noeva-shared.
  • Client SQL diretto fatto in casa verso processes, senza dipendere da noeva-shared — scartata: duplicherebbe l'esatta logica di claim CAS già testata e pubblicata in quel pacchetto, divergendo dal contratto usato dal resto del prodotto.

Conseguenze

  • Positive: le pipeline ETL sono ora lanciabili da dentro il sistema di job/workspace di Noeva esattamente come qualsiasi altra ProcessFunction, senza bisogno di un cron esterno; il claim atomico riusa un meccanismo già collaudato invece di reinventare polling/locking.
  • Costi/limiti:
    • Nuova dipendenza runtime @my-ginkgo/noeva-shared (+ @supabase/supabase-js): la modalità etl worker richiede SUPABASE_URL/SUPABASE_SERVICE_KEY/ SUPABASE_JWT_SECRET in ambiente (fail-fast se mancanti — vedi runbooks/etl-worker.md).
    • Due modalità di invocazione (batch vs daemon) coesistono con modelli operativi diversi (one-shot vs long-running); vanno tenute chiaramente separate in documentazione per non confondere gli operatori.
    • Nessun timeout applicativo sul singolo dispatch dentro il worker (a differenza del limite fisso di 30 minuti di noeva-server-worker): una pipeline molto lunga blocca il polling di quell'istanza finché non finisce.
    • claimed_by/claimed_at restano vuoti (non si estende ProcessManager per questo); nessun indice DB dedicato aggiunto su processes.

Noeva è un marchio registrato di 4D S.R.L.