genro-asgi · documento 4 di 4
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.
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.
Vale la pena essere espliciti, perché sono costi che si pagano ogni giorno e a cui ci si abitua.
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.
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.
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.
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.
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 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'è.
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.
Poco, ed è il punto. Il sito non si tocca: stesso codice, stessa configurazione, stesse pagine. Cambia il comando con cui lo si avvia.
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 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.
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.
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.
È 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.
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.
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.
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.
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ù.
| Gradino | Che cosa isola |
|---|---|
| Il pavimento | Il costo del server e basta: niente database, niente logica di pagina. |
| Il ping autenticato | Il costo del dialogo continuo, mentre il numero di sessioni collegate sale. |
| Il record singolo | Una query indicizzata e una risposta piccola: la macchina di una chiamata, senza il peso dei dati. |
| La sessione intera | Il 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 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.
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.
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.