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

时间:2014-06-17 22:50:55

标签: java amazon-dynamodb

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

{ object:one, name:"hey", version:1}

{ object:one, name:"ho", version:2}

我想要的是在db中有两个条目;即:

{ object:one, name:"hey", version:1 }

{ object:one, name:"hey", version:1}
{ object:one, name:"ho", version:2}

有什么方法可以实现这个目标吗?

4 个答案:

答案 0 :(得分:9)

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

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

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

Hash    Attr   Version
hey      a2     2
hey_v1   a1     1

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

Hash    Attr   Version
hey      a3      3
hey_v1   a1      1
hey_v2   a2      2

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

答案 1 :(得分:4)

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

资源表,其中 hash 是主键。

      +----------+---------+-------------------+
      |   hash   | version |   attr1..attrN    |
      +----------+---------+-------------------+
      | 1c5815b2 |    2    |  some values      |
      +----------+---------+-------------------+

resource-history 表,其中 hash 是分区键, version 是排序键。

      +----------+---------+-------------------+
      |   hash   | version |   attr1..attrN    |
      +----------+---------+-------------------+
      | 1c5815b2 |    2    |  some values      |
      +----------+---------+-------------------+
      | 1c5815b2 |    1    |  some old values  |
      +----------+---------+-------------------+

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

创建或更新资源时,首先写入 resource-history 表,然后写入 resource 表。

我发现这稍微更清晰,因为在单个表上处理不可变数据时,您不会遇到潜在的数据丢失情况。

答案 2 :(得分:4)

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

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

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

总结:

创建商品时:

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

在商品更新或覆盖时:

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

在只有主键的表上,这就是转换为转换的样子:

主键: id

  id  color
9501  violet
9502  cyan
9503  magenta

主键: id + version

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

这里要转换已使用排序键的表:

主键: id + date

id    date     color
9501  2018-01  violet
9501  2018-02  cyan
9501  2018-03  black

主键: 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

替代方法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

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

使用同一张表:

  • 主键由分区键和排序键组成
  • 版本必须在排序键中单独用作number或附加到现有的排序键中作为string

优势:

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

缺点:

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

使用辅助表:

  • 向两个表添加revision
  • 如果不使用排序键,请为辅助表version建立一个排序键。主表将始终具有version: 0。不是必须在主表上使用此键。
  • 如果已经使用排序键,请参见上面的“替代#2”

优势:

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

缺点:

  • 需要管理第二张表

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

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

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

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

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

费用

  • 在v0上获取每4K 1个读取容量单位
  • 1个用于准备TransactWriteItem的写容量单位
  • 在v0上进行放置/更新的每1K 1个写入容量单位
  • 修订版每1K 1个写入容量单位
  • 1个用于提交TransactWriteItem的写容量单位

注释:

  • 项目限制为400KB

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

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

通过

执行revisions
update

在覆盖先前存在的行时,我们可以将{ UpdateExpression: 'SET revisions = :newRevisionCount', ExpressionAttributeValues: { ':newRevisionCount': previousRow.revisions + 1, ':expectedRevisionCount': previousRow.revisions, }, ConditionExpression: 'revisions = :expectedRevisionCount', } ConditionExpression一起使用。

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

费用

  • 获取v0,每4K 1个读取容量单位
  • v0上的Put / UpdateItem每1KB 1个写入容量单位
  • 修订版每1KB 1个写入容量单位

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

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

通过

执行revisions
update-item

{ UpdateExpression: 'ADD revisions :revisionIncrement', ExpressionAttributeValues: { ':revisionIncrement': 1, }, ReturnValues: 'ALL_OLD', } 操作将自动创建ADD(如果不存在),并将其视为revisions。 ReturnValues的一个不错的好处是:

  

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

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

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

费用

  • 对于v0更新,每1KB 1个写入容量单位
  • 修订版每1KB 1个写入容量单位

注释:

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

自动异步修订插入

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

通过{p>执行revision

update

如果{ UpdateExpression: 'ADD revisions :revisionIncrement', ExpressionAttributeValues: { ':revisionIncrement': 1, }, } 操作不存在,则会自动创建ADD并将其视为revisions

用于基于先前的0请求以put递增revisions值覆盖记录。

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

get

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

费用

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

注释:

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

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

对于异步选项,存在一些故障点。不过,您可以立即按需重试,也可以为DynamoDB Stream解决方案安排稍后的时间。

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

答案 3 :(得分:2)

Amazon提出了有关如何在DynamoDB中进行版本控制的建议:https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-sort-keys.html#bp-sort-keys-version-control

使用排序键作为版本,您可以确保最新的键始终排在最前面(例如“ v0_”),然后其余键依次排序。他们还建议将v0_latest克隆为“ v00x_”,以便它可以作为最后一个关键字,以便进行查找以按顺序获取版本历史记录。

有关详细信息,请参见该链接。