MongoDB / NoSQL:保持文档更改历史

数据库应用程序中一个相当常见的需求是跟踪对数据库中一个或多个特定实体的更改。 我听说过所谓的行版本控制,日志表或历史表(我敢肯定还有其他的名字)。 在RDBMS中有很多方法可以处理它 – 您可以将所有源表中的所有更改写入单个表(更多日志),或者为每个源表创build单独的历史logging表。 您还可以selectpipe理应用程序代码中的日志logging或通过数据库触发器进行pipe理。

我想通过对NoSQL /文档数据库(特别是MongoDB)中相同问题的解决scheme进行思考,以及如何以统一的方式解决这个问题。 它会像为文档创build版本号一样简单,而且不会覆盖它们? 为“真实”与“已logging”文档创build单独的集合? 这将如何影响查询和性能?

无论如何,这是NoSQL数据库的一个常见的情况,如果是这样,是否有一个共同的解决scheme?

好问题,我自己也在研究这个。

每次更改都创build一个新版本

我遇到了Ruby的Mongoid驱动程序的Versioning模块 。 我自己并没有使用它,但从我能find的 ,它增加了每个文件的版本号。 较早的版本embedded在文档本身中。 主要缺点是每次更改都会复制整个文档 ,这会导致在处理大型文档时会存储大量重复的内容。 这种方法很好,但是当你处理小文档和/或不经常更新文档时。

只能将更改存储在新版本中

另一种方法是只将更改的字段存储在新版本中 。 然后,您可以“拼合”您的历史logging,以重新构build文档的任何版本。 但这很复杂,因为您需要跟踪模型中的更改,并以应用程序可以重新构build最新文档的方式存储更新和删除。 这可能是棘手的,因为你正在处理结构化文档而不是平坦的SQL表。

将更改存储在文档中

每个领域也可以有个人的历史。 以这种方式将文档重build为给定的版本要容易得多。 在您的应用程序中,您不必显式地跟踪更改,只需在更改其值时创build新版本的属性。 一个文件可能看起来像这样:

{ _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" } ] } ] } 

将文档的部分标记为在版本中删除仍然有些尴尬。 您可以为您的应用程序中可以删除/恢复的部分引入一个state字段:

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

通过这些方法,您可以在一个集合中存储最新的扁平版本,并将历史数据存储在单独的集合中。 如果您只对最新版本的文档感兴趣,这应该会改善查询时间。 但是当你需要最新版本和历史数据时,你需要执行两个查询,而不是一个。 因此,使用单个集合与两个单独集合的select应取决于应用程序需要多久的历史版本

这个答案大部分只是我的想法的大脑转储,我还没有实际尝试过任何。 回顾一下,第一个选项可能是最简单和最好的解决scheme,除非重复数据的开销对于您的应用程序非常重要。 第二个选项相当复杂,可能不值得。 第三个选项基本上是选项二的优化,应该更容易实现,但可能不值得执行的努力,除非你真的不能select一个。

期待对此的反馈,以及其他人对该问题的解决scheme:)

我们已经部分在我们的网站上实现了这个function,我们使用“在一个单独的文档中存储修订版本”(和单独的数据库),我们编写了一个自定义函数来返回差异,我们将其存储起来。

为什么Store上的变体不会在文档中发生变化

文档中的当前密钥对并不是按照每个密钥对来存储版本,而是始终代表最近的状态,并将更改的“日志”存储在历史数组中。 只有那些自创build以来已经改变的密钥才会在日志中有一个条目。

 { _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" } } ] } 

可以有一个当前的NoSQL数据库和一个历史的NoSQL数据库。 每天都会有一个夜间ETL。 这个ETL将logging每个值与时间戳,所以而不是值它将永远是元组(版本字段)。 只有在当前值发生变化的情况下,才会logging新值,节省了处理空间。 例如,这个历史的NoSQL数据库json文件可以如下所示:

 { _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" } ] } ] }