genro-asgi · documento 3 di 3
Come il piano cresce oltre una macchina senza cedere a nessuno la decisione su dove vive una persona — e come si usa Kubernetes prendendone i muscoli e non il giudizio.
Il secondo documento arriva a una conclusione che poi diventa un vincolo: se lo stato di lavoro di una persona vive nella memoria di un processo, allora dove quella persona viene servita è una decisione applicativa, non infrastrutturale. Nessun bilanciatore può prenderla, perché nessun bilanciatore sa cosa sia una persona.
Va benissimo su una macchina. Ma le macchine finiscono: la memoria si esaurisce, i core si esauriscono, e prima o poi il parco deve diventare più di uno. E qui la domanda si fa scomoda: come si cresce su più macchine, se la decisione su dove vive un utente non può essere delegata?
La risposta breve è che non si delega la decisione — si delega l'esecuzione. La distinzione è tutto il contenuto di questo documento.
Descrive l'architettura distribuita nella sua forma compiuta. La roadmap in appendice dichiara cosa è già operativo e cosa è in corso di implementazione: è l'unica parte che cambia mentre il lavoro procede. Presuppone il secondo documento — lo stato vivo, l'utente come chiave, i gruppi come versioni.
C'è una frase che riassume la posizione rispetto a qualunque piattaforma di orchestrazione, Kubernetes compreso: il cervello è nostro, le braccia sono sue.
Le braccia sono quello che una piattaforma fa meglio di noi e che non abbiamo interesse a riscrivere: procurare una macchina, far nascere un processo dentro un confine di risorse, riavviarlo se cade, distribuirlo su nodi diversi, sostituire un nodo morto. È lavoro ingrato, e loro lo fanno bene.
Il cervello è quello che nessuna piattaforma può fare al posto nostro, perché richiede di sapere cosa sia un utente e cosa possieda: quante unità servono, quando servono, chi ci va dentro e quando qualcuno deve essere spostato.
La piattaforma non vede mai un utente. Vede solo processi da far nascere, quando glielo ordiniamo.
Questa non è diffidenza verso Kubernetes: è la constatazione che il suo modello — unità intercambiabili, traffico distribuito su chi è disponibile — è esattamente il modello che il nostro caso d'uso non può adottare. Prenderne le parti giuste è un guadagno; prenderne anche il giudizio significherebbe rompere lo sticky.
Distribuire introduce una seconda dimensione. Fino a una macchina esisteva solo la versione: quale codice esegue un processo. Con più macchine si aggiunge la capacità: quale ferro lo ospita. Le due dimensioni sono ortogonali — una versione gira su più macchine, una macchina ospita più versioni — e si incrociano esattamente sul processo di lavoro.
Da qui la scelta di avere due gerarchie distinte invece di una sola che tenta di fare entrambi i mestieri:
La conseguenza è che ogni processo di lavoro ha due genitori con due mestieri diversi: il capogruppo, che sa quali utenti gli appartengono e decide il suo destino; e il titolare del dominio fisico, che lo fa nascere, lo sorveglia e ne misura le risorse. Nessuno dei due invade il mestiere dell'altro.
La matrice versione × capacità non è compilata dall'alto: emerge dal basso, come somma delle decisioni dei capigruppo, vincolata dai budget che il vertice assegna e dalla capacità che i domini dichiarano.
Il vertice tiene i propri figli come canali tipizzati, di due specie: un processo di lavoro che ha generato e sorveglia lui stesso, oppure un sub-commander remoto che si è agganciato. Le due specie possono convivere nello stesso vertice.
IL VERTICE (utenti, gruppi, budget, giudizio) ├── worker processo di casa, generato qui ├── worker processo di casa, generato qui ├── sub-commander macchina remota ── worker … worker └── sub-commander dominio su cluster ── worker … worker
La proprietà che rende questo disegno sostenibile è che il tipo del canale non produce mai una diramazione nel codice. Chi deve raggiungere un destinatario risolve l'indirizzo, prende il canale, spedisce la busta: lo stesso gesto identico per un processo di casa e per una macchina all'altro capo della rete. Cosa ci sia dietro il canale è una proprietà del dato, non un ramo nella logica.
È lo stesso principio già visto nel ruolo singolo: quando tutto vive in un processo, il canale è una coppia di code in memoria che codifica i messaggi esattamente come farebbe un socket. Un solo protocollo, tre collocazioni — in processo, su un'altra macchina, su una fetta di cluster — e una sola batteria di test di contratto che le verifica tutte, perché due implementazioni della stessa cosa divergono appena qualcosa non le obbliga a restare uguali.
Un sub-commander è il vertice stesso, la stessa classe, con l'indicazione di chi sia il proprio genitore. Non è un componente a parte da mantenere in parallelo: è la stessa macchina montata un livello più in basso.
Una regola sola tiene a bada la complessità: la gerarchia è profonda un livello. Un sub-commander non ha a sua volta sub-commander. Non c'è ricorsione, non ci sono cammini di lunghezza imprevedibile, e non esiste la topologia che nessuno riesce più a disegnare alla lavagna.
Il traffico segue i rami dell'albero: un processo parla al proprio commander, che lo indirizza al canale di destinazione. Non ci sono scorciatoie laterali fra domini — e questa è la ragione per cui i processi di lavoro non si affacciano mai sulla rete: gli unici esposti sono il vertice e i sub-commander.
Il problema più insidioso di un registro distribuito è la duplicazione: se due livelli sanno la stessa cosa, prima o poi divergono, e non c'è modo di stabilire quale dei due abbia ragione.
La soluzione è che i due livelli sanno cose diverse, con precisione diversa:
| Livello | Cosa sa | Per decidere |
|---|---|---|
| Il vertice | utente → indirizzo (un dominio, oppure un processo di casa) | dove mandare la richiesta; le domande globali; l'arbitraggio fra gruppi |
| Il dominio | utente → processo, e il carico dei propri processi | la distribuzione interna: assegnare, ribilanciare, far nascere |
| Il processo | utente → connessioni → pagine, con tutti i contenuti | eseguire |
La mappa fine — quale processo esatto tiene una persona — vive solo dove vivono i processi. Il vertice tiene la mappa grossa: sa che Maria sta nel dominio B, non sa quale processo di B la stia servendo, e non ha bisogno di saperlo.
Quindi la risoluzione è a due tappe, ognuna su un solo livello: il vertice riconosce il dominio e spedisce lì; il dominio risolve il processo e serve. Nessuna ricerca ricorsiva, nessuna cache da invalidare in cascata, e — soprattutto — nessuna verità duplicata: quando il dominio B sposta Maria da un suo processo a un altro, il vertice non ha nulla da aggiornare, perché quello che il vertice sa è ancora vero.
Vale anche per gli aggiornamenti dei dati: una modifica destinata a una pagina che sta altrove risale al vertice, che la indirizza al dominio, che la consegna al processo. Un solo schema per il traffico di controllo e per quello dei dati.
Un solo scrittore per ogni fatto, e ogni livello sa una cosa più grossa di quello sotto. È ciò che permette a ogni livello di decidere in autonomia senza chiedere permesso: le informazioni non si sovrappongono, quindi non possono contraddirsi.
Un sistema distribuito si rompe quando due componenti credono entrambe di decidere la stessa cosa. Qui i mestieri sono tre, e sono disgiunti — l'immagine è quella di un'azienda con sezioni autonome dentro un budget.
Guarda l'occupazione dei processi della propria versione e decide: far nascere, ritirare, ricollocare una persona, cambiare la propria versione con un rilascio graduale. Decide da solo, dentro il budget che gli è stato assegnato. Le sezioni hanno vite indipendenti: quello che accade in un gruppo non passa dal cervello di un altro.
Non micro-decide. Fa tre cose: smista una persona verso il gruppo giusto quando entra; assegna i budget e i massimali di ciascun gruppo, che è il vero arbitraggio del ferro condiviso; governa le operazioni che attraversano i gruppi, come portare una persona da una versione all'altra.
Pubblica la capacità del proprio dominio, materializza le nascite e le morti che i capigruppo ordinano, riferisce la salute di quel che ospita. Non conosce la strategia: non sa perché serva un processo in più, sa come procurarlo.
Questa divisione risponde anche alla domanda pratica di come nasce un nuovo processo quando le risorse si esauriscono, e vale la pena essere precisi su chi tiene la penna, perché è il punto in cui è facile immaginare la catena al contrario.
Non è il processo saturo a chiedere aiuto per sé. Il processo misura e riferisce: quanta memoria sta usando davvero, quanto sono saturi i suoi componenti. Chi legge quelle misure è il capogruppo, che vede l'intera sezione e non un pezzo, e che quindi può distinguere il caso in cui basta spostare qualcuno da quello in cui serve davvero un'unità in più. Se la conclusione è che serve, il capogruppo ordina; il sub-commander del dominio scelto materializza; il processo nuovo nasce, si presenta al proprio commander, e da quel momento è nel giro.
Chi misura non decide, chi decide non esegue, chi esegue non conosce la strategia.
C'è un caso che ha guidato una revisione importante del disegno, e vale la pena raccontarlo perché mostra cosa si guadagna dall'ammettere figli di specie mista.
Un cliente lavora per dieci mesi l'anno su una macchina sola, comodamente. Poi arriva la stagione — la campagna, la chiusura, il picco — e per qualche settimana servono tre macchine. Poi il picco passa.
Una versione precedente del disegno vietava i ruoli doppi: il vertice non poteva possedere processi di lavoro, doveva sempre esserci un livello intermedio. Era coerente, e imponeva una cerimonia costante: anche per la macchina singola servivano tre processi, uno dei quali esisteva solo per rispettare la simmetria.
Ammettendo la specie mista, il caso stagionale diventa una transizione a costo zero. Il vertice possiede i suoi processi di casa; quando arrivano le macchine di picco, la sua tabella comincia a contenere anche voci di dominio remoto; quando il picco passa, quelle voci scompaiono. Il vertice non cambia mai ruolo, non c'è una riconfigurazione, non c'è un momento di migrazione da una topologia all'altra: crescere e restringersi sono la stessa operazione in due direzioni.
E il caso comune — che per la maggior parte dei clienti è l'unico — non paga processi che esistono solo per la coerenza dello schema. Il minimo di sviluppo resta due processi: il vertice e un lavoratore.
Su un cluster, un dominio di capacità è una fetta di cluster e il sub-commander è il processo che la governa: materializza i processi di lavoro chiamando le API della piattaforma, e quei processi attaccano il proprio canale a lui. La catena non cambia di una virgola — cambia soltanto chi, dietro il canale, ha le mani sul ferro.
| Da noi | Sul cluster |
|---|---|
| processo di lavoro | un pod, con un solo processo dentro — la forma idiomatica |
| gruppo, cioè una versione | un Deployment |
| versione dell'ambiente | l'immagine del container: sul cluster la domanda «come faccio arrivare il runtime alla macchina» si risponde da sé |
| capogruppo | il controllore del proprio Deployment |
| dominio di capacità | il sub-commander: un processo che materializza pod via API e a cui i pod si agganciano |
Resta possibile, come via di mezzo, il pod che contiene un sub-commander e diversi processi di lavoro insieme — utile in certi assetti, ma non è il disegno primario.
Questa è la parte che sorprende chi arriva dal mondo dei container, e sono tre rinunce deliberate. Nessuna è ideologica: ognuna corrisponde a un meccanismo che, applicato a processi che tengono stato, farebbe danno.
| Meccanismo | Perché non lo usiamo |
|---|---|
| Il bilanciatore del cluster | Distribuisce il traffico su chi è disponibile: è esattamente ciò che non deve accadere. Il traffico degli utenti entra solo dalla nostra porta, e l'inoltro va al pod preciso che il registro indica — non a uno equivalente. |
| L'autoscaling della piattaforma | Scala su metriche che non sanno cosa sia una persona: aggiungerebbe unità guardando la CPU mentre la risorsa che si esaurisce è la memoria di chi sta fermo con un oggetto grosso in pancia. La scala la decide il capogruppo, e la ordina alle API — la piattaforma è pienamente comandabile in modo imperativo, ed è così che la usiamo. |
| Il rilascio graduale nativo | Sostituisce i pod con i propri tempi, ignorando chi ci sta sopra: applicato a processi con stato significa scollegare gli utenti. Il cambio di versione resta la nostra coreografia — porta su il gruppo nuovo, spostagli le persone a caldo, ritira il vecchio quando è vuoto — eseguita chiamando le API. |
Un cluster prende iniziative fisiche unilaterali: un nodo muore e i suoi pod vengono rischedulati altrove, un pod viene sfrattato perché il nodo serve ad altro. Non si può impedirlo, e non si deve provarci.
Ma non è un dovere nuovo: è lo stesso dovere che abbiamo sul ferro nostro, dove i processi muoiono comunque. Il piano è costruito sul presupposto che un processo possa sparire in qualunque momento — la morte è un canale che si chiude, e ciò che quel processo teneva viene liberato e ricostruito. Su un cluster quel presupposto si verifica più spesso; il meccanismo che lo affronta è identico.
Il disegno ha un prezzo dichiarato: il traffico verso un dominio remoto fa un salto in più, perché passa dal vertice al sub-commander e da lì al processo, invece di andare diretto.
È stato accettato con tre ragioni, che vale la pena esporre perché è il tipo di scelta su cui un lettore tecnico vuole vedere i conti:
In cambio si ottiene la proprietà che rende il sistema comprensibile: i processi di lavoro non si affacciano sulla rete, il perimetro esposto è fatto di due sole specie di componenti, e il traffico segue l'albero — quindi il cammino di un messaggio è sempre disegnabile.
È registrata come possibile evoluzione, non come impegno, l'ipotesi che su una rete piatta e fidata il solo traffico dei dati possa andare diretto, mantenendo il controllo sull'albero. È un'ottimizzazione che si fa quando i numeri la chiedono, non prima.
Un'architettura distribuita si giudica da cosa succede quando un pezzo sparisce. Qui i casi sono tre, con raggi di danno diversi e dichiarati.
Il suo canale si chiude, e questo è il segnale: ciò che teneva viene liberato, i permessi che aveva in mano rilasciati, e un sostituto nasce con un nome nuovo — mai riusato, così nessun messaggio in ritardo può essere confuso col nuovo arrivato. Il danno è limitato alle sessioni che quel processo ospitava.
È un solo canale che si chiude, e questa è la virtù del disegno: il raggio del danno coincide con il dominio. Il vertice cancella dalla propria tabella le voci che puntavano lì, e le persone che vivevano su quella macchina vengono ricollocate sui domini che restano.
Si porta via il cervello, e va detto chiaramente. Ma è un costo di qualunque topologia che ospiti il cervello da qualche parte, non un costo di questo disegno in particolare: il coordinamento sta su una macchina, e quella macchina è un punto singolo. La difesa è la stessa che vale per il caso mono-macchina — la sessione applicativa sopravvive al riavvio, perché lo stato viene raccolto scendendo e ripiazzato risalendo.
Il pannello del secondo documento guarda i processi e le persone di un pool. Salendo al vertice la vista cambia grana, e cambia per la stessa ragione per cui la tabella degli indirizzi è più grossa: il vertice non sa quale processo serva chi, e non deve saperlo. Guarda domini.
Chi vuole scendere al dettaglio apre il pannello del dominio — che è lo stesso pannello, perché un sub-commander è la stessa cosa del vertice montata un livello più in basso.
Ogni riga è un canale del vertice. «Casa» è il dominio che il vertice governa direttamente e non paga salti; gli altri sono agganciati, con la latenza del salto in chiaro accanto — quella frazione di millisecondo che è il costo dichiarato del disegno. La capacità è ciò che il dominio offre contro ciò che ha impegnato: è il numero su cui il vertice assegna i budget.
Le caselle vuote non sono un errore di configurazione: nessuno ha deciso che «stabile» debba stare sul cluster, e nessuno gliel'ha vietato. Quella casella si riempirà se e quando il capogruppo di «stabile» avrà bisogno di capacità e il cluster sarà il posto migliore per prenderla. È il senso pratico di una matrice che emerge dal basso invece di essere dichiarata.
I due documenti precedenti descrivono i primi due livelli: il processo che espone se stesso, e il supervisore che ripubblica le letture dei propri processi — raccolte dalla sonda che già gira, perché i processi di lavoro non parlano HTTP.
Con più macchine la stessa forma si ripete di un livello, e questa è la proprietà interessante: il terzo livello non è un meccanismo nuovo. Un sub-commander sta verso i propri processi esattamente come il vertice sta verso i suoi, quindi espone le metriche del proprio dominio nello stesso modo. Il vertice, a sua volta, espone ciò che sa alla propria scala.
E ciò che il vertice sa è deliberatamente più grosso, per la stessa ragione per cui la sua tabella degli indirizzi è più grossa: non tiene un doppione di quello che sanno i domini.
| Livello | Chi lo espone | Le grandezze |
|---|---|---|
| Il processo | se stesso, quando serve HTTP; altrimenti risponde al supervisore sul canale | traffico e tempi di risposta, pool di lavoro, sessioni e accessi, task nello spool |
| Il dominio | il suo titolare — il vertice per i processi di casa, il sub-commander per gli altri | saturazione e memoria di ciascun processo, ampiezza del pool, nascite e ritiri con la causa, spostamenti, traffico dello stato vivo |
| Il sistema | il vertice | capacità offerta e impegnata per dominio, distribuzione delle persone fra domini e fra gruppi, budget assegnati e consumati, canali agganciati e loro cadute, operazioni fra gruppi |
La raccolta non attraversa la gerarchia: chi raccoglie interroga ogni livello dove è esposto, senza che un livello debba fare da tramite per le metriche di quello sotto. È l'opposto di quel che accade al traffico applicativo, che invece segue i rami dell'albero — e la differenza è voluta, perché il percorso delle metriche non deve poter influenzare le prestazioni di ciò che misura.
Un dominio irraggiungibile non fa cadere la lettura del sistema: risulta come tale, esattamente come nel pannello di supervisione. Un buco nei dati che si dichiara è un'informazione; un buco che passa per zero è un errore.
Gli allarmi si scrivono una volta sola. «La saturazione di qualcosa è oltre soglia da dieci minuti» ha la stessa forma per un processo, per un pool e per un dominio intero — e la stessa dashboard, cambiando solo l'etichetta, serve il caso mono-macchina e il parco di dieci nodi. Chi cresce non cambia strumenti: cambia solo quante righe legge.
Il documento descrive l'architettura distribuita nella sua forma compiuta. Questa appendice è il solo posto dove si legge lo stato di avanzamento.
Il fondamento è già in piedi: il protocollo del canale è nato con due collocazioni — in processo e su socket — e una sola batteria di test di contratto le verifica insieme. È la premessa che rende il sub-commander un'aggiunta e non una riscrittura, perché non introduce un protocollo nuovo: monta quello esistente un livello più in basso.
| Capitolo | Stato | Nota |
|---|---|---|
| Protocollo del canale con più collocazioni | operativo | in processo e su socket, verificate dalla stessa batteria di test |
| Il vertice come titolare del dominio di casa | operativo | genera, sorveglia, rilancia, misura |
| Spostamento a caldo di una persona fra processi | operativo | la primitiva su cui poggia ogni ricollocazione, anche fra domini |
| Sopravvivenza dello stato al riavvio | operativo | raccolta allo spegnimento, ripiazzamento all'avvio |
| Misura delle risorse e giudizio sull'occupazione | operativo | saturazione per componente, memoria misurata onestamente |
| Canali di specie mista sul vertice | in implementazione | oggi il vertice tiene processi di casa; l'aggancio di un dominio remoto è il passo che apre il multi-macchina |
| Sub-commander | in implementazione | è la stessa classe del vertice con il genitore armato: il disegno prevede due collocazioni fin dall'inizio, oggi ne esiste una |
| Capogruppo come decisore della propria versione | in implementazione | le politiche — nascita, ritiro, ribilanciamento, ricambio — sono operative sul pool; resta da separarle per gruppo |
| Mappa grossa e risoluzione a due tappe | in implementazione | la mappa fine utente → processo è operativa; l'indirizzo tipizzato è il passo che la affianca |
| Attuatore per cluster | in implementazione | materializzazione dei pod via API, con bilanciatore, autoscaling e rilascio graduale nativi deliberatamente non usati |
| Pannello dei domini e matrice gruppi × domini | in implementazione | il pannello del pool è operativo e il sub-commander espone lo stesso; la vista di sistema arriva con i canali di specie mista |
| Metriche di sistema in formato Prometheus | in implementazione | terzo livello: capacità per dominio, distribuzione fra gruppi, budget · stesso formato dei livelli sottostanti |
| Budget per gruppo | in implementazione | l'arbitraggio del ferro condiviso fra sezioni autonome |
Ultimo aggiornamento della roadmap: 13 agosto 2026. Le voci del corpo del documento non cambiano quando una riga di questa tabella passa a «operativo».
Il primo descrive il server: routing agnostico dal trasporto, grammatiche di configurazione, OpenAPI e MCP, autenticazione, sessioni, task, storage. Il secondo spiega perché lo stato di lavoro vive in memoria, perché questo lega una persona al suo processo, e come i gruppi permettono di far convivere due versioni. Questo terzo porta lo stesso disegno oltre il confine della macchina.