快照拍摄和恢复策略

Snapshot taking and restore strategies

我一直在阅读有关 CQRS+EventSoucing 模式(我希望在不久的将来应用)的内容,我发现的所有平台和演示文稿的一个共同点是拍摄模型状态的快照,以便恢复它,但其中 none 个分享 patterns/strategies 这样做。

我想知道您是否可以分享您在这件事上的想法和经验,特别是在以下方面:

TL;DR:您如何在 CQRS+EventSourcing 应用程序中实现 Snapshotting?优点和缺点?

  • 规则 1:不要。
  • 规则 #2:不要。

为事件源模型创建快照是一种性能优化。性能优化的第一条规则?不要。

具体来说,快照减少了您在存储库中尝试从事件存储重新加载模型历史记录的时间。

如果您的存储库可以将模型保存在内存中,那么您就不会经常重新加载它。所以快照的好处很小。因此:不要。

如果你可以将你的模型分解成aggregates,也就是说你可以将你的模型的历史分解成许多具有不重叠历史的实体,那么你的一个模型很长的模型历史变成许多短的历史,每一个都描述了对单个实体的更改。您需要加载的每个实体历史记录都非常短,因此快照的优势很小。因此:不要。

The kind of systems I'm working today require high performance but not 24x7 availability. So in a situation where I shut down my system for maintenace and restart it I'd have to load and reprocess all my event store as my fresh system doesn't know which aggregate ids to process the events. I need a better starting point for my systems to restart be more efficient.

您担心在存储库内存缓存很冷时错过写入 SLA,并且您有很长的模型历史记录需要重新加载大量事件。与尝试将模型历史重构为更小的流相比,使用快照可能要合理得多。好的....

快照存储是一个读取模型 -- 在任何时间点,您应该能够清除模型并从事件存储中的持久历史中重建它.

从存储库的角度来看,快照存储是一个缓存;如果没有可用的快照,或者如果商店本身在 SLA 内没有响应,您想要退回到重新处理整个事件历史记录,从初始种子状态开始。

服务提供商界面将类似于

interface SnapshotClient {
    SnapshotRecord getSnapshot(Identifier id)
}

SnapshotRecord 将向存储库提供使用快照所需的信息。这将至少包括

  1. 一个 memento 允许存储库重新水化快照状态
  2. 快照投影仪在构建快照时处理的最后一个事件的描述。

然后模型将从备忘录中重新水化快照状态,从事件存储中加载历史记录,扫描向后(即,从最近的事件开始)寻找对于 SnapshotRecord 中记录的事件,然后按顺序应用后续事件。

SnapshotRepository 本身可以是一个键值存储(对于任何给定的 ID 最多一条记录),但是具有 blob 支持的关系数据库也可以正常工作

select * 
from snapshots s 
where id = ? 
order by s.total_events desc 
limit 1

快照投影仪和存储库紧密耦合——它们需要就所有可能的历史实体的状态达成一致,它们需要就如何 de/re-hydrate 纪念品达成一致,并且它们需要同意使用哪个 id 来定位快照。

紧耦合也意味着你不需要特别担心 纪念品的模式;一个字节数组就可以了。

然而,他们不需要同意自己以前的化身。 Snapshot Projector 2.0 discards/ignores Snapshot Projector 1.0 留下的任何快照——毕竟快照存储只是一个缓存

i'm designing an application that will probably generate millions event a day. what can we do if we need to rebuild a view 6 month later

这里一个更有说服力的答案是显式建模 time。您有一个实体可以活六个月,还是有 180 多个实体每个都可以活一天?会计是一个很好的参考领域:在财政年度结束时,账簿关闭,下一年的账簿打开结转。

Yves Reynhout 经常谈论建模时间和日程安排; Evolving a Model 可能是一个很好的起点。

您确实需要拍摄快照的实例很少。但是有几个 - 一个常见的例子是分类帐中的帐户。您将有成千上万的 credit/debit 事件产生帐户的最终 BALANCE 状态 - 如果不经常快照,那就太疯狂了。

我设计 Aggregates.NET 时的快照方法默认关闭,要启用您的聚合或实体必须继承自 AggregateWithMementoEntityWithMemento,而您的实体必须定义一个 RestoreSnapshot、一个 TakeSnapshot 和一个 ShouldTakeSnapshot

是否拍摄快照由实体自行决定。一个常见的模式是

Boolean ShouldTakeSnapshot() {
    return this.Version % 50 == 0;
}

这当然会每 50 个事件拍摄一次快照。

读取实体流时,我们做的第一件事是检查快照,然后从拍摄快照的那一刻起读取实体流的其余部分。 IE:不要要求整个流只是我们没有快照的部分。

至于商店 - 您几乎可以使用任何东西。 VOU 是正确的,虽然键值存储是最好的,因为你只需要 1. 检查是否存在 2. 加载整个东西 - 这对于 kv

是理想的

对于系统重启 - 我并没有真正了解您描述的问题所在。你的域服务器没有理由在不同的时间点做不同的事情的意义上是有状态的。它应该只做一件事——处理下一个命令。在处理命令的过程中,它从事件存储加载数据(包括快照),针对产生业务异常或记录到存储中的域事件的实体运行命令。

我认为您可能在谈论缓存和冷启动时试图优化太多。