如何在不替换DynamoDB中的先前记录的情况下实现版本控制?

iCo*_*unk 16 amazon-dynamodb

目前,我看到当我在DynamoDB中使用版本控制时,它会更改版本号,但新条目将替换旧条目; 即:

旧

{ object:one, name:"hey", version:1}
Run Code Online (Sandbox Code Playgroud)

新

{ object:one, name:"ho", version:2}
Run Code Online (Sandbox Code Playgroud)

我想要的是在数据库中有两个条目; 即:

旧

{ object:one, name:"hey", version:1 }
Run Code Online (Sandbox Code Playgroud)

新

{ object:one, name:"hey", version:1}
{ object:one, name:"ho", version:2}
Run Code Online (Sandbox Code Playgroud)

有没有办法实现这个目标?

Sho*_*use 18

我一直在尝试和计算在读/写单位和成本方面最有效的方法,考虑了在记录版本时进行更新的竞争条件,并避免了数据重复。我缩小了一些可能的解决方案。您必须考虑自己的最佳变化。

基本概念围绕着将版本0视为最新版本。另外,我们将使用一个revisions密钥,该密钥将列出该项目之前存在多少个修订,也将用于确定该项目的当前版本(version = revisions + 1)。能够计算版本的存在是一项要求,在我看来,它revisions可以满足该需求以及可以提供给用户的价值。

因此,将使用version: 0和创建第一行revisions: 0。尽管从技术上讲这是第一个版本(v1),但在归档之前,我们不会应用版本号。当该行更改时,version停留在0,仍表示最新,并revisions增加到1。将使用所有先前的值创建新行,但现在该行表示version: 1。

总结一下:

关于项目创建:

  • 使用revisions: 0和创建项目version 0

关于项目更新或覆盖:

  • 增量 revisions
  • 完全像以前一样插入旧行,但是更改version: 0为新版本,可以很容易地将其计算为version: revisions + 1。

这是在仅具有主键的表上转换为转换的样子:

主键:id

  id  color
9501  violet
9502  cyan
9503  magenta
Run Code Online (Sandbox Code Playgroud)

主键:id +版本

id    version  revisions  color
9501        0          6  violet
9501        1          0  red
9501        2          1  orange
9501        3          2  yellow
9501        4          3  green
9501        5          4  blue
9501        6          5  indigo
Run Code Online (Sandbox Code Playgroud)

这是在转换已经使用排序键的表:

主键:id +日期

id    date     color
9501  2018-01  violet
9501  2018-02  cyan
9501  2018-03  black
Run Code Online (Sandbox Code Playgroud)

主键:id + date_ver

id    date_ver     revisions  color
9501  2018-01__v0          6  violet
9501  2018-01__v1          0  red
9501  2018-01__v2          1  orange
9501  2018-01__v3          2  yellow
9501  2018-01__v4          3  green
9501  2018-01__v5          4  blue
9501  2018-01__v6          5  indigo
Run Code Online (Sandbox Code Playgroud)

备选方案2:

id    date_ver     revisions  color
9501  2018-01              6  violet
9501  2018-01__v1          0  red
9501  2018-01__v2          1  orange
9501  2018-01__v3          2  yellow
9501  2018-01__v4          3  green
9501  2018-01__v5          4  blue
9501  2018-01__v6          5  indigo
Run Code Online (Sandbox Code Playgroud)

实际上,我们可以选择将以前的版本放在同一张表中,也可以将它们分成自己的表。两种选择都有其各自的优点和缺点。

使用同一表:

  • 主键由分区键和排序键组成
  • 版本必须在排序键中单独使用,也number可以在现有排序键后附加string

好处:

  • 所有数据都存在一个表中

缺点:

  • 可能限制您使用表格的排序键
  • 版本控制使用与主表相同的写入单位
  • 排序键只能在创建表期间配置
  • 可能需要重新调整代码以针对v0进行查询
  • 以前的版本也会受到索引的影响

使用辅助表:

  • 将revision密钥添加到两个表
  • 如果不使用排序键,请为称为的辅助表构建一个排序键version。主表将始终具有version: 0。在主表上不必使用此键。
  • 如果已经使用排序键,请参见上面的“替代方法2”

好处:

  • 主表不需要更改任何键或重新创建。get要求保持不变。
  • 主表保留其排序键
  • 辅助表可以具有独立的读写容量单位
  • 辅助表具有自己的索引

缺点:

  • 需要管理第二张桌子

无论您决定如何对数据进行分区,现在我们都必须决定如何创建修订行。以下是几种不同的方法:

按需同步项目覆盖/更新和修订插入

摘要:获取该行的当前版本。对当前行执行更新,并通过一个事务插入先前的版本。

为避免竞争情况,我们需要使用在同一操作中写入更新和插入TransactWriteItems。另外,我们需要确保在请求到达数据库服务器时,我们正在更新的版本是正确的版本。我们可以通过两项检查之一,甚至两项检查来实现:

  1. 在中的Update命令中TransactItems,ConditionExpression必须检查revision要更新的行revision中的与我们Get之前执行过的对象中的是否匹配。
  2. 在中的Put命令中TransactItems,进行ConditionExpression检查以确保该行尚不存在。

成本

  • v0上的每4K 1个读取容量单位
  • 1个写容量单位,用于准备TransactWriteItem
  • v0上的Put / Update的每1K 1个写入容量单位
  • 修订时每1K 1个写入容量单位
  • 1个写入容量单位,用于提交TransactWriteItem

笔记:

  • 项目限制为400KB

按需,异步项获取,项覆盖/更新和修订版插入

摘要:获取并存储当前行。覆盖或更新行时,请检查当前的修订版本和增量revisions。插入先前存储的带有版本号的行。

执行update与

{
  UpdateExpression: 'SET revisions = :newRevisionCount',
  ExpressionAttributeValues: {
    ':newRevisionCount': previousRow.revisions + 1,
    ':expectedRevisionCount': previousRow.revisions,
  },
  ConditionExpression: 'revisions = :expectedRevisionCount',
}
Run Code Online (Sandbox Code Playgroud)

我们可以使用相同的ConditionExpression具有put覆盖以前存在的行时。

在回应中,我们正在注意ConditionalCheckFailedException。如果返回此内容,则意味着修订已被另一个过程更改,我们需要从头开始重复该过程或完全中止。如果没有例外,那么我们可以在适当时更新版本属性上的值(数字或字符串)后插入上一个存储的行。

成本

  • v0上的每4K 1个读取容量单位
  • v0上Put / UpdateItem的每1KB 1个写入容量单位
  • 每1KB 1个写入容量单位(用于修订)

按需,异步盲项更新和修订插入

摘要:在v0行上执行“盲目”更新,同时增加revisions并请求旧属性。使用返回值创建一个带有版本号的新行。

执行update-item与

{
  UpdateExpression: 'ADD revisions :revisionIncrement',
  ExpressionAttributeValues: {
    ':revisionIncrement': 1,
  },
  ReturnValues: 'ALL_OLD',
}
Run Code Online (Sandbox Code Playgroud)

如果该ADD动作revisions不存在,它将自动创建并考虑该动作0。ReturnValues的一个不错的好处是:

除了小型网络外,没有其他与请求返回值相关的成本以及接收较大响应的处理开销。不会消耗读取容量单位。

在更新响应中,该Attributes值将是旧记录中的数据。该记录的版本为的值Attributes.revisions + 1。适当地更新版本属性上的值(数字或字符串)。

现在,您可以将此记录插入目标表中。

成本

  • v0上的更新每1KB 1个写入容量单位
  • 每1KB 1个写入容量单位(用于修订)

笔记:

  • 返回的对象的Attributes长度限制为65535。
  • 没有覆盖行的解决方案。

自动异步修订版插入

摘要:在递增的同时对主要数据库执行“盲目”更新和插入revisions。使用Lambda触发器监视更改以revision异步插入修订。

执行update与

{
  UpdateExpression: 'ADD revisions :revisionIncrement',
  ExpressionAttributeValues: {
    ':revisionIncrement': 1,
  },
}
Run Code Online (Sandbox Code Playgroud)

如果该ADD动作revisions不存在,它将自动创建并考虑该动作0。

用于基于先前的请求覆盖具有put增量revisions值的记录get。

配置DynamoDB流视图类型以返回新图像和旧图像。针对数据库表设置Lambda触发器。这是NodeJS的示例代码,它将比较旧图像和新图像并调用一个函数以批量编写修订。

/**
 * @param {AWSLambda.DynamoDBStreamEvent} event
 * @return {void}
 */
export function handler(event) {
  const oldRevisions = event.Records
    .filter(record => record.dynamodb.OldImage
      && record.dynamodb.NewImage
      && record.dynamodb.OldImage.revision.N !== record.dynamodb.NewImage.revision.N)
    .map(record => record.dynamodb.OldImage);
  batchWriteRevisions(oldRevisions);
}
Run Code Online (Sandbox Code Playgroud)

这只是示例,但是生产代码可能会包含更多检查。

成本

  • v4上的每4K 1个读取容量单位(仅在覆盖时)
  • v1上的放置/更新操作每1KB 1个写入容量单位
  • 每个GetRecords命令1个DynamoDB流读取请求单位
  • 每1KB 1个写入容量单位用于修订版

笔记:

  • DynamoDB流分片数据在24小时后过期
  • DynamoDB流读取请求单位独立于表读取容量单位
  • 使用Lambda函数有其自己的定价
  • 更改流视图类型需要禁用和重新启用流
  • 与Write,Put,BatchWriteItems,TransactWriteItems命令一起使用

对于我的用例,我已经在使用DynamoDB流,并且我不希望用户经常请求版本化的行。我也可以让用户稍等片刻,因为它们是异步的。这使得使用第二张桌子和自动lambda流程对我来说是更理想的解决方案。

对于异步选项,存在一些故障点。不过,您可以立即按需重试,也可以计划稍后为DynamoDB Stream解决方案进行调度。

如果有人还有其他解决方案或批评,请发表评论。谢谢!


Erb*_* Mo 10

我认为DynamoDB服务目前不支持行版本控制.如果您需要版本控制功能,则需要在您这边做.

在DynamoDB中,一行由其主键唯一标识.主键可以是HashKey-only或HashKey + RangeKey.如果要区分具有不同版本的同一行,则需要在主键中的某处包含版本号.

例如,您可以将版本号附加到散列键的末尾,以用于行的所有旧版本.具有最新版本的行将使用原始哈希键.

Hash    Attr   Version
hey      a2     2
hey_v1   a1     1
Run Code Online (Sandbox Code Playgroud)

在将行更新到版本3之后,该表应如下所示:

Hash    Attr   Version
hey      a3      3
hey_v1   a1      1
hey_v2   a2      2
Run Code Online (Sandbox Code Playgroud)

在客户端进行版本控制总是不完美.例如,对于上述方法,如果进行扫描,您将获得hey_V1和hey_v2.如果这对你有用,请告诉我.如果您有更好的方法在客户端进行版本控制,请在此处发布.

  • 很好。另一种选择是将版本号移到排序键中。允许您查询所有“一个”对象以及特定版本。扩展这一点可以标准化地表示排序键0始终是“当前” (2认同)
  • DynamoDB现在支持[交易](https://aws.amazon.com/blogs/aws/new-amazon-dynamodb-transactions/),这意味着在编写新项目和对其进行版本控制之间不存在竞争条件 (2认同)

Efe*_*kus 8

您还可以通过维护两个单独的表来实现此目的。一个仅用于最新项目,另一个用于其版本。我写了一篇博客文章,其中有详细的说明https://www.efekarakus.com/2018/05/25/client-side-row-versioning-in-dynamo-db.html

在资源表,其中散列是主键。

      +----------+---------+-------------------+
      |   hash   | version |   attr1..attrN    |
      +----------+---------+-------------------+
      | 1c5815b2 |    2    |  some values      |
      +----------+---------+-------------------+
Run Code Online (Sandbox Code Playgroud)

该资源的历史表,其中散列是分区键和版本的排序键。

      +----------+---------+-------------------+
      |   hash   | version |   attr1..attrN    |
      +----------+---------+-------------------+
      | 1c5815b2 |    2    |  some values      |
      +----------+---------+-------------------+
      | 1c5815b2 |    1    |  some old values  |
      +----------+---------+-------------------+
Run Code Online (Sandbox Code Playgroud)

重要的是,任何更改记录的操作都应增加其版本号。

创建或更新资源时,请先写入资源历史表,然后再写入资源表。

我发现这样做稍微干净一点,因为您不会像在单个表上处理不可变数据时那样遇到潜在的数据丢失情况。


kos*_*kos 8

亚马逊已就如何在 DynamoDB 中进行版本控制提出了建议:https ://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-sort-keys.html#bp-sort-keys-version-control

使用排序键作为版本,您可以确保最新的总是在前(例如“v0_”),其余的键在此之后按顺序排列。他们还建议将 v0_latest 克隆到“v00x_”,以便它可以成为最后一个键,以便查找想要按顺序获取版本历史记录。

有关完整详细信息,请参阅该链接。

  • 在这种方法中,要找到下一个版本号,您必须先阅读。这使得写入成本更高,在 v0 上写入,然后找到下一个最大的因此读取,然后在 vX 上再次写入 (4认同)