Modulo PeerServices - Dettagli Tecnici

Requisiti

L'utilizzatore del modulo PeerServices per prima cosa inizializza il modulo richiamando il metodo statico init di PeersManager. In tale metodo viene anche passata l'istanza di INtkdTasklet per fornire l'implementazione del sistema di tasklet.

Quando il nodo ha completato la sua fase di bootstrap come descritto nel modulo QSPN, il nodo istanzia il suo PeersManager passando al costruttore:

Deliverables

Quando viene costruito il PeersManager gli viene passato il livello del g-nodo che il nodo ha formato. Se non si tratta di un nodo che ha costituito una intera rete ma piuttosto di un nodo entrato in un g-nodo g esistente, allora il modulo dialoga con un suo vicino appartenente a g, per reperire le mappe dei partecipanti ai servizi opzionali. Lo stub per parlare con il vicino è ottenuto con il metodo i_peers_fellow di map_paths. Il metodo remoto usato è get_participant_set.


Il modulo segnala quando l'operazione di recupero delle mappe dei partecipanti ai servizi opzionali è completata con successo, attraverso il segnale participant_maps_ready di PeersManager.


Il modulo segnala quando l'operazione di recupero delle mappe dei partecipanti ai servizi opzionali fallisce, attraverso il segnale participant_maps_failure di PeersManager.


Il modulo fornisce su richiesta il livello del g-nodo che il nodo ha formato (informazione che gli è stata passata nel costruttore di PeersManager) e lo stato dell'operazione di recupero delle mappe dei partecipanti ai servizi opzionali, con le proprietà level_new_gnode, participant_maps_retrieved, participant_maps_failed di PeersManager.


Il modulo fornisce la classe base astratta PeerService. Se un nodo vuole fornire un servizio, deve implementare una classe derivata da PeerService. Una istanza di tale classe va registrata con il metodo register di PeersManager.

La classe che implementa il servizio può fare in modo che il nodo non venga contattato per elaborare richieste. Questo può essere utile (o anche rendersi necessario) per vari motivi a seconda dello specifico servizio. Questo la classe lo può fare ridefinendo il suo metodo is_ready() definito dalla classe base.


Il modulo aiuta le classi che implementano un servizio nel contattare i nodi da eleggere come replica, con i metodi begin_replica e next_replica di PeersManager.


Il modulo fornisce la classe base PeerClient per implementare uno stub per fare richieste ad un servizio.

Tale classe, usando il metodo contact_peer di PeersManager, permette di avviare il calcolo distribuito di Ht, ricevere le segnalazioni dei nodi nel tragitto e infine consegnare la richiesta e ricevere la risposta.

Instradamento dei messaggi

Il calcolo della funzione Ht deve essere realizzato con una implementazione distribuita perché nella topologia gerarchica di Netsukuku la conoscenza di ogni nodo è parziale. Con questa funzione viene individuato e contattato un nodo esistente nella rete e partecipante al servizio a partire da un indirizzo obiettivo (il risultato di hp ( k )) del quale non sappiamo se è detenuto da un nodo né se il nodo che eventualmente lo detiene partecipa al servizio.

Definiamo la funzione Ht(x̄) = x, dove x è l'indirizzo associato ad un nodo esistente (x ∈ dom(αt)) che minimizza la distanza x̄ - x, in modo più rigoroso minargx∈dom(αt)dist(x̄,x). La funzione dist rappresenta in modo intuitivo la distanza tra due indirizzi, ma è definita in modo che la funzione Ht cerchi il primo indirizzo valido incrementando l'identificativo fino a gsize-1 per poi ripartire da 0. Questo comportamento ci ritornerà utile in seguito. Precisamente la funzione dist(x̄,x) si calcola così:

Implementazione distribuita

Sia n un nodo che vuole inviare un messaggio m all'hash-node della chiave k per il servizio p. Il nodo n ha indirizzo n0·n1·...·nl-1. Ha inoltre una conoscenza parziale del dominio di αt, conoscenza che indichiamo con domnt), che comprende:

Il nodo n usa la funzione hp definita da p per calcolare dalla chiave k l'indirizzo formato da x̄0·x̄1·...·x̄l-1. A questo punto dovrebbe calcolare x=Ht(x̄). Procede così:

Il messaggio m’ viene così inoltrato:

Il messaggio m’ raggiunge un nodo dentro il gnodo che mirava a raggiungere.

Il messaggio m’ raggiunge la destinazione finale:

Gestione degli errori e della non partecipazione

Siano n ed m due nodi che conoscono una certa chiave k per un servizio p. Entrambi sono in grado di calcolare x = Ht ( hp ( k ) ) e di contattare x. Sia j_n il livello del g-nodo comune a n ed x: cioè x ∈ nj_n, x ∉ nj_n-1. Sia j_m il livello del g-nodo comune a m ed x: cioè x ∈ mj_m, x ∉ mj_m-1. Sia j il minore tra j_n e j_m. Ne consegue che x ∉ nj-1, x ∉ mj-1. Cioè l'interno del g-nodo xj-1 di livello j-1 a cui appartiene il nodo x è sconosciuto per n e per m. Supponiamo ora che per qualche motivo i messaggi instradati dal modulo PeerServices si perdano all'interno del g-nodo xε (con ε piccolo a piacere), ε < j-1. Oppure se il servizio p è opzionale può verificarsi che nessun nodo ora partecipa all'interno di xε ma che questa informazione non si era ancora divulgata al suo esterno.

Pur essendo questa anomalia circoscritta ad un g-nodo piccolo a piacere, questo impedirebbe ai nodi n ed m di scrivere e leggere dati con chiave k nel servizio p.

Dopo che n vede fallire il suo tentativo di contattare x per salvare un record con chiave k, n deve cercare di isolare il g-nodo malfunzionante. Se lo facesse basandosi solo sulle sue conoscenze potrebbe solo calcolare x’ = Ht ( hp ( k ), exclude_list=[xj_n-1] ). Indichiamo con questa dicitura che xj_n-1 viene considerato non valido come risultato. Ora il nodo m cerca di contattare x per leggere il record con chiave k, vede che il suo tentativo di contattare x fallisce e quindi prova ad isolare il g-nodo malfunzionante. Basandosi solo sulle sue conoscenze calcolerebbe x’’ = Ht ( hp ( k ), exclude_list=[xj_m-1] ). La conseguenza è che se j_m ≠ j_n allora n ed m contattano nodi diversi.

Questo significa che un meccanismo robusto deve prevedere che il nodo n che cerca di contattare il nodo x ∈ nj_n e non vi riesce deve rilevare tutti gli identificativi del g-nodo xε (dal livello ε al livello j_n-1) in cui il messaggio si è perso.

Modifiche agli algoritmi

Modifichiamo l'algoritmo di instradamento considerando che si deve comunicare al nodo originante del messaggio m’ quali g-nodi sono stati raggiunti correttamente e quali invece sono stati identificati come non affidabili o non usabili nella presente operazione.

Modifichiamo inoltre l'algoritmo di calcolo distribuito di Ht considerando che deve essere possibile escludere un set di determinati g-nodi.

Gli elementi di questo set sono g-nodi che appartengono ad un unico g-nodo g di livello w, ma ognuno di questi g-nodi h può avere un diverso livello ε, con ε < w. Menzioniamo il g-nodo g di livello w perché in alcuni casi il nodo che inizia la ricerca di un hash_node ha interesse a circoscrivere1 la sua ricerca all'interno di un suo g-nodo di livello w. Lo fa passando alla funzione Ht una tupla che ha un numero di w elementi. Come caso particolare possiamo avere w = l, cioè l'intera rete è il g-nodo contenente i singoli g-nodi di questo set.

Ogni elemento del suddetto set ha come dati il livello del g-nodo g contenitore (w) e inoltre la tupla di posizioni del g-nodo h, da ε a w - 1. Chiamiamo queste tuple "tuple globali nel g-nodo di ricerca".

Rendiamo cioè possibile l'esclusione di un g-nodo, di qualsiasi livello, anche se non visibile nella mappa del nodo richiedente, nel mezzo dell'esecuzione del calcolo distribuito di Ht.

Dal momento che rendiamo possibile l'esclusione in itinere anche di un singolo nodo, possiamo anche dare al nodo che riceve il messaggio (quindi dopo che lo ha ricevuto e letto) la possibilità di rifiutarsi di elaborarlo e far proseguire da qui il calcolo distribuito di Ht. Questo ci consentirà di gestire i casi in cui un nodo voglia demandare al successivo per "mancanza di memoria", come descritto nell'analisi funzionale.

Inoltre introduciamo la possibilità da parte del nodo che riceve il messaggio di rifiutarsi di elaborarlo e dare istruzione al chiamante di riavviare da capo il calcolo distribuito di Ht. Questo ci servirà nelle situazioni in cui, come vedremo in seguito, un servente sia costretto a fare lunghe operazioni di recupero dati e nel contempo si voglia garantire la coerenza degli stessi in un database distribuito.

Iniziamo.

Il nodo n vuole contattare l'hash-node per la chiave k per fare una richiesta r al servizio p. Il nodo deve avere una istanza della classe PeerClient del servizio p. Questa conosce il p_id, come calcolare = hp ( k ), il tempo massimo di attesa dell'esecuzione timeout_exec, come produrre la richiesta r di tipo IPeersRequest e come interpretare la risposta di tipo IPeersResponse. Sempre l'istanza della classe PeerClient, avvia il seguente algoritmo, che in seguito chiamiamo contact_peer, che ha come argomenti (p_id, x̄, r, timeout_exec, exclude_myself) e restituisce come risultato una istanza di IPeersResponse.

Durante l'istradamento del messaggio m’ il nodo v riceve il messaggio.


Note:

Mappa dei partecipanti ai servizi opzionali

Di default un nodo non partecipa ad un servizio opzionale. Si ricordi che un nodo non ha bisogno di partecipare ad un servizio opzionale per poter chiedere i servizi, ma solo se vuole fornirli.

Quando un nodo v vuole entrare in un servizio opzionale come partecipante deve comunicarlo ai suoi vicini perché l'informazione si propaghi per tutta la rete.

Quando v volesse uscire da un servizio non è necessario che lo comunichi subito. Grazie al meccanismo di fault tolerance introdotto dal modulo PeerServices, nel momento in cui una richiesta arrivi da un nodo n al nodo v, questo comunicherà a n che non partecipa al servizio.

Come regola generale, si consideri che divulgare l'informazione che un nodo o un g-nodo partecipano al servizio non è mai dannoso. Supponiamo che si sparge la voce che il g-nodo uj partecipa al servizio p. Ad un certo momento il nodo n, n ∉ uj, vuole salvare un record in p con chiave k e il messaggio viene inoltrato fino a uj che risulta il più vicino partecipante a hp ( k ).

Sia w il border nodo di uj che riveve il messaggio, w ∈ uj. Supponiamo che w ritiene che uj partecipa al servizio in quanto ritiene che uk (con j > k ≥ 0) che è nella sua mappa (vale a dire w ∈ uk+1, w ∉ uk) partecipa. Allora w inoltra il messaggio verso uk.

Sia il border nodo di uk che riceve il messaggio. Supponiamo che w̄ sa che uk non partecipa al servizio. Allora w̄ contatta il nodo n e gli dice, con la tupla dal livello k fino al livello comune con n, che uk non partecipa al servizio. A questo punto n può memorizzare questa conoscenza. Inoltre n ritenterà indicando nel messaggio che uk va escluso in quanto non partecipante. Quando il messaggio giunge a w, il border nodo di uj, questo scopre che uk non partecipa al servizio e da questo conclude che nemmeno uj, in quanto l'unico che pensava partecipante era uk. Allora w contatta il nodo n e gli dice, con la tupla dal livello j fino al livello comune con n, che uj non partecipa al servizio. A questo punto n può memorizzare questa conoscenza. Infine n ritenterà con le sue nuove conoscenze, quindi contatterà un nodo veramente partecipante e memorizzerà in esso il record.

Supponiamo ora che un nodo x vuole leggere il record con chiave k. Analogamente a quanto visto sopra, sebbene x possa inizialmente ritenere erroneamente che un g-nodo (ad esempio uj) partecipi al servizio, x cercherà di contattarlo e alla fine avrà aggiornate le sue conoscenze e contatterà il vero partecipante e leggerà il record. Quindi, nessun danno deriva dal supporre che un g-nodo partecipi.

Diversamente, supporre che un g-nodo non partecipa, mentre questo partecipa e qualcun altro lo sa, produce malfunzionamenti nel servizio. Sia uj partecipante al servizio p. Sia n un nodo che lo sa, mentre x lo ritiene non partecipante. Supponiamo che l'indirizzo più vicino a hp ( k ) sia proprio uj. Allora se n vuole salvare un record con chiave k in p lo salverà in uj, ma se x lo vorrà leggere non lo cercherà in uj, bensì altrove.

Il meccanismo più robusto potrebbe essere quindi quello di considerare sempre ogni nodo o g-nodo esistente nella rete come partecipante a tutti i servizi, fino a prova contraria data nel momento in cui si fa una richiesta. Anche in seguito, a richiesta soddisfatta, si dovrebbe subito supporre che i g-nodi esclusi potrebbero essere nuovamente entrati nel servizio.

Questo meccanismo sarebbe robusto ma introdurrebbe pesantezza e notevoli ritardi soprattutto in servizi in cui partecipano pochissimi nodi.

Descrizione del meccanismo individuato

Sia n un nodo appena entrato in un g-nodo g di livello l. Esso chiede ad un vicino che è anche membro del g-nodo g le mappe dei partecipanti ai servizi opzionali dal livello l in su, con il metodo remoto get_participant_set.

Il nodo n considera un generico g-nodo g, in assenza di comunicazioni a riguardo di g, non partecipante ad un servizio opzionale. Può venire a conoscenza della partecipazione solo quando riceve un messaggio del flood che viene illustrato sotto. A quel punto lo considera partecipante.

Per scoprire che un g-nodo è non partecipante ci saranno due possibilità.

Sia n un nodo che avvia per sé una richiesta r al servizio p. Il nodo n ritiene che Ht ( hp ( k ) ) sia il g-nodo g visibile nella sua mappa quindi invia il messaggio m’ verso g. Dopo qualche istante riceve la segnalazione che g non partecipa al servizio, quindi ora considera g non partecipante. Questa era la prima possibilità.

Supponiamo invece che un tentativo da parte di n di contattare un hash_node fallisce perché viene segnalato che un certo g-nodo h ritenuto partecipante in realtà non partecipa. Può essere h visibile nella mappa di n oppure non visibile. Comunque il nodo n aggiunge al messaggio m’ per i prossimi tentativi una informazione che consente di identificare il g-nodo h all'interno della rete (oppure all'interno del g-nodo nel quale la ricerca dell'hash_node era eventualmente circoscritta).

Ora il nodo n invia il messaggio m’ per un nuovo tentativo di raggiungere l'hash_node. Sia v un generico nodo che instrada questo messaggio. Il nodo v esaminando il dato di cui sopra nel messaggio, individua il g-nodo h che potrebbe essere visibile nella sua mappa. Supponiamo che tale g-nodo era considerato da v partecipante al servizio p. Ora v ha una indicazione diversa. Allora per averne conferma avvierà da lì a breve una finta richiesta verso un nodo a caso in h. Se riceve la segnalazione che h non partecipa da ora lo considera non partecipante. Questa era la seconda possibilità.

Modifiche agli algoritmi per la divulgazione della non partecipazione

Sia n un nodo che tenta di inviare un messaggio ad un certo hash_node in un servizio opzionale p. Supponiamo che n riceve l'informazione che un certo g-nodo g non partecipa al servizio. Può essere g visibile nella mappa di n oppure non visibile. In ogni caso il nodo n sta per inviare un nuovo messaggio alla ricerca del suo hash_node. Ne approfittiamo per aggiungere al messaggio l'informazione sulla non partecipazione di g, che può essere di interesse per i nodi che instradano il messaggio.

Il nodo n riceve questa informazione come tupla interna ad un g-nodo di livello j, con j < w. Salva questa tupla in una lista non_participant_tuple_list di tuple globali nel g-nodo di ricerca di livello w. Come per la lista exclude_tuple_list, anche questa è implementata in modo tale che quando vi si inserisce una tupla che rappresenta un g-nodo g vengono anche rimosse le tuple che rappresentano un g-nodo g’ ∈ g. Anche questa lista inoltre viene istanziata all'inizio del tentativo di instradare un messaggio e rimane in vita solo fino a tentativo esaurito.

Supponiamo ora che il nodo n avvii un messaggio verso il g-nodo xj all'interno di nj+1. Allora aggiunge a m’ la lista m’.non_participant_tuple_list di tuple globali mettendovi quelle tuple t ∈ non_participant_tuple_list che rappresentano un g-nodo g tale che sia visibile in alcuni nodi dentro nj+1.

Per i g-nodi g con livello l ≥ j questi devono avere il loro g-nodo superiore, al livello l+1, in comune con n. Per i g-nodi g con livello l < j questi devono avere il loro g-nodo di livello j+1 in comune con n.

Quando un nodo v riceve m’:

Divulgazione della partecipazione

Segue un tentativo di definire un algoritmo distribuito che possa fornire una notifica di partecipazione a tutta la rete con un buon livello di affidabilità, che sia leggero in termini di traffico.

Sia n un nodo che avvia la sua partecipazione ad un servizio p.

Sia v un nodo che riceve un messaggio di partecipazione ad un servizio p.

Mantenimento di un database distribuito

Se un obiettivo essenziale di un certo servizio è quello di mantenere un database distribuito "robusto" occorre che tale servizio tenga in considerazione alcuni aspetti che ora esamineremo. Con il termine "robusto" intendiamo un database che:

Come vedremo in seguito, il modulo fornisce dei metodi helper (inclusi nel modulo per evitare duplicazione di codice) che potranno essere usati dai vari servizi registrati che si occupano di mantenere un database distribuito.

Gli algoritmi con cui questi metodi affrontano le problematiche che intendono risolvere, dipendono da quali sono le caratteristiche dello specifico servizio. Vedremo in seguito che al momento sono state individuate due classi di servizi: i database temporali e i database a chiavi fisse.

Il modulo fornisce la classe DatabaseHandler e l'interfaccia IDatabaseDescriptor.

La classe DatabaseHandler è esposta dal modulo, ma il suo contenuto è del tutto oscuro all'esterno del modulo. I suoi membri, accessibili solo dal modulo, permettono di mantenere per uno specifico servizio le strutture dati necessarie al modulo per gestire le operazioni di mantenimento di questi due tipi di database.

L'interfaccia IDatabaseDescriptor espone dei metodi che il modulo utilizza nello svolgere le operazioni di cui stiamo parlando. Questi sono usati in entrambi questi due tipi di database. Poi l'interfaccia viene estesa da due interfacce, la ITemporalDatabaseDescriptor e la IFixedKeysDatabaseDescriptor, che espongono in aggiunta i metodi che il modulo utilizza nei rispettivi tipi di database.

Tali metodi dovranno essere implementati dalla classe di uno specifico servizio.

Un primo servizio dell'interfaccia IDatabaseDescriptor sarà quello di mantenere come proprietà dh l'istanza di DatabaseHandler associata al servizio. Questa proprietà verrà valorizzata dal modulo nel primo metodo del modulo che il servizio richiamerà con l'interfaccia a parametro (di norma il metodo *_on_startup).

Alcune strutture dati memorizzate nella classe DatabaseHandler sono:

Repliche

Le repliche dei dati aumentano l'affidabilità del database quanto alla persistenza dei dati.

Quando un nodo v riceve la richiesta di memorizzare (o aggiornare, o rinfrescare) un record con chiave k e valore val nella sua porzione del database distribuito del servizio p, il nodo v si occupa di replicare questo dato su un numero q di nodi replica. L'obiettivo è fare sì che se il nodo muore o si sconnette dalla rete, alla prossima richiesta di lettura del dato venga comunque data la risposta corretta. Quindi v deve scegliere i nodi che saranno contattati per la chiave k quando lui non parteciperà più.

Grazie all'introduzione del meccanismo di fault tolerance descritto sopra, scegliere e contattare tali nodi diventa un esercizio banale.

Modifiche agli algoritmi

Quando un nodo avvia l'algoritmo per contattare l'hash_node, gli può passare tra gli argomenti una lista di PeerTupleGNode da usare come exclude_tuple_list. Se non la passa allora l'algoritmo ne istanzia una nuova, come faceva prima. Se invece la passa si ottengono 2 risultati:

Inoltre l'algoritmo va modificato affinché, all'inizio, se nella lista di tuple da escludere vi sono g-nodi che sono visibili nella topologia del nodo corrente questi vengano inclusi anche in exclude_gnode_list come istanze HCoord.

Infine l'algoritmo va modificato affinché, a risposta ottenuta, la tupla globale nel g-nodo di ricerca che rappresenta il nodo che ha risposto venga restituita come argomento di out.

La lista completa degli argomenti di contact_peer diventa ora:

Una volta apportate queste modifiche, il nodo v che vuole contattare q nodi che saranno prossimi alla chiave k quando lui non sarà più partecipante procederà così:

Ingresso nella rete di un nodo partecipante

Un primo evento che introduce criticità riguardo la coerenza dei dati è l'ingresso nella rete di un nuovo nodo che partecipa al servizio.

Per ogni servizio p quando un nodo n entra nella rete (oppure quando inizia a partecipare al servizio) può venirgli assegnato un indirizzo prossimo a qualche chiave k che in precedenza era stata salvata nel database distribuito con il valore w. Ma il nodo n non ha informazioni sulla chiave k, né sul valore w.

Se un nodo q cercasse ora di accedere in lettura alla chiave k contatterebbe il nodo n. Questi non ha il record nella sua memoria, ma se rispondesse che il record non esiste questo sarebbe contrario al requisito di coerenza dei dati. Occorre quindi introdurre il concetto di esaustività del nodo rispetto a una chiave k.

Descriviamo qui gli aspetti fondamentali di questo concetto, ma diciamo subito che alla fine rimanderemo i dettagli ad altri documenti perché essi sono dipendenti dalle caratteristiche proprie di ogni singolo servizio.

Un nodo n servente, che cioè partecipa attivamente al servizio p, se riceve una richiesta di qualche tipo riferita alla chiave k, si deve chiedere se può o non può asserire di conoscere l'attuale valore del record per la chiave k, o di sapere che tale record 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 e per tale chiave si considera esaustivo, allora può asserire di conoscere l'attuale valore del record per la chiave k e può elaborare la richiesta.

Se un nodo viene interrogato su una chiave k e per tale chiave si considera non esaustivo, allora non può asserire di conoscere l'attuale valore del record per la chiave k e non può elaborare la richiesta. Dovrà fare in modo, con diverse strategie a seconda del tipo di servizio come vedremo sotto, di sopperire a questa mancanza oppure di indirizzare il client a contattare un altro nodo servente.

Procedimento di recupero di un record

Un nodo servente che si considera non esaustivo per una chiave k, può decidere di avviare un procedimento di recupero del record. Se e quando lo fa, dipende dallo specifico servizio. Ad esempio un certo servizio potrebbe avere un numero esiguo di chiavi e allora si potrebbe stabilire di ricercare subito i record per tutte le chiavi. Un altro servizio potrebbe avere molte possibili chiavi e allora si potrebbe stabilire di ricercare una chiave solo dopo che qualcuno ne ha fatto richiesta.

Se il nodo decide di avviare il procedimento di recupero di un record, questo introduce una seconda criticità riguardo la coerenza dei dati.

Quando abbiamo spiegato cosa si intende per coerenza dei dati abbiamo fatto un esempio. Riprendiamolo e vediamo quale problema può sorgere con un database distribuito:

Vediamo anche un altro scenario problematico:

Per rimediare a questi possibili scenari si modificano le operazioni che sono state sottolineate nei precedenti esempi. Quando n1 vuole recuperare il record per la chiave k quale suo nuovo detentore, procede così:

Vediamo nei due esempi precedenti come questo comportamento risolve il problema.

Esempio 1:

Esempio 2:

Affinché un servente n1 sia in grado di effettuare il recupero di un record per la chiave k dal servente n0 tramite gli algortmi forniti dal modulo, ci sono alcune regole che la classe del servizio deve rispettare:

Il modulo usa la classe RequestWaitThenSendRecord. Si tratta di una classe serializzabile, che deriva da Object, e contiene una istanza di Object serializzabile k. Implementa l'interfaccia (vuota) IPeersRequest. È la richiesta di aspettare un tempo δ (valutato direttamente dal nodo che riceve la richiesta) e poi inviare il record relativo alla chiave k. Tale classe non viene esposta dal modulo.

Siccome la richiesta implicitamente dice al nodo servente di attendere un tempo che lui stesso dovrà valutare, il nodo client non può fare altro che accettare una attesa che può arrivare al massimo valore che si può ottenere dalla valutazione del tempo critico di coerenza. Questo valore non dipende dal servizio specifico. Viene quindi memorizzato in un membro statico timeout_exec della classe RequestWaitThenSendRecord.

Il modulo usa la classe RequestWaitThenSendRecordResponse. Si tratta di una classe serializzabile, che deriva da Object, e contiene una istanza di Object serializzabile record. Implementa l'interfaccia (vuota) IPeersResponse. È la risposta alla richiesta sopra descritta. Tale classe non viene esposta dal modulo.

Il modulo usa la classe RequestWaitThenSendRecordNotFound. Si tratta di una classe serializzabile, che deriva da Object, e non ha alcun dato al suo interno. Implementa l'interfaccia (vuota) IPeersResponse. È la risposta come eccezione "NOT_FOUND" alla richiesta sopra descritta. Tale classe non viene esposta dal modulo.

L'interfaccia IDatabaseDescriptor espone i metodi sopra descritti: is_valid_key, evaluate_hash_node, key_equal_data, key_hash_data, is_valid_record, my_records_contains, get_record_for_key e set_record_for_key.

Per gestire il tempo in cui il nodo n1 attende la risposta da parte del nodo n0 relativa al recupero di un record, in altre parole per ricordarsi di quali chiavi è stato avviato un procedimento di recupero ancora in corso, il nodo n1 fa uso di alcune strutture dati memorizzate nella classe DatabaseHandler:

Requisiti comuni

Le classi dei servizi che implementano un database distribuito devono tenere precisi comportamenti quando ricevono una richiesta. Possono farlo usando alcuni algoritmi forniti dal modulo, diversi a seconda del tipo di servizio.

Questi algoritmi pongono alcune regole che la classe del servizio deve rispettare. Esaminiamo qui alcuni requisiti che sono comuni ai diversi tipi di servizio che in seguito analizzeremo separatamente.

L'interfaccia IDatabaseDescriptor espone anche i metodi sopra descritti: get_key_from_request, get_timeout_exec, is_insert_request, is_read_only_request, is_update_request, prepare_response_not_found, prepare_response_not_free e execute.

Requisiti specifici

Abbiamo identificato alcune tipologie di servizi (al momento due) che usano distinti approcci all'uso del concetto di esaustività; per ogni classe rimanderemo ad un documento di dettaglio.

Database con record che hanno un TTL

Consideriamo un servizio che vuole mantenere un database che abbia queste caratteristiche:

Per facilitare l'implementazione di un servizio con queste caratteristiche, il modulo fornisce alcuni algoritmi. Questi algoritmi usano i requisiti descritti sopra e altri descritti nell'interfaccia ITemporalDatabaseDescriptor.

Esaminiamo nel dettaglio questi algoritmi nel documento Database TTL.

Database con un numero esiguo e fisso di chiavi

Consideriamo un servizio che vuole mantenere un database che abbia queste caratteristiche:

Per facilitare l'implementazione di un servizio con queste caratteristiche, il modulo fornisce alcuni algoritmi. Questi algoritmi usano i requisiti descritti sopra e altri descritti nell'interfaccia IFixedKeysDatabaseDescriptor.

Esaminiamo nel dettaglio questi algoritmi nel documento Database a chiavi fisse.

Algoritmi

Gli algoritmi in dettaglio sono illutrati nei seguenti documenti:

Netsukuku/ita/docs/ModuloPeers/DettagliTecnici (last edited 2015-11-24 08:14:33 by lukisi)