genro-asgi · documento 4 di 4

Un sito GenroPy senza il suo daemon

Come un sito già scritto sale su questo server senza essere riscritto, che cosa succede al processo separato che oggi ne tiene il registro, e come si dimostra che il comportamento non è cambiato.

Il punto di partenzaChe cos'è il daemon, e perché c'è

Un sito GenroPy in esercizio non è un processo: sono molti processi che servono le stesse persone. Ognuno deve sapere chi è collegato, quali schede ha aperte, che cosa quelle schede stanno guardando e che cosa è cambiato da notificare. Quella conoscenza non può stare dentro un processo solo, perché la richiesta successiva della stessa persona può arrivare a un altro.

La soluzione classica è portarla fuori. Il registro del sito — utenti, connessioni, pagine, con i loro dati vivi e i cambiamenti in attesa — vive in un processo a parte, il daemon, e ogni processo che serve richieste ci si collega. È una soluzione corretta, ed è la ragione per cui esiste: se lo stato deve essere condiviso da molti, va messo dove tutti lo vedono.

Il daemon non è un difetto di GenroPy. È la conseguenza necessaria di una scelta precedente: molti processi intercambiabili che servono la stessa persona.

Il prezzoQuello che quella scelta costa

Vale la pena essere espliciti, perché sono costi che si pagano ogni giorno e a cui ci si abitua.

  • Due processi da avviare, in ordine. Prima il daemon, poi i processi che servono: il sito legge l'indirizzo del daemon da un file mentre si costruisce, quindi partire nell'ordine sbagliato significa parlare a un indirizzo a cui non risponde nessuno. È un vincolo che ogni script di avvio deve conoscere.
  • Ogni lettura del registro è un viaggio. Sapere chi è una connessione, che cosa una pagina ha in attesa, quali tabelle sta guardando: ogni domanda esce dal processo e torna. Sono domande che una pagina viva fa in continuazione.
  • Una morte sola le porta via tutte. Il daemon è un punto singolo: se cade, cade la fotografia di tutti, non di qualcuno.
  • Un pezzo in più da installare e sorvegliare. Una porta, un file di indirizzo, un processo che qualcuno deve accorgersi se non c'è.

La conseguenzaIl registro torna dentro il processo

Il secondo documento descrive una scelta diversa: ogni persona vive in un processo solo, e tutte le sue pagine stanno lì. Non è una scelta fatta per togliere il daemon — è fatta perché lo stato di lavoro di una sessione di otto ore non entra in un tubo e non va serializzato a ogni colpo. Ma ha una conseguenza diretta su questo capitolo.

Se tutte le pagine di una persona stanno nello stesso processo, la fotografia che doveva essere condivisa non deve più esserlo: è già locale. Chi è quella connessione, che cosa quella pagina ha in attesa, quali tabelle guarda — sono domande a cui il processo risponde da sé, senza uscire.

Resta ciò che attraversa davvero: un valore comune a tutti, un cambiamento che deve raggiungere la pagina di un'altra persona. Quelle cose passano dal supervisore, indirizzate, sulla corsia che il processo tiene già aperta per ricevere gli ordini. Non c'è un secondo canale e non c'è un processo in più: il filo che serve a governare i processi è lo stesso che porta i loro scambi.

Emulare, non reinterpretare

La regola sotto tutto questo lavoro è che il comportamento osservabile deve combaciare con l'originale. Prima di progettare un meccanismo — la consegna dei cambiamenti, le pendenze di una pagina, il giro di una sottoscrizione — si ricostruisce come funziona nel daemon di GenroPy, leggendolo nel suo codice. Il consumatore finale è un sito che quel comportamento se lo aspetta, e un'architettura più elegante che risponde diversamente è un'architettura sbagliata.

Il meccanismoGenroPy non viene modificato

La domanda pratica è come si sostituisce il registro senza mettere le mani nel framework. La risposta è un cancello dichiarato dentro GenroPy stesso, e la sua forma merita di essere raccontata perché è la parte che rende la cosa reversibile.

Il namespace del daemon può essere sostituito per intero da un pacchetto installato che si dichiari come fornitore. Ma la sostituzione non è automatica: avviene solo quando una variabile d'ambiente nomina il fornitore. Con la variabile assente non c'è nessuna sostituzione e nemmeno la ricerca di un fornitore — il pacchetto è quello che era.

Per processo, non per ambiente

La scelta sta in una variabile d'ambiente, quindi lo stack classico e quello nuovo convivono nella stessa installazione e si sceglie a ogni avvio quale eseguire. Non serve un secondo virtualenv per provare.

Nominare, non indovinare

Il fornitore si chiede per nome, e se quel nome non trova esattamente un fornitore la cosa fallisce. Una richiesta esplicita è una dichiarazione di configurazione: non deve poter degradare in «allora avvio il daemon classico».

Il sito importa quello che ha sempre importato

Il codice del sito continua a chiedere il client del registro dallo stesso posto di sempre. Quello che cambia è che cosa trova. Nessuna riga dell'applicativo sa che il daemon non c'è.

Si torna indietro togliendo una parola

Non c'è una migrazione da annullare. Si riavvia senza quella variabile e il sito è di nuovo sullo stack classico, con il suo daemon, esattamente come prima.

In praticaChe cosa cambia per chi ha già un sito

Poco, ed è il punto. Il sito non si tocca: stesso codice, stessa configurazione, stesse pagine. Cambia il comando con cui lo si avvia.

  • Un comando al posto dell'altro. Il server sincrono lascia il posto a quello ASGI. Stesse opzioni, stesso sito, e in più il canale WebSocket che prima non c'era.
  • Nessun daemon da avviare. Sparisce il processo separato, sparisce l'ordine di avvio, sparisce il file con il suo indirizzo.
  • Il pool non si dimensiona a mano. Non c'è un numero di processi da dichiarare né una modalità da scegliere: il pool parte con uno e cresce quando serve, e ogni persona resta sul suo.
  • Il sito eredita quello che il piano gli dà. Sessione appiccicata all'identità, stato vivo per pagina, spostamento a caldo, sopravvivenza al riavvio: un applicativo scritto anni fa entra nel piano senza sapere di esserci entrato.
  • Si sviluppa su quello che va in produzione. Oggi lo sviluppo gira sul server di prova di werkzeug e l'esercizio su un altro stack: sono due macchine diverse sotto lo stesso sito. Qui il comando è uno solo, ed è quello vero — stesso server, stesso pool, stesso registro, sulla tua macchina come sul cliente.
  • Salvi un file e non rifai il login. La sentinella che guarda i sorgenti fa rinascere il server a ogni modifica, e l'uscita che provoca è di quelle che salvano: le persone collegate vengono parcheggiate congelate e riprese dal processo nuovo. Senza il daemon lo stato vive nei processi, quindi senza questo meccanismo ogni salvataggio scollegherebbe tutti.
La distanza che sparisce

Vale la pena fermarsi su questo punto, perché è quello che si sente ogni giorno e non compare in nessun elenco di funzionalità. Nel modo classico lo sviluppo gira su un server di prova e l'esercizio su un altro, e i due non sono la stessa cosa: servono i file in modo diverso, trattano le connessioni in modo diverso, uno esegue una richiesta per volta e l'altro no. Ogni differenza è un posto dove può nascere un difetto che si vede solo dal cliente e sulla macchina di chi l'ha scritto non si riproduce.

Con un comando solo quella distanza non c'è. Quello che cambia fra il tuo portatile e il cliente è un flag di modalità, e il server non si ramifica su quel flag in nessun punto. A leggerlo è il sito, per cui quel flag è il cancello che rende disponibili i suoi strumenti per chi programma — ciascuno dei quali ha poi il proprio interruttore. La macchina che serve il sito non cambia.

Del vecchio server di prova resta disponibile una cosa sola, la pagina di errore che mostra la traccia e permette di valutare Python nel processo, e va chiesta per nome: la modalità di sviluppo da sola non la accende più, così quella pagina non può comparire per distrazione.

Il confine, dichiarato

Il core non impara la logica di GenroPy. Quello che il sito deve fornire e quello che può consumare è un contratto: la callable sincrona da invocare, il sito che battezza la propria connessione mentre serve, i verbi del piano dati. Il ponte implementa quel contratto; il server offre meccanismi generali. È la regola che impedisce al server di diventare il server di un solo framework.

La provaIl comparatore: la stessa sessione sui due stack

Sostituire il registro di un sito in esercizio è una promessa forte, e una promessa forte va dimostrata. Non con una batteria di test scritti da chi ha fatto il lavoro — quelli provano che il codice fa quello che l'autore credeva — ma mettendo i due stack a rispondere alla stessa cosa.

Il banco di confronto fa esattamente questo. Si naviga il sito attraverso un proxy, e ogni richiesta viene eseguita due volte: prima sullo stack classico, poi sul ponte. La risposta che il browser riceve è sempre quella dello stack classico, quindi un difetto del ponte non blocca mai chi sta lavorando. Il ponte è l'ombra, e quello che risponde viene solo confrontato.

Uguale significa uguale per struttura

Il confronto non è fra due testi. I due stack hanno ciascuno le proprie sessioni, i propri identificatori di pagina, le proprie connessioni: chiedere che quei numeri coincidano sarebbe chiedere una cosa priva di senso. Quello che deve coincidere è la sequenza delle chiamate al registro, la forma dei loro argomenti e la forma delle loro risposte. Se il cliente letto e modificato è Mario Rossi da una parte e Luigi Bianchi dall'altra, non è una divergenza.

Ci si ferma alla prima divergenza

È la decisione che rende il banco utile invece di ingombrante. La prima versione confrontava due tracce finite, a posteriori — e il problema di quel disegno è che una divergenza sporca tutto quello che viene dopo, così un rapporto con seicento differenze ne contiene una vera e cinquecentonovantanove conseguenze.

Una divergenza che contamina tutto il seguito non costa niente, se nessuno cammina oltre la prima.

Quindi il confronto è vivo: alla prima differenza il proxy stampa il rapporto, lo scrive accanto alla registrazione della sessione, e smette di mandare traffico all'ombra. Non chiude niente. I due stack restano in piedi e continuano a rispondere, perché il senso di confrontare dal vivo è poter guardare dentro tutti e due mentre la differenza è ancora lì. Il browser continua a funzionare: sta parlando con lo stack classico, come sempre.

Che cosa viene mandato e che cosa viene confrontato

Sono due domande diverse, e tenerle separate è ciò che rende i risultati credibili. Al ponte viene mandato tutto quello che il browser chiede, file statici compresi: se l'ombra vedesse meno traffico dell'originale, il suo stato potrebbe divergere per una ragione introdotta dal banco invece che dal codice. Confrontato, invece, è solo ciò che dice qualcosa — un file statico produce sempre la stessa coppia di chiamate e non insegna niente.

Le differenze che non sono differenze

Alcune divergenze sono prodotte dalla situazione, non dal codice, e vanno riconosciute per nome invece di essere discusse ogni volta. Una scheda rimasta aperta da una sessione precedente porta un cookie che il registro nuovo non conosce: lo stack classico la rifiuta, l'ombra — che non ha ereditato niente — la serve. I due stack non stanno dicendo cose diverse, gli si stanno facendo domande diverse. Ogni regola di questo tipo è dichiarata, con la data in cui è stata decisa e la chiamata su cui è stata misurata.

Le misureI benchmark, e perché non c'è ancora un numero

La domanda che tutti fanno per prima è quanto va più veloce. La risposta onesta, oggi, è che non è stata ancora misurata — e non per dimenticanza: è un ordine deciso.

Il programma ha tre fasi. Le prime due riguardano la fedeltà: che il ponte faccia le stesse cose dello stack classico. La terza riguarda la velocità. Sono in quest'ordine per una ragione che si vede subito appena si prova a invertirlo: gli strumenti che dimostrano la fedeltà registrano ogni scambio e ogni chiamata al registro, e una misura di velocità presa con quella registrazione accesa non misura il sistema, misura lo strumento.

Quindi durante le prime due fasi la strumentazione può essere pesante quanto le serve, e i tempi non si leggono. La terza fase parte con la raccolta spenta, e allora i tempi contano.

La scala delle misure

Gli strumenti ci sono già, e sono costruiti in modo che un rallentamento si possa attribuire invece che indovinare. Ogni gradino isola uno strato in più.

GradinoChe cosa isola
Il pavimentoIl costo del server e basta: niente database, niente logica di pagina.
Il ping autenticatoIl costo del dialogo continuo, mentre il numero di sessioni collegate sale.
Il record singoloUna query indicizzata e una risposta piccola: la macchina di una chiamata, senza il peso dei dati.
La sessione interaIl rigioco di una sessione vera registrata da un browser: login, pagine, le letture pesanti.

Ogni gradino si può puntare anche sullo stack classico, con lo stesso comando: è così che i due diventano confrontabili invece che raccontabili.

La memoria prima della velocità

La terza fase comincia dalla memoria, non dai tempi, e anche questo è deliberato. La risorsa che si esaurisce per prima in un sistema che tiene stato è la memoria, non la CPU: la si misura su tutto lo stack, a parità di processi, da fermo e sotto carico. I tempi vengono dopo.

Quello che questo documento non dice

Non c'è, in queste pagine, nessun confronto di prestazioni fra i due stack, e non va dedotto: gli strumenti per produrlo esistono, il confronto non è ancora stato eseguito. Chi deve decidere sulla base di numeri deve aspettare la terza fase, che parte quando la fedeltà è chiusa.

Le condizioni di ogni misura vengono dichiarate insieme al risultato — quante persone, quali processi, quale versione e quale modo di esecuzione da entrambe le parti — perché un numero senza le sue condizioni non è confrontabile con nessun altro numero. E c'è un caso peggiore del numero senza condizioni: il numero le cui condizioni sono dichiarate e non sono state imposte. È successo, e vale la pena raccontarlo: i due stack venivano avviati leggendo il modo di esecuzione da una riga di comando, mentre il sito lo leggeva dalla propria configurazione. Per un tratto del lavoro uno dei due ha girato in modalità di sviluppo mentre la riga che descriveva quella prova dichiarava il contrario, e nessuno poteva accorgersene guardando i risultati. Da allora entrambe le letture passano dalla stessa fonte, e una riga che dichiara una condizione che nessuno ha imposto è considerata un difetto di categoria, non un incidente singolo.

AppendiceDove siamo oggi

Questo documento descrive il ponte nella sua forma compiuta. Lo stato di avanzamento vive in un documento suo: stato attuale, voce per voce, con la prova accanto a ciascuna. La voce che riguarda questo capitolo è il contratto del bridge.

Due cose vanno dette qui e non lasciate dedurre. Il ponte pubblicato oggi è agganciato alla versione precedente del nucleo e non è ancora stato portato su questa: finché non lo è, il contratto descritto in queste pagine non è mai stato esercitato da un sito vero su questo core. E il banco di confronto sta lavorando alla seconda delle tre fasi — la sessione di riferimento riprodotta sul ponte senza divergenze inspiegate.