Maneiras de implementar a versão de dados no MongoDB

298

Você pode compartilhar seus pensamentos como implementaria a versão de dados no MongoDB. (Eu fiz uma pergunta semelhante a respeito de Cassandra . Se você tem alguma idéia de qual db é melhor para isso, compartilhe)

Suponha que eu precise atualizar os registros em um catálogo de endereços simples. (Os registros do catálogo de endereços são armazenados como objetos json simples). Espero que a história:

  • será usado com pouca frequência
  • será usado de uma só vez para apresentá-lo de forma "máquina do tempo"
  • não haverá mais versões do que algumas centenas para um único registro. a história não vai expirar.

Estou considerando as seguintes abordagens:

  • Crie uma nova coleção de objetos para armazenar o histórico de registros ou alterações nos registros. Ele armazenaria um objeto por versão com uma referência à entrada do catálogo de endereços. Esses registros teriam a seguinte aparência:

    {
     '_id': 'novo ID',
     'usuário': user_id,
     'timestamp': timestamp,
     'address_book_id': 'identificação do registro do catálogo de endereços' 
     'old_record': {'first_name': 'Jon', 'last_name': 'Doe' ...}
    }
    

    Essa abordagem pode ser modificada para armazenar uma matriz de versões por documento. Mas essa parece ser uma abordagem mais lenta, sem vantagens.

  • Armazene versões como objeto serializado (JSON) anexado às entradas do catálogo de endereços. Não sei como anexar esses objetos aos documentos do MongoDB. Talvez como uma série de strings. ( Modelado após o Simple Document Versioning com CouchDB )

Piotr Czapla
fonte
1
Quero saber se isso mudou desde que a pergunta foi respondida? Eu não sei muito sobre o oplog, mas isso acontecia na época, faria alguma diferença?
Randy G
Minha abordagem é pensar em todos os dados como uma série temporal.

Respostas:

152

A primeira grande questão ao abordar isso é "como você deseja armazenar conjuntos de alterações" ?

  1. Difícil?
  2. Cópias inteiras?

Minha abordagem pessoal seria armazenar diferenças. Como a exibição dessas diferenças é realmente uma ação especial, eu as colocaria em uma coleção diferente de "histórico".

Eu usaria a coleção diferente para economizar espaço de memória. Você geralmente não deseja um histórico completo para uma consulta simples. Portanto, mantendo o histórico fora do objeto, você também pode mantê-lo fora da memória geralmente acessada quando esses dados são consultados.

Para facilitar minha vida, eu faria um documento histórico conter um dicionário de diferenças de data e hora. Algo assim:

{
    _id : "id of address book record",
    changes : { 
                1234567 : { "city" : "Omaha", "state" : "Nebraska" },
                1234568 : { "city" : "Kansas City", "state" : "Missouri" }
               }
}

Para facilitar minha vida, eu faria essa parte dos meus DataObjects (EntityWrapper, qualquer que seja) que eu uso para acessar meus dados. Geralmente esses objetos têm alguma forma de histórico, para que você possa substituir facilmente o save()método para fazer essa alteração ao mesmo tempo.

ATUALIZAÇÃO: 2015-10

Parece que agora há uma especificação para lidar com diferenças JSON . Essa parece ser uma maneira mais robusta de armazenar as diferenças / alterações.

Gates VP
fonte
2
Você não se preocuparia que esse documento do Histórico (o objeto de alterações) cresça com o tempo e as atualizações se tornem ineficientes? Ou o MongoDB lida com o crescimento de documentos com facilidade?
Piotr Czapla
5
Dê uma olhada na edição. Adicionar a changesé realmente fácil: db.hist.update({_id: ID}, {$set { changes.12345 : CHANGES } }, true)isso fará uma subida que alterará apenas os dados necessários. O Mongo cria documentos com "espaço no buffer" para lidar com esse tipo de alteração. Ele também observa como os documentos em uma coleção são alterados e modificam o tamanho do buffer de cada coleção. Portanto, o MongoDB foi projetado para exatamente esse tipo de alteração (adicione nova propriedade / push à matriz).
Gates VP
2
Eu fiz alguns testes e, de fato, a reserva de espaço funciona muito bem. Não consegui detectar a perda de desempenho quando os registros foram realocados para o final do arquivo de dados.
Piotr Czapla 27/11/2010
4
Você pode usar o github.com/mirek/node-rus-diff para gerar diferenças (compatíveis com o MongoDB) para o seu histórico.
Mirek Rusin
1
O JSON Patch RFC fornece uma maneira de expressar diferenças. Possui implementações em vários idiomas .
Jérôme
31

Existe um esquema de controle de versão chamado "Vermongo", que aborda alguns aspectos que não foram tratados nas outras respostas.

Um desses problemas é atualizações simultâneas, outro está excluindo documentos.

O Vermongo armazena cópias completas de documentos em uma coleção de sombras. Para alguns casos de uso, isso pode causar muita sobrecarga, mas acho que também simplifica muitas coisas.

https://github.com/thiloplanz/v7files/wiki/Vermongo

Marian
fonte
5
Como você realmente o usa?
hadees
6
Não há documentação sobre como esse projeto é realmente usado. É algo que mora no Mongo de alguma forma? É uma biblioteca Java? É apenas uma maneira de pensar sobre o problema? Nenhuma idéia e nenhuma dica é dada.
Ftrotter
1
Na verdade, este é um aplicativo java e o código relevante
ftrotter
20

Aqui está outra solução usando um único documento para a versão atual e todas as versões antigas:

{
    _id: ObjectId("..."),
    data: [
        { vid: 1, content: "foo" },
        { vid: 2, content: "bar" }
    ]
}

datacontém todas as versões. A datamatriz é ordenada , novas versões serão editadas apenas $pushno final da matriz. data.vidé o ID da versão, que é um número incremental.

Obtenha a versão mais recente:

find(
    { "_id":ObjectId("...") },
    { "data":{ $slice:-1 } }
)

Obtenha uma versão específica vid:

find(
    { "_id":ObjectId("...") },
    { "data":{ $elemMatch:{ "vid":1 } } }
)

Retorne apenas os campos especificados:

find(
    { "_id":ObjectId("...") },
    { "data":{ $elemMatch:{ "vid":1 } }, "data.content":1 }
)

Inserir nova versão: (e impedir inserção / atualização simultânea)

update(
    {
        "_id":ObjectId("..."),
        $and:[
            { "data.vid":{ $not:{ $gt:2 } } },
            { "data.vid":2 }
        ]
    },
    { $push:{ "data":{ "vid":3, "content":"baz" } } }
)

2é a vidversão mais recente atual e 3a nova versão está sendo inserida. Como você precisa das versões mais recentes vid, é fácil obter as próximas versões vid:nextVID = oldVID + 1 .

A $andcondição garantirá, que 2é o mais recentevid .

Dessa forma, não há necessidade de um índice exclusivo, mas a lógica do aplicativo precisa cuidar do incremento da vidinserção on.

Remova uma versão específica:

update(
    { "_id":ObjectId("...") },
    { $pull:{ "data":{ "vid":2 } } }
)

É isso aí!

(lembre-se dos 16 MB por limite de documento)

Benjamin M
fonte
Com o armazenamento mmapv1, sempre que uma nova versão é adicionada aos dados, existe a possibilidade de o documento ser movido.
raok1997
Sim está certo. Mas se você apenas adicionar novas versões de vez em quando, isso deve ser negligenciado.
Benjamin M
9

Eu trabalhei com essa solução que acomoda versões publicadas, de rascunho e históricas dos dados:

{
  published: {},
  draft: {},
  history: {
    "1" : {
      metadata: <value>,
      document: {}
    },
    ...
  }
}

Eu explico mais o modelo aqui: http://software.danielwatrous.com/representing-revision-data-in-mongodb/

Para aqueles que podem implementar algo parecido com isto em Java , aqui está um exemplo:

http://software.danielwatrous.com/using-java-to-work-with-versioned-data/

Incluindo todo o código que você pode bifurcar, se quiser

https://github.com/dwatrous/mongodb-revision-objects

Daniel Watrous
fonte
Awesome stuff :)
Jonathan
4

Se você estiver usando o mongoose, achei o seguinte plugin uma implementação útil do formato JSON Patch

mongoose-patch-history

bmw15
fonte
4

Outra opção é usar o plugin mongoose-history .

let mongoose = require('mongoose');
let mongooseHistory = require('mongoose-history');
let Schema = mongoose.Schema;

let MySchema = Post = new Schema({
    title: String,
    status: Boolean
});

MySchema.plugin(mongooseHistory);
// The plugin will automatically create a new collection with the schema name + "_history".
// In this case, collection with name "my_schema_history" will be created.
Muhammad Reda
fonte
1

Eu usei o pacote abaixo para um projeto meteoro / MongoDB e funciona bem, a principal vantagem é que ele armazena histórico / revisões em uma matriz no mesmo documento, portanto, não há necessidade de publicações ou middleware adicionais para acessar o histórico de alterações . Ele pode suportar um número limitado de versões anteriores (por exemplo, últimas dez versões), também suporta concatenação de alterações (todas as alterações ocorridas dentro de um período específico serão cobertas por uma revisão).

nicklozon / revisão de coleções de meteoros

Outra opção de som é usar o Meteor Vermongo ( aqui )

helcode
fonte