Skip to content

0021. Worker daemon multi-workspace: un'istanza serve N workspace via N poller config-driven

  • Stato: Accettata
  • Data: 2026-07-16
  • Decisori: Team BI/ETL

Contesto

Fino a ADR-0019, etl worker è monoworkspace: loadEtlWorkerConfig legge un unico NOEVA_WORKSPACE_ID dall'.env e etl worker avvia un solo EtlProcessWorker, che rivendica i processi con header x-noeva-workspace fisso. ADR-0019 assumeva esplicitamente il worker "deployato come istanza indipendente per workspace".

Nota importante sul modello di autenticazione: l'NOEVA_API_KEY è una sola e vale per tutti i workspace del cliente; il workspace target si seleziona esclusivamente con l'header x-noeva-workspace. Non esiste una API key per workspace — un errore assunto nelle prime bozze di questo ADR e corretto qui.

Ma un cliente Noeva può avere più workspace pur volendo una sola istanza ETL. Con il modello monoworkspace servirebbero N istanze/deploy separati, ciascuno con il proprio .env — costo operativo e infrastrutturale che cresce linearmente col numero di workspace del cliente, senza motivo tecnico.

Decisione

Una singola istanza etl worker può servire N workspace avviando un poller indipendente per workspace (approccio config-driven, zero modifiche a noeva-server-api):

  • Nuovo loader loadEtlWorkerConfigs(env): EtlWorkerConfig[] (src/worker/config.ts). Se NOEVA_WORKSPACE_IDS è impostata (lista di workspace separati da virgola), produce una EtlWorkerConfig per workspace; altrimenti fa fallback al singolo loadEtlWorkerConfig da env NOEVA_* (comportamento monoworkspace invariato).
  • Credenziali condivise da tutti i workspace: NOEVA_API_KEY e NOEVA_API_BASE_URL (più ETL_WORKER_TYPE/ETL_POLL_INTERVAL_MS) sono comuni; per workspace varia solo il workspaceId (l'id inviato nell'header x-noeva-workspace). Nessun secret per workspace. I connettori sorgente/destinazione (PG/MSSQL/S3/SFTP…) restano su env globale condiviso.
  • startWorkers (src/worker/run-workers.ts) crea un EtlProcessWorker per config e ritorna un handle di shutdown combinato (stopAll, best-effort via Promise.allSettled). La CLI (src/cli.ts) lo usa in etl worker, registrando un solo handler SIGTERM/SIGINT per tutti i poller.
  • Ogni poller riusa immutato il claim server-side esistente: rivendica con la stessa API key e il proprio header x-noeva-workspace. Il contratto processes/etl/* non cambia.
  • L'identità del workspace viaggia nel RunContext (RunContext.workspaceId opzionale), propagata dal worker via DispatchOptions fino a createRunContext, così uno stage che parlerà con Noeva (es. un futuro sink verso noeva-server-api) legge il workspace dalla run, non da process.env.

Alternative considerate

  • Claim server-side multi-workspace (claim che ritorna job cross-workspace col workspace_id nel payload, un solo poller) — scartata per questo passo: richiede un cambio di contratto lato noeva-server-api (cross-repo), a fronte di un beneficio marginale rispetto agli N poller. Resta un'evoluzione possibile.
  • File JSON montato (ETL_WORKSPACES_CONFIG) — scartata: era giustificata da un modello (poi rivelatosi errato) con una API key per workspace, che avrebbe richiesto un artefatto con più secret. Con un'unica API key condivisa per workspace varia solo l'id: una lista in env (NOEVA_WORKSPACE_IDS) è più semplice e non introduce un file di secret.
  • Config per-workspace anche dei connettori sorgente/destinazione — rimandata: oggi le sorgenti sono condivise per il cliente. Se servirà l'override per-workspace delle credenziali sorgente, si sposterà la config connettori da env globale al RunContext (seam già predisposto) in un ADR dedicato.

Conseguenze

  • Retrocompatibile: senza NOEVA_WORKSPACE_IDS, etl worker resta monoworkspace, identico a prima (stesso codice di loadEtlWorkerConfig).
  • Nessun nuovo secret: NOEVA_WORKSPACE_IDS contiene solo id di workspace (non sensibili), l'unica credenziale resta NOEVA_API_KEY come nel caso singolo. Il loader valida fail-fast (lista non vuota, id non duplicati, API key/base URL presenti).
  • Concorrenza: N poller = fino a N pipeline concorrenti (prima 1). È un vantaggio di isolamento (un job lento su un workspace non blocca gli altri); ogni pipeline apre/chiude i propri pool connettori in run(), quindi non c'è stato condiviso tra run.
  • docs/pipelines/* e i runbook che assumevano "un'istanza = un workspace" vanno letti alla luce di questo ADR: l'istanza può ora enumerare più workspace.

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