Modulo PeerServices - Database TTL

Implementiamo un servizio che mantiene un database distribuito. Ogni record in esso ha un Time To Live. Le chiavi sono in un set che può essere molto grande.

Ogni nodo partecipante può avere un limite di memoria, oltre il quale il nodo rifiuterà di immagazzinare altri record.

In questo servizio ipotiziamo che non ci siano vincoli per il client, cioè qualsiasi operazione è permessa (nei limiti del dominio delle chiavi e dei valori) senza alcun tipo di autorizzazione. Comunque risulterà facile implementare in un servizio analogo qualsiasi tipo di vincolo da parte del server; basterà codificare nella classe del valore di ritorno le eccezioni previste dal servizio.

Diamo per assunto che il lettore abbia già compreso negli altri documenti i concetti:

Operazione di inserimento

Il nodo q vuole inserire un record k=w nel database distribuito.

Ci sono 4 possibili esiti finali a questa richiesta:

  1. Il nodo q viene informato che al momento nella rete non c'è nessun partecipante al servizio. Chiamiamolo esito "NO_PARTICIPANTS".

  2. Il nodo q viene informato che la chiave k era libera e il record k=w è stato inserito. Chiamiamolo esito "OK".

  3. Il nodo q viene informato che la chiave k è già valorizzata con il valore v. Chiamiamolo esito "NOT_FREE".

  4. Il nodo q viene informato che la chiave k è libera ma la memoria nel database distribuito è esaurita. Chiamiamolo esito "OUT_OF_MEMORY"

Operazione di sola lettura

Il nodo q vuole leggere il valore del record per una chiave k nel database distribuito.

Ci sono 3 possibili esiti finali a questa richiesta:

  1. Il nodo q viene informato che al momento nella rete non c'è nessun partecipante al servizio. Chiamiamolo esito "NO_PARTICIPANTS".

  2. Il nodo q viene informato che il record per la chiave k è stato trovato e il suo valore è v. Chiamiamolo esito "OK".

  3. Il nodo q viene informato che la chiave k non è presente nel database. Chiamiamolo esito "NOT_FOUND".

E' importante che l'operazione di sola lettura sia distinta dalle operazioni che possono diventare un aggiornamento. Diciamo possono dal punto di vista di un nodo n’ che, contattato durante il cammino di ricerca dell'​hash_node per la chiave k e ricevuta la richiesta, si rifiuta di elaborarla in quanto non esaustivo; infatti l'esito finale della richiesta dipende dal nodo servente n che la elaborerà. Questo nodo n’, se la richiesta che riceve può essere identificata come di "sola lettura", non necessiterà di resettare il tempo per cui si deve considerare non esaustivo per k.

Operazione di modifica

Il nodo q vuole modificare il valore o rimuovere il record per una chiave k nel database distribuito.

Ci sono 3 possibili esiti finali a questa richiesta:

  1. Il nodo q viene informato che al momento nella rete non c'è nessun partecipante al servizio. Chiamiamolo esito "NO_PARTICIPANTS".

  2. Il nodo q viene informato che il record per la chiave k è stato modificato/rimosso. Chiamiamolo esito "OK".

  3. Il nodo q viene informato che la chiave k non è presente nel database. Chiamiamolo esito "NOT_FOUND".

Nodo servente esaustivo o non esaustivo

Un nodo n servente, che cioè partecipa attivamente al servizio, quando viene a conoscenza di una chiave k per la quale dovrebbe essere il detentore di un record (ad esempio perché riceve una richiesta di qualche tipo riferita alla chiave k), se il record per la chiave k non è presente nella sua memoria, si deve chiedere se può o non può asserire che il record per la chiave k non esiste nel database.

Ci riferiamo a questo quando diciamo che il nodo n è esaustivo o non esaustivo per la chiave k.

Se un nodo viene interrogato su una chiave k che non è nella sua memoria e per tale chiave si considera esaustivo, allora può asserire che il record per la chiave k non esiste nel database.

Se un nodo viene interrogato su una chiave k che non è nella sua memoria e per tale chiave si considera non esaustivo, allora non può asserire che il record non esiste.

Per gestire questo aspetto il nodo n fa uso di alcune strutture dati:

Il nodo n gestisce l'elenco not_exhaustive_keys in modo da memorizzare quando e per quanto tempo deve ritenersi non esaustivo per una data chiave k. Il nodo n è in grado di farlo perché le richieste per tale chiave giungono al nodo stesso e possono essere esaminate (ad esempio per vedere se sono di sola lettura o possono produrre un aggiornamento) prima che il nodo risponda rifiutando l'elaborazione. Se nessuna richiesta giunge per la chiave k entro il TTL dei record di questo servizio, allora il record se era presente nel database adesso non lo è più; per questo il singolo elemento della lista not_exhaustive_keys può essere rimosso dalla lista quando scade il suo timer.

Questo elenco non può crescere senza limite; però non si può permettere che il nodo si consideri esaustivo quando non lo è. Per questo, se l'elenco not_exhaustive_keys diventa troppo grande il nodo n mette il suo stato di default a non esaustivo per il tempo TTL dei record e può svuotare del tutto l'elenco not_exhaustive_keys. In seguito tornerà ad inserire singole chiavi nell'elenco not_exhaustive_keys mentre il timer dello stato di default andrà scorrendo.

Il nodo n gestisce anche l'elenco not_found_keys in modo da memorizzare quando deve ritenersi esaustivo per una data chiave k. Inserisce una chiave in questo elenco quando elabora una richiesta di rimozione per la chiave k oppure quando la procedura di recupero del record per la chiave k si conclude con un "NOT_FOUND". Questo elenco è una sorta di cache: se una chiave si trova in esso allora il nodo può rispondere senza costringere il client a ulteriori trasmissioni e attese, ma se non vi si trova e il nodo è non esaustivo per k allora rifiuterà l'elaborazione e il client giungerà comunque alla risposta corretta. Non si vuole far crescere la lista senza limite, ma allo stesso tempo si vuole tenere chiavi utili, quindi si mettono in testa alla lista gli elementi appena aggiunti e si rimuovono gli elementi in coda quando serve.

La gestione dell'elenco not_found_keys è indipendente dallo stato di default del nodo. Cioè, sebbene l'elenco abbia un effetto concreto solo quando lo stato di default è non esaustivo, comunque l'elenco non viene svuotato quando il nodo passa allo stato di default esaustivo.

Algoritmi

Nell'illustrare i seguenti algoritmi viene posta particolare attenzione alla gestione delle strutture dati not_exhaustive_keys, not_found_keys e timer_default_not_exhaustive.

Algoritmo di valutazione esaustivo per k:

bool is_exhaustive(k)

Algoritmo di valutazione out of memory:

bool is_out_of_memory()

Algoritmo di aggiunta k a not_exhaustive_keys:

void add_not_exhaustive(k)

Algoritmo di aggiunta k a not_found_keys:

void add_not_found(k)

Algoritmo di rimozione di k da not_exhaustive_keys:

void remove_not_exhaustive(k)

Algoritmo di rimozione di k da not_found_keys:

void remove_not_found(k)

Algoritmo alla ricezione della richiesta:

IPeersResponse on_request(IPeersRequest r, int common_lvl) throws PeersRefuseExecutionError, PeersRedoFromStartError

Algoritmo di avvio del recupero (in una nuova tasklet) del record per la chiave k:

void start_retrieve(k)