【问题标题】:DynamoDB - Event Store on AWSDynamoDB - AWS 上的事件存储
【发布时间】:2019-09-09 19:02:18
【问题描述】:

我正在 AWS 上设计一个 Event Store,我选择了 DynamoDB,因为它似乎是最好的选择。我的设计看起来很不错,但我遇到了一些我无法解决的问题。

**设计

事件由 (StreamId, EventId) 对唯一标识:

  • StreamId:和aggregateId一样,意思是one Event Stream for one Aggregate。
  • EventId:一个递增的数字,有助于将排序保持在同一个事件流中

事件保留在 DynamoDb 上。每个事件映射到表中的单个记录,其中必填字段为 StreamId、EventId、EventName、Payload(可以轻松添加更多字段)。

partitionKey为StreamId,sortKey为EventId。

在将事件写入事件流时使用乐观锁定。为此,我使用了 DynamoDb 条件写入。如果已经存在相同(StreamId,EventId)的事件,我需要重新计算聚合,重新检查业务条件,如果业务条件通过,最后再次写入。

事件流

每个事件流都由 partitionKey 标识。查询所有事件的流等于查询 partitionKey=${streamId} 和 0 到 MAX_INT 之间的 sortKey。

每个事件流标识一个且只有一个聚合。如前所述,这有助于使用乐观锁定处理同一聚合上的并发写入。这也可以在重新计算聚合时提供出色的性能。

活动发布

利用 DynamoDB Streams + Lambda 的组合发布事件。

重播事件

问题从这里开始。将每个事件流仅映射到一个聚合(这会导致拥有大量事件流),没有简单的方法可以知道我需要从哪些事件流中查询所有事件。

我正在考虑在 DynamoDB 中的某处使用一个额外的记录,它将所有 StreamId 存储在一个数组中。然后我可以查询它并开始查询事件,但如果在我重播时创建了一个新流,我会丢失它。

我错过了什么吗?或者,我的设计是否完全错误?

【问题讨论】:

    标签: amazon-web-services amazon-dynamodb distributed-computing event-sourcing amazon-dynamodb-data-modeling


    【解决方案1】:

    原答案: 您能否详细说明“聚合”?是和EventID一样,还是不同的item属性?

    您需要存储事件和聚合吗?

    您的事件持久性要求是什么?

    如果

    如果您愿意,如果您可以分享更多详细信息,我很乐意提供帮助。

    4/23 更新

    让我提出另一种可供您考虑的替代方案:CloudWatch Logs。 CloudWatch 日志组相当于您的事件表。您的每个流都将映射到 CloudWatch 日志流。

    您需要考虑上面针对 DynamoDB 表描述的条件写入等效逻辑。

    CWL 的优势在于您可以避免上面提到的热键问题。缺点是: (1) 您需要考虑针对 CWL 的解决方案。 (2) DynamoDB 为读取提供

    我希望这会有所帮助。

    【讨论】:

    • 聚合是事件流的业务表示。在事件溯源环境中工作,我不会在 14 天后丢失我的事件。这就是为什么 Kinesis 不能成为我的选择。
    【解决方案2】:

    您可以使用 GSI 检索给定时间段内的事件。根据正在处理的事件数量,您可能需要编写 GSI 分片以避免热键。假设事件项小于 1KB,如果摄取率高于 1000 项/秒,则需要将它们分散到 GSI 上。如果事件大于 1KB,则需要将它们分散得更多。对于小于 1KB 的项目,将每秒的事件总数除以 1000。这将告诉您 GSI 需要多少分片才能跟上表格,例如假设您每秒摄取 5K 事件,您将需要 5 个分片。

    当您将事件写入表时,添加一个名为“GSIKey”的新属性,并在插入事件时为该属性创建一个介于 0-4 之间的随机值。使用“GSIKey”作为分区键和时间戳作为排序键创建 GSI。当您需要获取给定时间范围内的所有事件时,请使用您正在查找的时间范围查询所有 5 个分片,然后简单地对结果集进行合并排序以生成按时间排序的事件列表。如果您每秒处理的事件少于 1000 个,则可以使用“0”作为 GSIKey 值,然后在该分区中查询您需要的事件。

    【讨论】:

    • 太好了!问题是随着数据的增长,我将无法利用相同数量的分片。如果我添加一个新的,过去的事件将不会在分片之间重新平衡。我是否应该从 100-1000 之类的大量分片开始?另一件事是,我无法查询大量事件(记录),因为随着数据的增长,我肯定需要 1MB 以上的响应空间。
    • 最后一个问题,DynamoDB其实是支持respones分页的。
    【解决方案3】:

    我错过了什么吗?

    不是真的;这是一个难题[tm]。

    您的写入用例通常只关注模型中的单个引用——指向当前事件历史的指针。您的读取用例通常涉及分布在多个流中的数据。

    这通常起作用的方式是,您的持久性存储不仅维护已写入的更改,而且还维护一个支持读取的索引。例如,Eventide's postgres message store 取决于将行插入表时发生的索引。在Event Store 的情况下,对索引的更新将作为与流更改相同的序列化“事务”的一部分写入。

    表达相同想法的另一种方式:查询实际上以比写入更粗的粒度运行,存储设备隐含地提供您期望的协调保证。

    去掉协调,你就有了类似于为每个流分配一个唯一主机的东西。

    仔细查看Git object database 并熟悉隐藏在该商店中的真实情况可能会很有用。我还发现 Rich Hickey 的演讲 The Language of the System 提供了有用的概念来区分 valuesnamesreferences

    我选择了 DynamoDB,因为它似乎是最好的选择

    除非您有一些令人信服的商业理由从头开始建立您的活动商店,否则我建议您转而查看Aurora,看看您能做到多远。它可能会为您赢得等待其他人为您组装成本效益高的云原生事件存储设备所需的时间。

    【讨论】:

    • 在讨论的 DynamoDB 无法使用正确的数据建模的情况下,Aurora 将如何做不同的事情?
    猜你喜欢
    • 1970-01-01
    • 2012-08-16
    • 2017-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-15
    • 1970-01-01
    • 2022-11-16
    相关资源
    最近更新 更多