Consistenza - 2

Lezione del 2019-05-29 mer.

I modelli di consistenza sono contratti che assicurano un determinato comportamento in relazione ai dati (data centric), o al client (client centric).

I modelli di consistenza usati più frequentemente sono i sequential consistency: sono i più semplici da comprendere, i più vicini alla mentalità umana, e quindi anche quelli utilizzati con più successo. Esistono modelli via via più complessi, ma si consideri il fatto che un modello più complesso è anche più difficile da comprendere, e quindi da utilizzare.

Qui vedremo i principi di gestione delle repliche ed elencheremo alcuni esempi di implementazione.

Gestione delle repliche

Per la gestione delle repliche bisogna considerare

  • il loro posizionamento;

  • i meccanismi da utilizzare per mantenerle consistenti.

A sua volta il problema del posizionamento può essere suddiviso in due:

  • dove localizzare i server delle repliche;

  • come piazzare i contenuti.

Replica dei contenuti e il loro posizionamento

La replica dei contenuti e il loro posizionamento può essere considerato da un punto di vista di organizzazione logica a seconda del tipo di copie del data store:

_images/34_gestione_repliche.svg

Gli item contrassegnati come replica permanente sono l’insieme iniziale delle repliche, che costituiscono il data store. Ad esempio, un sito web ad alto traffico può essere replicato su più server acceduti tramite switch in load balancing. Oppure in mirroring il client sceglie su quale server accedere.

Come detto in precedenti lezioni, la replica può essere comandata dal server (server-initiated replicas).

Ad esempio esistono analizzatori di traffico web in grado di individuare a runtime le zone da cui arrivano le richieste. Se il sistema osserva un carico costante da una determinata zona, può decidere di eseguire una replica a runtimesu in un server localizzato nella zona target, per aumentare la velocità di risposta e abbattere l’impegno di rete.

Questo stesso principio (porre il dato in una località più vicina possibile all’utilizzatore) viene utilizzato anche per l’invio di dati massivi (video), ed è un principio generale. Si noti che questo tipo di replica può essere decisa solo dal server: è questa macchina che può analizzare tutte le richieste, i client non hanno questa visibilità.

Una implementazine efficace di questo principio non è semplice. Vanno considerati diversi fattori, inclusa la dimensione dei dati da copiare. Se è necessario copiare grandi quantità di dati, è possibile che l’attività di replica venga completata in tempi lunghi, quando potrebbe essere superata la necessità che ha dato il via all’operazione.

Consideriamo ora le repliche comandate dai client (client-initiated replicas). In questi casi, tipicamente si parla di cache. E si tratta di repliche parziali del contenuto del server, non di repliche totali.

Se il dato richiesto è in cache, il vantaggio è notevole: non vi è nessun impegno né di rete, né del server: il client carica il dato presente localmente in tempi brevissimi. Per contro, è necessario capire se l’informazione in cache è valida o meno, e tipicamente un dato in cache si tiene per una quantità di tempo limitata. Ad esempio, nel caso di web browser, questo se ha una pagina in cache, chiede al web server il solo HEAD della pagina, al fine di controllare la validità del contenuto in cache. Se confermato, usa la cahe, altrimenti prosegue con una GET della pagina dal server, visualizzandola e sostituento quella in cache.

Come effettuare gli aggiornamenti

Il problema principe delle repliche è mantenere la loro consistenza. Se i dati fossero completamente statici sarebbe tutto ok: una volta eseguita una replica, non si sposta più nulla. Purtroppo non è così. I dati cambiano, e le repliche vanno allineate di conseguenza. Se gli aggiornamenti sono molto frequenti, si può avere un eccesso di traffico per l’aggiornamento delle repliche tale da saturare il sistema.

Per aggiornare le repliche a fronte di un update, si può:

  • inviare il dato modificato;

  • inviare l’operazione;

  • inviare una sola notifica di un avvenuto update.

La terza opzione consiste nell’inviare alla replica un invaliation message che avverte la replica del fatto che è divenuta (parzialmente) inconsistente. Il messaggio può specificare quale parte del data store deve essere aggiornato. In tal modo si impegna meno banda passante. Da' buoni risultati quando le letture sono molte meno degli aggiornamenti. Questo è un esempio di lazy consistency: aggiornerò quando potrò.

Se la lazy consistency può andare bene per i server Google, non è così per repliche di server con DB bancari. In questo caso non è accettable sapere che un DB è non valido: li vogliamo tutti allineati come contenuti.

In questi casi, o si trasferiscono i dati modificati (quando il rapporto letture/scritture è relativamente elevato), o si invia l’operazione di update da effettuare (active replication).

Pull vs Push

Un altro aspetto da considerare è il verso dell’operazione di aggiornamento 1:

  • push, quando l’aggiornamento è spinto verso chi deve essere aggiornato;

  • pull, quando chi deve essere aggiornato richiede l’aggiornamento.

In caso di approccio push-based gli update sono propagati alle repliche senza che queste ultime chiedano di essere aggiornate. Sono protocolli utilizzati dai server e sono applicati quando le repliche devono essere mantenute identiche, ad es. in ambito bancario. Il rapporto letture/update per ogni replica è relativamente elevato.

Se l’approccio è pull-based, un server o un client richiede ad un altro server di inviargli ogni update che è pronto al momento della richiesta. Viene utilizzato spesso per la gestione delle cache dei client. Risulta efficiente se il rapporto letture/scritture è relativamente basso.

Queste modalità di lavoro dipendono anche dal modo in cui interagiscono i programmi nella sorgente rispetto la destinazione:

  • interrupt-driven per il push,

  • mentre si usa il polling per il pull.

Zigbee prevede entrambe le modalità di funzionamento: il controllore può fare pull dei dati dalle periferiche, o si possono configurare periferiche che facciano push verso router e/o controller.

Un esempio di pull è la gestione della cache da parte dei web browser nel client. In pratica il web browser controlla la cache locale per verificare se vi è l’oggetto da richiedere. Se l’oggetto è in cache ed è valido (aggiornato) allora il browser lo visualizza. Il protocollo HTTP supporta questo tipo di comportamento tramite la richiesta If-Modified-Since, cui il server risponde inviando la risorsa cercata solo se è stata modificata dopo il timestamp indicato nella richiesta.

Protocolli di consistenza

Un protocollo di consistenza descrive una implementazione di uno specifico modello di consistenza.

Primary-based

Questo è un protocollo centralizzato. Ha la proprietà che tutti i processi vedono tutte le operazioni di write nello stesso ordine.

Viene illustrato dal seguente diagramma in cui viene utilizzato per mantenere le repliche consistenti.

_images/35_primary_based.svg

Client 1 invia W1: una richiesta di modifica dell’item x. La replica che riceve la richiesta la inoltra (messaggio W2) al server primario per x. Questo invia il comando di update (messaggio W3) a tutti e si pone in attesa dei messaggi di conferma. Quando il server primario ha ricevuto tutte le conferme (messaggi W4) viene inviato al Client 1 il messaggio W5 di update completato 2.

In questo contesto vi è sequential consistency perché l’ordine delle richieste di update è controllato dal (solo) server primario.

Si noti che questo schema non certifica che l’ordine delle operazioni sia quello di sottomissione da parte dei client, ma quello di ordinamento da parte del server primario. Server primario che può ricevere due richieste di client diversi con un ordine invertito rispetto l’originale.

Praticamente tutti i prodotti commerciali di replica sono primary based. Per avere qualcosa di più evoluto si deve ricorrere alla blockchain.

Questa architettura è push: il primary server spinge gli aggiornamenti verso le repliche.

Local-write

Questo è simile al primary-based, ma con la particolarità che il ruolo di primario viene assunto dinamicamente dal server che ha ricevuto la richiesta.

_images/36_local_write.svg

In questo caso, client2 invia il messaggio di update W1 sull’item x. La replica che ha ricevuto l’ordine avoca a se il ruolo di primario per l’item x. Il vecchio primario gli comunica l’avvenuto cambio di ruolo inviando il messaggio W2 3. A questo punto il nuovo primario può eseguire l’update richiesto e rispondere a client2 che l’operazione è eseguita inviando il messaggio W3. Dopo di che invia l’ordine di update a tutte le altre repliche (messaggio W4), e si mette in attesa delle risposte. Quando tutte le risposte W5 saranno arrivate, il data store sarà nuovamente allineato 4.

Si nota la differenza dei tempi per l’invio di operazione completata rispetto il caso di primary-based. In quel caso il client veniva sganciato al termine di tutte le operazioni di aggiornamento dopo la ricezione di tutti gli ack (messaggio W5 dello schema primary-based). Qui invece la comunicazione di ok al client è al terzo passaggio (messaggio W3 dello schema local-write), non appena il nuovo primario ha assunto il ruolo e ha fatto la propria modifica.

Si osserva che in questi schemi nulla si assicura riguardo il comportamento in fase di lettura. Si può ipotizzare che durante le fasi di update, client diversi leggano valori diversi dell’item x, in funzione di quale replica viene utilizzata per la lettura e a che punto è arrivata la replica nella fase di aggiornamento.


1

I concetti di pull e push sono generali. Non vengono usati solo per l’aggiornamento delle repliche. Ad esempio sono presenti anche nella raccolta dei dati da sensori in ambito domotico. Oppure MQTT: considerato dal lato del publisher, fa push delle notizie verso i canali pubblicati.

2

Dall’illustrazione non è chiaro se W5 viene inviata dopo l’arrivo di tutti i W4, o può essere inviata a client 1 al termine dell’update della singola replica. Io direi che W5 viene inviata dopo l’arrivo di tutti gli ack. Il perché lo si può capire ipotizzando il seguente scenario. Replica 1 e replica 2 ricevono i W3. Replica 1 esegue, invia W4 e subito dopo invia W5. replica 2 intanto tenta di eseguire, ma non riesce. Di conseguenza comunica al primary il fallimento dell’operazione. A sua volta il primary osserva che il sistema è in uno stato inconsistente, per recuperare l’omogeneità invia il comando di disfacimento dell’ultima operazione alle repliche che sono riuscite ad eseguirlo, tra cui replica 1. A questo punto replica 1 deve tornare sui suoi passi, ma avrebbe già comunicato a client 1 che l’operazione è riuscita: incongruenza non accettabile.

3

Si nota che il nuovo primario per x non può accettare nuove richieste di spostamento della responsabilità di primario per l’item finché non avrà completato l’operazione di update richiesta e allineato di conseguenza il data store.

4

E, se necessario, sarà possibile spostare il primario per l’item x.