Usar o repositório git como infra-estrutura de bases de dados

Estou a fazer um projecto que trata de uma base de dados de documentos estruturada. Eu tenho uma árvore de categorias (~1000 categorias, até ~50 categorias em cada nível), cada categoria contém vários milhares (até, digamos, ~10000) de documentos estruturados. Cada documento é vários kilobytes de dados em alguma forma estruturada (eu preferiria YAML, mas pode muito bem ser JSON ou XML).

Os utilizadores deste sistema fazem vários tipos de operações:

    A recuperação destes documentos por ID
  • à procura de documentos por alguns dos atributos estruturados dentro deles
  • editar documentos (ou seja, Adicionar/Remover/mudar o nome/junção); cada operação de edição deve ser registada como uma transacção com algum comentário
  • ver um histórico de alterações gravadas para um determinado documento (incluindo a visualização de quem, quando e porquê alterou o documento, obtendo a versão anterior-e provavelmente revertendo para esta se solicitado)
É claro que a solução tradicional seria: usando algum tipo de base de dados de documentos (como CouchDB ou Mongo) para este problema-no entanto, esta coisa de controle de versões (histórico) me tentou a uma idéia Selvagem - por que eu não deveria usar git repositório como uma infra-estrutura de banco de dados para esta aplicação?

À primeira vista, pode ser resolvido assim:
  • Categoria = pasta, documento = ficheiro
  • obter o documento por ID = > alterar pastas + ler um ficheiro numa cópia de trabalho
  • editar documentos com editar comentários = > fazer commits por vários utilizadores + guardar mensagens de commit
  • história = > registo git normal e recuperação de transacções mais antigas
  • Essa é uma parte um pouco mais complicada, acho que exigiria a exportação periódica de uma categoria para uma base de dados relacional com indexação de colunas que permitiremos procurar por
Existem outras armadilhas comuns nesta solução? Alguém já tentou implementar tal infra - estrutura (ou seja, para qualquer frameworks populares-RoR, node.js, Django, CakePHP)? Será que esta solução tem quaisquer possíveis implicações sobre o desempenho ou a fiabilidade - ou seja, está provado que o git seria muito mais lento do que as soluções tradicionais de base de dados ou que haveria quaisquer dificuldades de escalabilidade/fiabilidade? Presumo que um conjunto de tais servidores que empurram/puxam o repositório uns dos outros deve ser bastante robusto e confiável.

{[[2]} basicamente, diga-me Se esta solução vai funcionar e porque vai ou não vai funcionar?

Author: GreyCat, 2013-11-22

5 answers

Responder à minha pergunta não é a melhor coisa a fazer, mas, como deixei cair a ideia, gostaria de partilhar a lógica que funcionou no meu caso. Gostaria de enfatizar que esta lógica pode não se aplicar a todos os casos, então cabe ao arquiteto decidir.

Geralmente, o primeiro ponto principal que a minha pergunta falha é que estou a lidar com sistema multi-utilizador que funciona em paralelo, simultaneamente, usando o meu servidor com um cliente fino (ou seja, apenas um navegador web). Por aqui, eu ... tem de manter o estado para todos eles. Existem várias abordagens para esta, mas todas elas são muito difíceis em recursos ou muito complexas para implementar (e assim Tipo de matar o propósito original de descarregar todo o material de implementação difícil para o git em primeiro lugar):

  • Abordagem "contundente": 1 Utilizador = 1 Estado = 1 cópia de trabalho completa de um repositório que o servidor mantém para o utilizador. Mesmo se estamos falando de um banco de dados de documentos bastante pequeno (por exemplo, 100s MiBs) com cerca de 100K de usuários, mantendo o clone do repositório completo para todos eles faz uso do disco executado através do telhado (ou seja, 100K de usuários vezes 100MiB ~ 10 TiB). O que é ainda pior, Clonagem 100 MIB repositório cada vez leva vários segundos de tempo, mesmo se feito em maneer bastante eficaz (ou seja, não usando por git e unpacking-repacking coisas), que é inaceitável, IMO. E ainda pior-cada edição que aplicamos a uma árvore principal deve ser puxada para o repositório de cada usuário, que é (1) recurso hog, (2) pode levar a conflitos de edição não resolvidos em caso geral.

    Basicamente, pode ser tão ruim quanto O(número de edições × dados × número de usuários) em termos de Uso do disco, e tal uso do disco automaticamente significa muito alto uso do CPU.

  • Abordagem "apenas utilizadores activos": manter a cópia de trabalho apenas para utilizadores activos. Desta forma, você geralmente não armazena um clone completo por usuário, mas:

    • à medida que o utilizador entra, você clona o repositório. Leva vários segundos e ~100 MiB de espaço de disco por utilizador activo.
    • Como o usuário continua a trabalhar no site, ele trabalha com a cópia de trabalho dada.
  • à medida que o utilizador sai, o seu clone do repositório é copiado de volta para o repositório principal como um ramo, armazenando assim apenas as suas "alterações não aplicadas", se existirem, o que é bastante eficiente em termos de espaço.

Assim, a utilização do disco neste caso atinge o pico de O (número de edições × dados × número de utilizadores activos), que é geralmente ~100..1000 vezes menos do que o número total de utilizadores, mas isso torna o login/out mais complicado e mais lento, pois envolve a clonagem de um ramo por usuário em cada login e puxando essas mudanças de volta no logout ou expiração da sessão (que deve ser feito transacionalmente => adiciona outra camada de complexidade). Em números absolutos, diminui 10 tíbias de Uso do disco para 10..100 GiBs no meu caso, isso pode ser aceitável, mas, mais uma vez, estamos agora a falar de uma base de dados bastante pequena de 100 MiBs.

  • "caixa esparsa" abordagem: fazer "checkout esparso" em vez de um clone completo do repo por usuário ativo não ajuda muito. Ele pode salvar ~10x do uso do espaço do disco, mas à custa de uma carga muito maior de CPU/disco no histórico-envolvendo operações, que tipo de mata o propósito.

  • "abordagem" Workers pool": em vez de fazer clones full-blown todas as vezes para pessoas ativas, podemos manter um pool de clones "trabalhadores", pronto para ser usado. Assim, cada vez que um usuário entra, ele ocupa um "trabalhador", puxando lá seu branch do repo principal, e, como ele faz login para fora, ele liberta o "trabalhador", que faz git inteligente difícil reset para se tornar mais uma vez apenas um clone do repo principal, pronto para ser usado por outro usuário que faz login em. Não ajuda muito com o uso do disco (ainda é um clone completo muito alto por usuário ativo), mas pelo menos faz o login entrar/sair mais rápido, como despesa de ainda mais complexidade.

  • Dito isto, note que calculei intencionalmente números de uma base de dados bastante pequena e base de utilizadores: 100K. users, 1K active users, 100 MiBs total database + history of edits, 10 MiBs of working copy. Se você olhar para projetos mais proeminentes de coletividade, há um número muito maior lá:
    │              │ Users │ Active users │ DB+edits │ DB only │
    ├──────────────┼───────┼──────────────┼──────────┼─────────┤
    │ MusicBrainz  │  1.2M │     1K/week  │   30 GiB │  20 GiB │
    │ en.wikipedia │ 21.5M │   133K/month │    3 TiB │  44 GiB │
    │ OSM          │  1.7M │    21K/month │  726 GiB │ 480 GiB │
    
    Obviamente, para essas quantidades de dados/actividade, esta abordagem seria totalmente inaceitável.

    Geralmente, teria funcionado, se se pudesse usar o navegador web como um cliente "grosso", ou seja, emitir operações git e armazenar praticamente o check-out completo do lado do cliente, não no servidor lado.

    Há também outros pontos que perdi, mas não são assim tão maus em comparação com o primeiro:
    • o próprio padrão de ter estado de edição" espesso " do utilizador é controverso em termos de ORMs normais, tais como ActiveRecord, Hibernate, DataMapper, Tower, etc.
    • Por Mais que eu tenha procurado, não há nenhuma base de código livre existente para fazer essa abordagem ao git de frameworks populares. Há pelo menos um serviço que consegue fazer isso. eficientemente-isso é obviamente github - mas, infelizmente, a sua base de código é de código fechado e eu suspeito fortemente que eles não usam as técnicas normais de armazenamento git / repo dentro, ou seja, eles basicamente implementaram Git de "big data" alternativa.

    Então, conclusão: é possível, mas para a maioria das usecases actuais não estará nem perto da solução ideal. A gravar a sua própria implementação document-edit-history-to-SQL ou a tentar usar qualquer implementação existente a base de dados de documentos seria provavelmente uma alternativa melhor.

     45
    Author: GreyCat, 2014-06-12 04:43:07
    Uma abordagem interessante. Eu diria que se você precisa armazenar dados, use uma base de dados, não um repositório de código fonte, que é projetado para uma tarefa muito específica. Se você puder usar o Git fora-da-caixa, então está tudo bem, mas você provavelmente precisa construir uma camada de repositório de documentos sobre ele. Então você pode construir sobre uma base de dados tradicional também, certo? E se é o controle de versão embutido no qual você está interessado, por que não usar apenas um repositório de documentos de código aberto ferramentas? Há muito por onde escolher.

    Bem, se você decidir ir para a infra-estrutura Git de qualquer maneira, então basicamente funcionaria para os seus requisitos se você implementá-lo como descrito. Mas:

    Mencionaste "conjunto de servidores que se empurram/puxam uns aos outros" - pensei nisso durante algum tempo e ainda não tenho a certeza. Você não pode empurrar/puxar vários repos como uma operação atômica. Pergunto-me se poderá haver alguma confusão de fusão durante o trabalho paralelo.

    2) Talvez tu não preciso dele, mas uma funcionalidade óbvia de um repositório de documentos que você não listou é o controle de acesso. Você poderia possivelmente restringir o acesso a alguns caminhos(=categorias) através de submódulos, mas provavelmente você não será capaz de conceder acesso a nível de documentos facilmente.

     13
    Author: Kombajn zbożowy, 2013-11-23 22:43:46
    Os meus 2 pence. Um pouco saudade, mas ... ..... Tive uma exigência semelhante num dos meus projectos de incubação. Semelhante ao seu, meus requisitos chave onde uma base de dados de documentos (xml no meu caso),com versão de documentos. Foi para um sistema multi-usuário com um monte de casos de uso de colaboração. Minha preferência era usar soluções opensource disponíveis que suportam a maioria dos Requisitos-chave. Para ir directo ao assunto, não encontrei nenhum produto que desde ambos, de uma forma que era escalável o suficiente (número de usuários, volumes de Uso, Armazenamento e recursos computáveis).Eu estava inclinado para o git para toda a capacidade promissora ,e (provável) soluções que se poderia criar fora dele. Como eu brincava com a opção git mais, passar de uma única perspectiva de usuário para uma perspectiva de usuário multi ( milli) tornou-se um desafio óbvio. Infelizmente, não pude fazer uma análise de desempenho substancial como você fez. ( .. preguiçoso / demite-te cedo ....para versão 2, mantra) poder para você!. De qualquer forma, minha idéia tendenciosa desde então se transformou para a próxima alternativa (ainda tendenciosa): uma malha de ferramentas que são as melhores em suas esferas separadas, bases de dados e controle de versão.

    Ainda a trabalhar ( ...e ligeiramente negligenciada ) a versão morfada é simplesmente isto .

    • na interface: (userfacing ) use uma base de dados para o primeiro nível armazenamento (interface com as aplicações do utilizador)
    • na infraestrutura, use um sistema de controle de versão(VCS) (como git ) para executar versão dos objectos de dados na base de dados

    Em essência, isso equivaleria a adicionar um plugin de controle de versão para o banco de dados, com alguma cola de integração, que você pode ter que desenvolver, mas pode ser muito mais fácil.

    Como (supostamente ) funcionaria é que as principais trocas de dados de interface multi-usuário são através da base de dados. O DBMS vai lidar com todas as questões divertidas e complexas tais como multi-usuário , concorrência e, operações atômicas, etc. Na infra-estrutura, o VCS realizaria controle de versão em um único conjunto de objetos de dados ( sem concorrência, ou problemas multi-usuário). Para cada transação efetiva na base de dados, o controle de versão é realizado apenas nos registros de dados que teriam efetivamente mudado. Quanto à cola de interface, será sob a forma de uma simples função de interfuncionamento entre a base de dados e o VCS. Em termos de design, como abordagem simples seria uma interface movida por eventos, com atualizações de dados da base de dados acionando os procedimentos de controle de versão (dica: assumindo Mysql, o uso de gatilhos e sys_exec () blah blah...). Em termos de complexidade de implementação, ele vai variar desde o simples e eficaz ( eg scripting ) para o complexo e maravilhoso ( alguma interface de conector programada) . Tudo depende de quão louco você quer ir com ele, e quanto suado capital você estão dispostos a gastar. Acho que um simples guião deve fazer a magia. E para acessar o resultado final, as várias versões de dados, uma alternativa simples é preencher um clone do banco de dados ( mais um clone da estrutura do banco de dados) com os dados referenciados pela tag/id/hash versão no VCS. mais uma vez este bit será uma simples consulta/traduzir/mapear trabalho de uma interface. Ainda há alguns desafios e incógnitas a serem enfrentados, mas suponho que o impacto, e ... a relevância da maioria deles dependerá em grande parte dos Requisitos de sua aplicação e casos de uso. Alguns podem acabar por não ter problemas. Algumas das questões incluem a correspondência de desempenho entre os 2 módulos chave, o banco de dados e o VCS, para uma aplicação com atividade de atualização de dados de alta frequência , escala de recursos (poder de armazenamento e processamento ) ao longo do Tempo no lado do git como os dados, e os usuários crescem: constante, exponencial ou eventualmente plateau [1] cocktail acima, eis o que estou a preparar.
    • usar o Git para os VCS (inicialmente considerado o bom e velho CVS para o devido à utilização de apenas changesets ou deltas entre 2 versões) {[[9]}
    • usar o mysql (devido à natureza altamente estruturada dos meus dados, xml com esquemas xml rigorosos)
    • brincar com o MongoDB (para tentar uma base de dados NoSQl, que corresponde De Perto à estrutura de base de dados nativa usada no git)
    Alguns factos divertidos - git. realmente faz coisas claras para otimizar o armazenamento, tais como compressão, e armazenamento de apenas deltas entre a revisão de objetos - Sim, o git armazena apenas changesets ou deltas entre revisões de objetos de dados, onde é aplicável ( ele sabe quando e como) . Referência: packfiles, profundamente enraizado nas entranhas de Git internals - Review of the git's object storage( content-addressable filesystem), shows stricking similarities (from the concept perspective) with noSQL bases de dados como o mongoDB. Mais uma vez, à custa do capital do suor, pode proporcionar possibilidades mais interessantes para a integração do 2, e ajustes de desempenho {[[2]}

    Se chegou até aqui, deixe-me se o acima pode ser aplicável ao seu caso, e assumindo que seria , como iria ajustar-se a alguns dos aspectos na sua última análise de desempenho abrangente

     12
    Author: young chisango, 2014-06-30 18:16:33
    Como mencionou, o caso multi-utilizador é um pouco mais complicado de lidar. Uma solução possível seria usar arquivos de índice git específicos ao usuário, resultando em
    • não há necessidade de cópias de trabalho separadas (a utilização do disco está limitada a ficheiros alterados)
    • não é necessário um trabalho preparatório moroso (por sessão de Utilizador)
    O truque é combinar o Git.GIT_INDEX_FILE variável ambiental com as ferramentas para criar git commits manualmente:

    Segue-se um contorno da solução (os traços SHA1 reais são omitidos dos comandos):

    # Initialize the index
    # N.B. Use the commit hash since refs might changed during the session.
    $ GIT_INDEX_FILE=user_index_file git reset --hard <starting_commit_hash>
    
    #
    # Change data and save it to `changed_file`
    #
    
    # Save changed data to the Git object database. Returns a SHA1 hash to the blob.
    $ cat changed_file | git hash-object -t blob -w --stdin
    da39a3ee5e6b4b0d3255bfef95601890afd80709
    
    # Add the changed file (using the object hash) to the user-specific index
    # N.B. When adding new files, --add is required
    $ GIT_INDEX_FILE=user_index_file git update-index --cacheinfo 100644 <changed_data_hash> path/to/the/changed_file
    
    # Write the index to the object db. Returns a SHA1 hash to the tree object
    $ GIT_INDEX_FILE=user_index_file git write-tree
    8ea32f8432d9d4fa9f9b2b602ec7ee6c90aa2d53
    
    # Create a commit from the tree. Returns a SHA1 hash to the commit object
    # N.B. Parent commit should the same commit as in the first phase.
    $ echo "User X updated their data" | git commit-tree <new_tree_hash> -p <starting_commit_hash>
    3f8c225835e64314f5da40e6a568ff894886b952
    
    # Create a ref to the new commit
    git update-ref refs/heads/users/user_x_change_y <new_commit_hash>
    

    Dependendo dos seus dados, você poderia usar uma tarefa cron para fundir os novos refs a {[[2]} mas a resolução de conflitos é, sem dúvida, a parte mais difícil aqui.

    As ideias para facilitar são bem-vindas.
     2
    Author: 7mp, 2016-10-20 14:47:05