Modulo PeerServices - Database con un numero esiguo e fisso di chiavi

Implementiamo un servizio che mantiene un database distribuito. Le chiavi sono in un dominio ben definito e non molto grande.

Ogni nodo partecipante può essere chiamato a mantenere in memoria un record per ognuna delle chiavi nel dominio. Inoltre ha, per ogni chiave, un record prefissato da usare come record di default quando elabora una richiesta di lettura e nessuno aveva in precedenza salvato un valore.

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à letto il documento Dettagli Tecnici.

Effetto di visibilità locale del dato

Opzionalmente, il servizio può stabilire di dare ad alcune chiavi un effetto di visibilità locale del dato. Con questo intendiamo che per una chiave kK si può identificare un livello l. Se un nodo n fa richiesta di memorizzare il valore v per la chiave k, tale valore sarà visibile ai soli nodi appartenenti allo stesso g-nodo di livello l a cui appartiene il nodo n. In altri g-nodi il valore associato alla chiave k sarà distinto.

Per ottenere questo, la classe del servizio dovrà semplicemente implementare il metodo evaluate_hash_node della sua istanza di IDatabaseDescriptor in modo da restituire una tupla di soli l elementi. La stessa implementazione ovviamente dovrà essere usata dalla classe del client del servizio nel suo metodo perfect_tuple.

Operazione di sola lettura

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

Ci sono 2 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 ha il valore v. Chiamiamolo esito "OK".

Operazione di scrittura

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

Ci sono 2 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. Chiamiamolo esito "OK".

Esaustività del nodo servente

Sia K l'insieme completo delle chiavi del servizio p; il nodo n appena entrato nella rete avvia immediatamente un procedimento di recupero dei record associati a tutte le possibili chiavi kK, con attesa del tempo critico di coerenza δ.

Il procedimento di recupero consiste nell'inviare la richiesta al nodo che in precedenza era detentore del record per la chiave k, chiamiamolo current(k).

Come detto, tale procedimento prevede che se il nodo n durante questo tempo riceve richieste per la chiave k che sono di scrittura, allora non le rifiuta subito, ma le mette in attesa. Siccome ora le richieste di scrittura per la chiave k passano prima per il nodo n che le fa attendere, di sicuro non giungeranno al current(k).

E' plausibile che queste richieste avvengano in modo circoscritto al g-nodo di appartenenza di n di un certo livello l, se il servizio come detto prima decide di dare ad alcune chiavi un effetto di visibilità locale del dato.

E' plausibile che la richiesta per la chiave k restituisca una eccezione PeersNoParticipantsInNetworkError. In questo caso va memorizzato il record di default come record associato alla chiave k nella memoria del nodo.

Quando il nodo n riceve una richiesta r per una chiave k, il suo comportamento dipende da due fattori:

Se la richiesta è di sola lettura e il recupero non è ancora completato, allora la rifiuta immediatamente come non esaustivo, di modo che sarà servita dal precedente detentore.

Se la richiesta non è di sola lettura e il recupero non è ancora completato, allora la mette in attesa finché non ha completato o, al massimo, fino quasi ad esaurire il tempo previsto per l'esecuzione della richiesta (timeout_exec) e poi da istruzioni al client di riavviare da capo il calcolo distribuito di Ht, rilanciando una eccezione PeersRedoFromStartError. Questo perché comunque potrebbe non essere più lui il miglior candidato.

Se il recupero del record è stato completato, qualunque sia il tipo della richiesta, la serve immediatamente.

Per gestire questo aspetto il nodo n fa uso di alcune strutture dati memorizzate nella classe DatabaseHandler:

Algoritmi

La classe del servizio deve fornire, oltre alle operazioni previste nei requisiti comuni, queste ulteriori operazioni:

Il modulo fornisce l'interfaccia IFixedKeysDatabaseDescriptor. Essa estende l'interfaccia IDatabaseDescriptor ed espone anche i metodi sopra descritti: get_full_key_domain e get_default_record_for_key.

La classe che implementa il servizio nel suo costruttore crea una istanza di IFixedKeysDatabaseDescriptor che userà in tutte le chiamate a questi algoritmi, che sono metodi di PeersManager. Subito richiamerà il metodo fixed_keys_db_on_startup. In seguito per ogni richiesta che riceve richiamerà il metodo fixed_keys_db_on_request.

Algoritmo all'avvio:

void fixed_keys_db_on_startup(IFixedKeysDatabaseDescriptor fkdd, int p_id)

Algoritmo alla ricezione della richiesta:

IPeersResponse fixed_keys_db_on_request(IFixedKeysDatabaseDescriptor fkdd, IPeersRequest r, int common_lvl) throws PeersRefuseExecutionError, PeersRedoFromStartError

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

internal void fixed_keys_db_start_retrieve(IFixedKeysDatabaseDescriptor fkdd, Object k)