Salta al contenuto

Blog ·

L’unico limite reale all’occupazione di un rack

Due tecnici scelgono la stessa unità rack nello stesso istante. A entrambi la maschera dice libera. Decide un indice univoco parziale, e nient’altro può.

Di Provibr

Due tecnici stanno collocando macchine nello stesso rack. Entrambe aprono la maschera, entrambe guardano la U nove, entrambe vedono uno slot libero, entrambe premono salva. Qualunque cosa faccia il codice applicativo fra quelle due richieste, c’è una finestra in cui entrambe leggono la stessa risposta, e nessuna quantità di verifiche prima della scrittura la chiude.

È la race condition più vecchia del manuale e ha una sola risposta corretta: decide il database. Tutto quello che sta sopra serve a produrre un rifiuto leggibile, non a impedire una collisione. Invertire quell’ordine è esattamente il modo in cui si finisce con una regola di unicità che perde in una giornata di traffico e, in un datacenter, una perdita significa qualcuno con un carrello fermo davanti a un rack che è già pieno.

Il confine è un indice

L’occupazione è memorizzata due volte di proposito. Un’assegnazione ha una riga di intestazione, che è la cosa che qualcuno ha creato: questo lato, da questa unità a quest’altra e, per una collocazione, la macchina che ci sta dentro. E ha una riga per ogni slot occupato: questo rack, questo lato, questa singola U.

La tabella degli slot esiste per un solo motivo. Porta un indice univoco parziale su rack, lato e numero di slot, ristretto alle righe che non sono state rilasciate. Quell’indice è l’unico limite reale all’occupazione in tutto il sistema. Nient’altro può esserlo, perché un indice univoco non può fare join: vede solo le colonne della riga su cui sta, quindi il rack e il lato vanno denormalizzati sulla riga dello slot perché il vincolo possa esistere.

Il prezzo è che rilasciare un’assegnazione sono due update dentro una sola transazione, dato che il timestamp di rilascio vive sia sull’intestazione sia sugli slot. Il ritorno è che l’invariante la fa rispettare il database anziché l’attenzione di chi scriverà la prossima funzionalità.

L’abbiamo misurato anziché ragionarci sopra. Due connessioni Postgres reali, una barriera reciproca perché entrambe siano dentro la propria transazione prima che una delle due faccia commit, entrambe a prendersi il fronte U nove. Risultato: esattamente un’assegnazione scritta, un errore 23505 che nomina quell’indice, e nessuna intestazione orfana lasciata indietro dal rollback. È la frase che si vuole poter scrivere su una garanzia di concorrenza, e non la si può scrivere leggendo il codice.

A che cosa serve davvero il controllo prima della scrittura

C’è una funzione pura che pianifica un’assegnazione, e rifiuta in tre modi, in quest’ordine.

Il lato non esiste. Un telaio aperto senza retro non è un rack in cui si appende qualcosa sul dietro, ed è per questo che il fatto che un rack sostenga un fronte e un retro è una proprietà del rack anziché una supposizione.

Fuori dai limiti. L’intero intervallo deve stare fra l’unità di partenza del rack e la sua fine, estremi compresi. Non tutti i rack partono da uno e non tutti numerano verso l’alto, quindi questa è aritmetica contro la geometria di quel particolare rack e non contro il numero quarantadue.

Occupato, e questo rifiuto porta con sé l’elenco degli slot in conflitto. Un messaggio che dice «pieno» rimanda qualcuno alla maschera senza la minima idea di dove guardare. Un messaggio che dice che il fronte U nove e la U dieci sono presi lo manda alla risposta.

Nessuno di questi è il confine. Entrambi i tecnici del primo paragrafo passano tutti e tre. La funzione esiste perché il caso ordinario, cioè una persona per volta che sbaglia, produca una frase invece di un codice di errore del database.

La profondità ignota si comporta come piena

Ogni macchina ha una classe di profondità: piena, a metà o ignota. Un oggetto a metà profondità occupa solo il lato a cui è appeso. Un oggetto a profondità piena occupa fronte e retro.

Anche l’ignota occupa fronte e retro, e quella scelta merita di essere difesa perché l’impostazione intuitiva è l’opposta. L’ignoto sembra un dato mancante che si compilerà più avanti e, nell’attesa, sembra innocuo lasciare libero l’altro lato.

Innocuo non è. Una macchina che si rivela a profondità piena non si può installare se nel frattempo qualcuno ha riempito il retro, e la correzione avviene con la macchina già in sede. Bloccare il retro costa uno slot che si sarebbe potuto avere. Non bloccarlo costa una consegna. L’ignoto non è l’assenza di un vincolo, è un vincolo a cui non si sa ancora dare un nome, e il suo lato sicuro è quello prudente.

Lo stesso principio attraversa il resto del modello di capacità: un peso ignoto, un assorbimento ignoto o uno stato di assegnazione stantio si leggono come ignoti o come bloccati, e mai come capacità libera.

Una collocazione non scade mai

Le assegnazioni sono di quattro tipi. Una collocazione è una macchina che sta fisicamente lì. Un blocco è spazio messo fuori servizio per il cablaggio. Un fermo per spostamento e una prenotazione sono i due che tengono lo spazio per qualcosa in movimento o in ordine. Solo il primo ha oggi una schermata; gli altri tre esistono come dati perché la primitiva sia una cosa sola anziché quattro cose simili.

Un vincolo di controllo del database impone che una collocazione non abbia scadenza. Sembra una regola da poco. Quello che impedisce è una riga che smette di contare in silenzio un martedì mentre la macchina è ancora avvitata dentro, ossia un rack che il software crede abbia uno slot libero. Una prenotazione che scade è una funzionalità; una collocazione che scade è una bugia con un timer sopra.

Un secondo vincolo richiede che una collocazione abbia una macchina, dato che gli altri tre tipi legittimamente non ne hanno, e un indice univoco parziale ammette al massimo una collocazione attiva per macchina. Senza quell’indice la stessa macchina può risultare registrata in due rack alla volta. Non è un problema di qualità del dato che si nota in un rapporto; è un problema che si nota quando qualcuno è fermo davanti al rack sbagliato.

Vale la pena sapere una cosa sugli indici di questa forma: sono globali anziché delimitati per organizzazione. Una macchina che porta già una riga attiva entra in collisione con il vincolo di unicità anziché con un controllo di proprietà, anche quando il secondo tentativo arriva da un’altra organizzazione. È il lato sicuro, e significa anche che per provare il caso delle due organizzazioni serve una terza macchina, perché il vincolo scatta prima che la logica di proprietà arrivi mai a girare.

La stessa forma un livello più su

Consegnare una macchina a un cliente ha la struttura identica, e ha bisogno di due indici anziché di uno, perché sono due invarianti che si è soliti enunciare come una frase sola.

Un servizio sta al massimo su una macchina. Senza di ciò, un servizio può puntare a due macchine e la sua scheda di posizione punta in due direzioni.

Una macchina è assegnata al massimo a un servizio. Senza di ciò, la stessa macchina viene venduta due volte, e lo si scopre quando chiamano due clienti.

Entrambi sono indici univoci parziali su righe la cui data di fine è ancora vuota, che è la stessa costruzione dell’indice sugli slot e per lo stesso motivo: il controllo nell’applicazione produce il rifiuto leggibile, l’indice decide chi vince.

Una regola non è potuta entrare nel database. Un’associazione deve puntare a un servizio dedicato, e un vincolo di controllo non può leggere un’altra tabella. Quella vive nella funzione che scrive l’associazione, e vale la pena essere espliciti su quali delle proprie regole le fa rispettare lo schema e quali le fa rispettare un percorso di codice, perché il secondo tipo vale solo quanto il numero di percorsi di scrittura che ci si ricorda di farci passare.

Le associazioni sono storiche anziché correnti. È la data di fine a rendere inattiva la riga, e ogni lettura di una posizione fisica filtra su di essa e sull’organizzazione sia dell’associazione sia della macchina. Si tolgano quei filtri e un ex cliente legge il rack e l’unità del cliente venuto dopo di lui. È stato dimostrato rosso prima di essere corretto, che è l’unico modo per sapere che un filtro sta lavorando.

Che cosa metteremmo per iscritto

  1. Mettere l’unicità dove sta la riga. Se il vincolo ha bisogno di un join, si denormalizzino le colonne che gli servono; un indice non può fare join e un trigger non è un vincolo.
  2. Materializzare l’unità di contesa. Una riga per ogni slot occupato, non un intervallo su un’intestazione, perché un intervallo non può essere univoco rispetto a un altro intervallo.
  3. Dimostrare la race condition con due connessioni e una barriera. Ragionare sui livelli di isolamento non è una prova.
  4. Lasciare che il controllo preventivo sia un messaggio, non una difesa, e fargli nominare gli slot esatti che confliggono.
  5. Far comportare l’ignoto come il caso costoso, così che una misura mancante non si presenti mai come spazio libero.

Per chiarezza su dove sta la cosa: il lavoro sulla logistica di datacenter è dietro un flag di rollout disattivato per impostazione predefinita, con sopra un’impostazione per organizzazione, quindi niente di quanto sopra è acquistabile oggi. Due cose non sono dimostrate: la misura della concorrenza è stata eseguita contro assegnazioni di tipo blocco in una prova diretta sul database anziché contro il percorso della collocazione vero e proprio, e i tipi fermo per spostamento e prenotazione non hanno ancora chi li scriva. L’abitudine più ampia di lasciare che sia il database a fare da arbitro compare anche altrove in questo prodotto, in come vengono distribuiti gli indirizzi di un pool e in come un intervallo di vmid sia in realtà un confine di permessi.

Dal lavoro sul datacenter di Provibr. Perché le cinque domande sulla capacità di un rack non vadano sommate sta in lo spazio in rack non è capacità, e che cosa succede quando un sistema esterno afferma di sapere dove sta una macchina sta in quando NetBox è la fonte di verità.

Inizi con un host e un agente

Crei un account, installi l’agente con un solo comando e colleghi il suo primo host Proxmox. Il periodo di prova dura 14 giorni e non chiede alcun dato di pagamento.