MongoDB/ NoSQL: Mantener el Historial de Cambios del Documento


Un requisito bastante común en las aplicaciones de bases de datos es rastrear los cambios en una o más entidades específicas en una base de datos. He oído esto llamado versionado de filas, una tabla de registro o una tabla de historial (estoy seguro de que hay otros nombres para ello). Hay varias formas de abordarlo en un RDBMS: puede escribir todos los cambios de todas las tablas de origen en una sola tabla (más de un registro) o tener una tabla de historial separada para cada tabla de origen. También tiene la opción de administrar el inicio de sesión código de aplicación o a través de disparadores de base de datos.

Estoy tratando de pensar cómo se vería una solución al mismo problema en una base de datos NoSQL/document (específicamente MongoDB), y cómo se resolvería de manera uniforme. ¿Sería tan simple como crear números de versión para documentos, y nunca sobrescribirlos? ¿Crear colecciones separadas para documentos" reales "frente a documentos" registrados"? ¿Cómo afectaría esto la consulta y el rendimiento?

De todos modos, es este un escenario común con NoSQL bases de datos, y si es así, ¿hay una solución común?

Author: Community, 2010-08-18

4 answers

Buena pregunta, yo también estaba investigando esto.

Crear una nueva versión en cada cambio

Me encontré con el módulo de versionado del controlador Mongoid para Ruby. Yo no lo he usado, pero de lo que pude encontrar, agrega un número de versión a cada documento. Las versiones anteriores están incrustadas en el propio documento. El principal inconveniente es que el documento completo se duplica en cada cambio, lo que dará lugar a una gran cantidad de contenido duplicado se almacena cuando se trata de documentos grandes. Sin embargo, este enfoque está bien cuando se trata de documentos de pequeño tamaño y/o no actualiza documentos muy a menudo.

Solo almacena los cambios en una nueva versión

Otro enfoque sería almacenar solo los campos modificados en una nueva versión. Luego puede 'aplanar' su historial para reconstruir cualquier versión del documento. Sin embargo, esto es bastante complejo, ya que necesita realizar un seguimiento de los cambios en su modelo y almacenar actualizaciones y elimina de manera que su aplicación pueda reconstruir el documento actualizado. Esto puede ser complicado, ya que se trata de documentos estructurados en lugar de tablas SQL planas.

Almacenar los cambios dentro del documento

Cada campo también puede tener un historial individual. Reconstruir documentos a una versión dada es mucho más fácil de esta manera. En su aplicación no tiene que realizar un seguimiento explícito de los cambios, sino simplemente crear una nueva versión de la propiedad cuando cambie su valor. Documento podría verse algo como esto:

{
  _id: "4c6b9456f61f000000007ba6"
  title: [
    { version: 1, value: "Hello world" },
    { version: 6, value: "Foo" }
  ],
  body: [
    { version: 1, value: "Is this thing on?" },
    { version: 2, value: "What should I write?" },
    { version: 6, value: "This is the new body" }
  ],
  tags: [
    { version: 1, value: [ "test", "trivial" ] },
    { version: 6, value: [ "foo", "test" ] }
  ],
  comments: [
    {
      author: "joe", // Unversioned field
      body: [
        { version: 3, value: "Something cool" }
      ]
    },
    {
      author: "xxx",
      body: [
        { version: 4, value: "Spam" },
        { version: 5, deleted: true }
      ]
    },
    {
      author: "jim",
      body: [
        { version: 7, value: "Not bad" },
        { version: 8, value: "Not bad at all" }
      ]
    }
  ]
}

Marcar parte del documento como eliminada en una versión todavía es algo incómodo. Puede introducir un campo state para las partes que se pueden eliminar / restaurar de su aplicación:

{
  author: "xxx",
  body: [
    { version: 4, value: "Spam" }
  ],
  state: [
    { version: 4, deleted: false },
    { version: 5, deleted: true }
  ]
}

Con cada uno de estos enfoques puede almacenar una versión actualizada y aplanada en una colección y los datos del historial en una colección separada. Esto debería mejorar los tiempos de consulta si solo está interesado en la última versión de un documento. Pero cuando necesite tanto la versión más reciente como los datos históricos, deberá realizar dos consultas, en lugar de una. Por lo tanto, la elección de usar una sola colección frente a dos colecciones separadas debe depender de la frecuencia con la que su aplicación necesita las versiones históricas.

La mayor parte de esta respuesta es solo un volcado cerebral de mis pensamientos, en realidad no he probado nada de esto todavía. Mirando hacia atrás, la primera opción es probablemente la solución más fácil y mejor, a menos que la sobrecarga de datos duplicados es muy importante para su aplicación. La segunda opción es bastante compleja y probablemente no vale la pena el esfuerzo. La tercera opción es básicamente una optimización de la opción dos y debería ser más fácil de implementar, pero probablemente no valga la pena el esfuerzo de implementación a menos que realmente no pueda ir con la opción uno.

Esperamos recibir comentarios sobre esto y las soluciones de otras personas al problema:)

 82
Author: Niels van der Rest,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-25 10:00:06

Hemos implementado parcialmente esto en nuestro sitio y utilizamos el 'Almacenar revisiones en un documento separado" (y una base de datos separada). Escribimos una función personalizada para devolver las diferencias y la almacenamos. No es tan difícil y puede permitir la recuperación automatizada.

 7
Author: Amala,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-19 12:20:02

Uno puede tener una base de datos NoSQL actual y una base de datos NoSQL histórica. Habrá una noche ETL corrió todos los días. Este ETL registrará cada valor con una marca de tiempo, por lo que en lugar de valores siempre serán tuplas (campos versionados). Solo registrará un nuevo valor si se realizó un cambio en el valor actual, ahorrando espacio en el proceso. Por ejemplo, este archivo json de la base de datos NoSQL histórica puede verse así:

{
  _id: "4c6b9456f61f000000007ba6"
  title: [
    { date: 20160101, value: "Hello world" },
    { date: 20160202, value: "Foo" }
  ],
  body: [
    { date: 20160101, value: "Is this thing on?" },
    { date: 20160102, value: "What should I write?" },
    { date: 20160202, value: "This is the new body" }
  ],
  tags: [
    { date: 20160101, value: [ "test", "trivial" ] },
    { date: 20160102, value: [ "foo", "test" ] }
  ],
  comments: [
    {
      author: "joe", // Unversioned field
      body: [
        { date: 20160301, value: "Something cool" }
      ]
    },
    {
      author: "xxx",
      body: [
        { date: 20160101, value: "Spam" },
        { date: 20160102, deleted: true }
      ]
    },
    {
      author: "jim",
      body: [
        { date: 20160101, value: "Not bad" },
        { date: 20160102, value: "Not bad at all" }
      ]
    }
  ]
}
 2
Author: Paul Kar.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-08 23:26:29

¿Por qué no una variación en Almacenar cambios dentro del documento?

En lugar de almacenar versiones contra cada par de claves, los pares de claves actuales en el documento siempre representan el estado más reciente y un 'registro' de cambios se almacena dentro de una matriz de historial. Solo aquellas claves que han cambiado desde su creación tendrán una entrada en el registro.

{
  _id: "4c6b9456f61f000000007ba6"
  title: "Bar",
  body: "Is this thing on?",
  tags: [ "test", "trivial" ],
  comments: [
    { key: 1, author: "joe", body: "Something cool" },
    { key: 2, author: "xxx", body: "Spam", deleted: true },
    { key: 3, author: "jim", body: "Not bad at all" }
  ],
  history: [
    { 
      who: "joe",
      when: 20160101,
      what: { title: "Foo", body: "What should I write?" }
    },
    { 
      who: "jim",
      when: 20160105,
      what: { tags: ["test", "test2"], comments: { key: 3, body: "Not baaad at all" }
    }
  ]
}
 2
Author: Paul Taylor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-20 19:14:43