Boa estrutura de dados instantâneos para um índice na memória

12

Estou projetando um banco de dados de objetos na memória para um caso de uso muito específico. É um escritor único, mas deve suportar leituras simultâneas eficientes. As leituras devem ser isoladas. Não há linguagem de consulta, o banco de dados suporta apenas:

  • obter objetos / s por atributo / conjunto de atributos (pode haver suporte para expressões, por exemplo x.count < 5)
  • obter atributo do objeto

Uma consulta é um script imperativo composto por um número arbitrário das operações acima. O tamanho dos dados será << memória; portanto, todos os objetos e índices na maioria dos atributos devem caber confortavelmente sem troca.

O que eu preciso é de uma estrutura de dados para o índice de atributo do objeto, que pode ser O (n) em gravações, não suporta simultaneidade de gravação, mas deve suportar idealmente O (1) instantâneos (talvez cópia na gravação) e acesso O (logN). Idealmente, isso permitiria alta simultaneidade nas leituras com o compartilhamento estrutural máximo entre as versões.

Eu estava olhando para CTries , BSTs simultâneos e árvores de exibição simultâneas, mas não tenho certeza se estou realmente olhando na direção certa aqui. As estruturas acima prestam muita atenção à complexidade das inserções com as quais não me importo.

A pergunta : existe uma estrutura de dados conhecida que seja adequada para o meu caso de uso imediatamente?

EDIT : depois de pensar um pouco mais, parece que uma árvore BST / Splay persistente funcionaria. O gravador atualizaria a cópia 'mestre' e as consultas obteriam a árvore a partir do início da execução e a jogariam fora depois que terminassem. No entanto, ainda estou interessado se houver uma solução melhor.

dm3
fonte
1
Você precisa de instantâneos na memória ou precisa salvá-los em disco / rede? Uma estrutura de dados puramente funcional fornece automaticamente instantâneos na memória; portanto, se é disso que você precisa, é sua melhor aposta.
Gilles 'SO- stop be evil'
Está tudo na memória. Eu estava pensando que talvez exista uma versão mutável eficiente com um instantâneo de tempo constante (como o CTrie, apenas sem gravações simultâneas).
Dm3
2
Seu problema pode ser menos a escolha da estrutura de dados, mas o tipo de controle de simultaneidade.
Raphael
Pode muito bem ser, você poderia falar um pouco mais sobre isso?
Dm3

Respostas:

5

Use qualquer tipo de estrutura de dados baseada em árvore persistente / imutável (ou seja, funcional). A chave está acertando o bloqueio, como @Raphael apontou nos comentários.

O bom das estruturas de dados funcionais / persistentes baseadas em árvore é que você obtém "instantâneos" gratuitamente. Vamos supor que você use um treap (árvore de pesquisa binária aleatória) para sua estrutura de dados. Aqui está um exemplo de um escrito em Go: https://github.com/steveyen/gtreap . O autor descreve assim:

Por imutável, quaisquer atualizações / exclusões em um treap retornarão um novo treap que pode compartilhar nós internos com o treap anterior. Todos os nós nesta implementação são somente leitura após sua criação. Isso permite que leitores simultâneos operem com segurança com gravadores simultâneos, pois as modificações criam novas estruturas de dados e nunca modificam as estruturas de dados existentes. Essa é uma abordagem simples para obter o controle de simultaneidade MVCC ou multi-versão.

O(registron)

Você usa uma trava para proteger o ponteiro para a raiz. Como a estrutura de dados é imutável, as leituras podem ser feitas simultaneamente e você pode salvar os ponteiros em instantâneos antigos. Uma leitura é:

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

Mesmo que a pesquisa demore um pouco, você só mantém o bloqueio pressionado enquanto copia o ponteiro, para que as pesquisas possam ocorrer simultaneamente.

Uma gravação é:

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

Nesta versão, as gravações precisam manter o bloqueio durante todo o processo de criação da nova versão da árvore. Você pode melhorar o desempenho da leitura (às vezes, com a transação de gravação falhando) alterando a gravação para algo como isto:

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

Você poderá se sair um pouco melhor (torná-lo "travado") se sua linguagem de programação tiver variáveis ​​atômicas com uma operação de comparação e troca atômica. (Por exemplo, usando C ++ 11. atomic<T*>)

Lógica Errante
fonte
Obrigado pela resposta elaborada. Eu meio que sabia disso, talvez não tivesse colocado isso claramente o suficiente na pergunta em si. No entanto, a resposta ainda é ótima!
Dm3
Sua versão "aprimorada" depende do modelo de memória do sistema em uso. Pode ser que seja necessário declarar voláteis em alguns sistemas e precisa de muita habilidade para obter a codificação correta.
Ian Ringrose
1

A Microsoft publicou detalhes sobre o novo banco de dados em memória, e possui índices que não bloqueiam as leituras enquanto as gravações estão sendo feitas.

Por exemplo:

Justin Levandoski, David Lomet e Sudipta Sengupta, The Bw-Tree: A B-tree for New Hardware, em 2013 IEEE 29a Conferência Internacional de Engenharia de Dados (ICDE), Conferência Internacional de Engenharia de Dados, 8 de abril de 2013.

Consulte http://research.microsoft.com/en-us/projects/main-memory_dbs/ para obter uma lista de suas publicações.

Ian Ringrose
fonte