Formas de implementar versiones de datos en MongoDB


298

¿Puede compartir sus pensamientos sobre cómo implementaría el control de versiones de datos en MongoDB? (He hecho una pregunta similar con respecto a Cassandra . Si tiene alguna idea sobre qué base de datos es mejor para eso, por favor comparta)

Supongamos que necesito versionar registros en una libreta de direcciones simple. (Los registros de la libreta de direcciones se almacenan como objetos planos json). Espero que la historia:

  • se usará con poca frecuencia
  • se utilizará de una vez para presentarlo en forma de "máquina del tiempo"
  • no habrá más versiones que unos pocos cientos para un solo registro. La historia no caducará.

Estoy considerando los siguientes enfoques:

  • Cree una nueva colección de objetos para almacenar el historial de registros o los cambios en los registros. Almacenaría un objeto por versión con una referencia a la entrada de la libreta de direcciones. Dichos registros se verían de la siguiente manera:

    {
     '_id': 'nueva identificación',
     'user': user_id,
     'marca de tiempo': marca de tiempo,
     'address_book_id': 'id del registro de la libreta de direcciones' 
     'old_record': {'first_name': 'Jon', 'last_name': 'Doe' ...}
    }
    

    Este enfoque se puede modificar para almacenar una variedad de versiones por documento. Pero este parece ser un enfoque más lento sin ninguna ventaja.

  • Almacene versiones como objeto serializado (JSON) adjunto a las entradas de la libreta de direcciones. No estoy seguro de cómo adjuntar dichos objetos a los documentos de MongoDB. Quizás como un conjunto de cuerdas. ( Modelado a partir de versiones de documentos simples con CouchDB )


1
¿Quiero saber si esto ha cambiado desde que se respondió la pregunta? No sé mucho sobre oplog, pero ¿existía esto en ese momento? ¿Haría alguna diferencia?
Randy L

Mi enfoque es pensar en todos los datos como una serie de tiempo.

Respuestas:


152

La primera gran pregunta al sumergirse en esto es "¿cómo desea almacenar los conjuntos de cambios" ?

  1. Diferencias?
  2. ¿Copias de registros completos?

Mi enfoque personal sería almacenar diffs. Debido a que la visualización de estas diferencias es realmente una acción especial, pondría las diferencias en una colección diferente de "historia".

Usaría la colección diferente para ahorrar espacio en la memoria. Por lo general, no desea un historial completo para una consulta simple. Entonces, al mantener el historial fuera del objeto, también puede mantenerlo fuera de la memoria de acceso común cuando se consultan esos datos.

Para hacerme la vida más fácil, haría que un documento de historia contuviera un diccionario de diferencias marcadas en el tiempo. Algo como esto:

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

Para hacer mi vida realmente fácil, haría esta parte de mis DataObjects (EntityWrapper, lo que sea) que utilizo para acceder a mis datos. En general, estos objetos tienen algún tipo de historial, por lo que puede anular fácilmente el save()método para realizar este cambio al mismo tiempo.

ACTUALIZACIÓN: 2015-10

Parece que ahora hay una especificación para manejar diferencias JSON . Esto parece una forma más robusta de almacenar los diferenciales / cambios.


2
¿No le preocupa que dicho documento de Historial (el objeto de cambios) crezca con el tiempo y las actualizaciones se vuelvan ineficientes? ¿O MongoDB maneja documentos crecen fácilmente?
Piotr Czapla

55
Echa un vistazo a la edición. Agregar changeses realmente fácil: db.hist.update({_id: ID}, {$set { changes.12345 : CHANGES } }, true)esto realizará una actualización que solo cambiará los datos requeridos. Mongo crea documentos con "espacio de búfer" para manejar este tipo de cambio. También observa cómo cambian los documentos de una colección y modifica el tamaño del búfer para cada colección. Por lo tanto, MongoDB está diseñado para exactamente este tipo de cambio (agregar nueva propiedad / inserción a la matriz).
Gates VP

2
He hecho algunas pruebas y, de hecho, la reserva de espacio funciona bastante bien. No pude detectar la pérdida de rendimiento cuando los registros se reasignaron al final del archivo de datos.
Piotr Czapla

44
Puede usar github.com/mirek/node-rus-diff para generar diferencias (compatibles con MongoDB) para su historial.
Mirek Rusin

1
El JSON Patch RFC proporciona una forma de expresar difffs. Tiene implementaciones en varios idiomas .
Jérôme

31

Hay un esquema de versiones llamado "Vermongo" que aborda algunos aspectos que no se han tratado en las otras respuestas.

Uno de estos problemas son las actualizaciones simultáneas, otro es eliminar documentos.

Vermongo almacena copias completas de documentos en una colección oculta. Para algunos casos de uso, esto puede causar demasiada sobrecarga, pero creo que también simplifica muchas cosas.

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


55
¿Cómo lo usas realmente?
Hadiz

66
No hay documentación sobre cómo se usa realmente este proyecto. ¿Es algo que vive con Mongo de alguna manera? Es una biblioteca de Java? ¿Es simplemente una forma de pensar sobre el problema? No se dan ideas ni se dan pistas.
ftrotter

1
Esta es en realidad una aplicación de Java y el código correspondiente vive aquí: github.com/thiloplanz/v7files/blob/master/src/main/java/v7db/…
ftrotter

20

Aquí hay otra solución que usa un solo documento para la versión actual y todas las versiones anteriores:

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

dataContiene todas las versiones. La datamatriz está ordenada , las nuevas versiones solo se $pusheditarán hasta el final de la matriz. data.vides el id de la versión, que es un número incremental.

Obtenga la versión más reciente:

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

Obtenga una versión específica por vid:

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

Devuelve solo los campos especificados:

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

Insertar nueva versión: (y evitar la inserción / actualización concurrente)

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

2es la vidversión más reciente actual y 3es la nueva versión que se está insertando. Debido a que necesita las versiones más recientes vid, es fácil obtener las siguientes versiones vid:nextVID = oldVID + 1 .

La $andcondición asegurará, eso 2es lo últimovid .

De esta forma no hay necesidad de un índice único, pero la lógica de la aplicación debe ocuparse de incrementar la vidinserción en.

Eliminar una versión específica:

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

¡Eso es!

(recuerde el límite de 16 MB por documento)


Con el almacenamiento mmapv1, cada vez que se agrega una nueva versión a los datos, existe la posibilidad de que se mueva el documento.
raok1997

Si, eso es correcto. Pero si solo agrega nuevas versiones de vez en cuando, esto debería ser descuidado.
Benjamin M


9

Trabajé a través de esta solución que acomoda una versión publicada, borrador e histórica de los datos:

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

Explico el modelo más aquí: http://software.danielwatrous.com/representing-revision-data-in-mongodb/

Para aquellos que pueden implementar algo como esto en Java , aquí hay un ejemplo:

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

Incluyendo todo el código que puede bifurcar, si lo desea

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


Cosas increíbles :)
Jonathan


4

Otra opción es usar el complemento 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.

1

He utilizado el siguiente paquete para un proyecto meteor / MongoDB, y funciona bien, la principal ventaja es que almacena el historial / revisiones dentro de una matriz en el mismo documento, por lo tanto, no es necesario tener publicaciones adicionales o middleware para acceder al historial de cambios . Puede admitir un número limitado de versiones anteriores (por ejemplo, las últimas diez versiones), también admite la concatenación de cambios (por lo que todos los cambios ocurridos dentro de un período específico estarán cubiertos por una revisión).

nicklozon / meteor-collection-revisions

Otra opción de sonido es usar Meteor Vermongo ( aquí )

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.