Buona struttura dati snapshot per un indice in memoria


12

Sto progettando un database di oggetti in memoria per un caso d'uso molto specifico. È un singolo autore, ma deve supportare letture simultanee efficienti. Le letture devono essere isolate. Non esiste un linguaggio di query, il database supporta solo:

  • ottenere oggetto / i per attributo / set di attributi (potrebbe esserci supporto per le espressioni, ad es. x.count < 5)
  • ottenere l'attributo dell'oggetto

Una query è uno script imperativo composto da un numero arbitrario delle operazioni precedenti. La dimensione dei dati sarà << memoria, quindi tutti gli oggetti e gli indici sulla maggior parte degli attributi dovrebbero adattarsi comodamente senza scambiare.

Ciò di cui ho bisogno è una struttura di dati per l'indice degli attributi dell'oggetto, che può essere O (n) nelle scritture, non supportare la concorrenza di scrittura, ma dovrebbe supportare idealmente le istantanee O (1) (forse copia su scrittura) e l'accesso O (logN). Idealmente, consentirebbe un'elevata concorrenza sulle letture con la massima condivisione strutturale tra le versioni.

Stavo guardando CTries , Concurrent BSTs e Concurrent Splay Tree ma non sono sicuro di guardare davvero nella giusta direzione qui. Le strutture di cui sopra prestano molta attenzione alla complessità degli inserti di cui non mi interessa.

La domanda : esiste una struttura di dati nota adatta per il mio caso d'uso pronta all'uso?

EDIT : dopo aver pensato un po 'di più sembra che un albero BST / Splay persistente funzionerebbe. Lo scrittore aggiornava la copia "principale" e le query avrebbero ottenuto l'albero dall'inizio dell'esecuzione e l'avrebbero buttato via dopo averlo fatto. Tuttavia, sono ancora interessato se esiste una soluzione migliore.


1
Hai bisogno di istantanee in memoria o devi salvarle su disco / rete? Una struttura di dati puramente funzionale ti offre automaticamente istantanee in memoria, quindi se è quello che ti serve, è la soluzione migliore.
Gilles 'SO- smetti di essere malvagio' il

È tutto in memoria. Mi chiedevo forse che esistesse una versione mutabile efficiente con uno snapshot a tempo costante (come CTrie, solo senza scritture simultanee).
dm3,

2
Il problema potrebbe essere meno la scelta della struttura dei dati, ma il tipo di controllo della concorrenza.
Raffaello

Potrebbe anche essere, potresti approfondire un po 'di più?
dm3,

Risposte:


5

Utilizzare qualsiasi tipo di struttura di dati basata su albero persistente / immutabile (ovvero funzionale). La chiave sta ottenendo il blocco giusto, come ha sottolineato @Raphael nei commenti.

La cosa bella delle strutture di dati basate su alberi funzionali / persistenti è che ottieni "istantanee" gratuitamente. Supponiamo che tu usi un treap (albero di ricerca binario randomizzato) per la tua struttura di dati. Ecco un esempio di uno scritto in Go: https://github.com/steveyen/gtreap . L'autore lo descrive così:

Di immutabile, qualsiasi aggiornamento / eliminazione di un treap restituirà un nuovo treap che può condividere nodi interni con il treap precedente. Tutti i nodi di questa implementazione sono di sola lettura dopo la loro creazione. Ciò consente ai lettori simultanei di operare in sicurezza con gli autori simultanei poiché le modifiche creano solo nuove strutture di dati e non modificano mai le strutture di dati esistenti. Questo è un approccio semplice per ottenere MVCC o controllo di concorrenza multi-versione.

O(logn)

Si utilizza un lucchetto per proteggere il puntatore alla radice. Poiché la struttura dei dati è immutabile, le letture possono essere eseguite contemporaneamente e puoi salvare i puntatori su vecchie istantanee. Una lettura è:

lock
tmp = ptr_to_root
unlock
value = search(tmp, <value to search for>)
return value

Anche se la ricerca può richiedere del tempo, tieni premuto il lucchetto solo durante la copia del puntatore, quindi le ricerche possono avvenire contemporaneamente.

Una scrittura è:

lock
old_ptr_to_root = ptr_to_root
ptr_to_root = insert(old_ptr_to_root, <new key/value pair>)
unlock

In questa versione, le scritture devono contenere il blocco durante l'intero processo di creazione della nuova versione dell'albero. È possibile migliorare le prestazioni di lettura (al costo del fallimento della transazione di scrittura) modificando la scrittura in qualcosa del genere:

top:
  lock
  old_ptr_to_root = ptr_to_root
  unlock
  new_ptr_to_root = insert(old_ptr_to_root, <new key/value pair>)
  lock
  if (ptr_to_root == old_ptr_to_root)   # make sure no other write happened in the interim
    ptr_to_root = new_ptr_to_root
    unlock
  else                                  # transaction fails, try again
    unlock
    goto top

Potresti essere in grado di fare anche leggermente meglio (rendilo "lock free") se il tuo linguaggio di programmazione ha variabili atomiche con un'operazione atomica di confronto e scambio. (Ad esempio utilizzando C ++ 11 atomic<T*>).


Grazie per la risposta elaborata. In un certo senso lo sapevo, forse non l'ho messo abbastanza chiaramente nella domanda stessa. Tuttavia, la risposta è ancora eccezionale!
dm3

La versione "migliorata" dipende dal modello di memoria del sistema in uso. Potrebbe aver bisogno di dati verificabili per essere dichiarati volatili su alcuni sistemi e avere bisogno di grandi abilità per ottenere la codifica corretta.
Ian Ringrose,

1

Microsoft ha pubblicato i dettagli sul loro nuovo database di memoria, ha indici che non bloccano le letture durante l'esecuzione delle scritture.

Per esempio:

Justin Levandoski, David Lomet e Sudipta Sengupta, The Bw-Tree: A B-tree for New Hardware, 2013 IEEE 29th International Conference on Data Engineering (ICDE), International Conference on Data Engineering, 8 aprile 2013.

Vedere http://research.microsoft.com/en-us/projects/main-memory_dbs/ per un elenco delle loro pubblicazioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.