目前,我看到当我在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关于项目更新或覆盖:
revisionsversion: 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好处:
缺点:
revision密钥添加到两个表version。主表将始终具有version: 0。在主表上不必使用此键。好处:
get要求保持不变。缺点:
无论您决定如何对数据进行分区,现在我们都必须决定如何创建修订行。以下是几种不同的方法:
摘要:获取该行的当前版本。对当前行执行更新,并通过一个事务插入先前的版本。
为避免竞争情况,我们需要使用在同一操作中写入更新和插入TransactWriteItems。另外,我们需要确保在请求到达数据库服务器时,我们正在更新的版本是正确的版本。我们可以通过两项检查之一,甚至两项检查来实现:
Update命令中TransactItems,ConditionExpression必须检查revision要更新的行revision中的与我们Get之前执行过的对象中的是否匹配。Put命令中TransactItems,进行ConditionExpression检查以确保该行尚不存在。成本
笔记:
摘要:获取并存储当前行。覆盖或更新行时,请检查当前的修订版本和增量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行上执行“盲目”更新,同时增加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。适当地更新版本属性上的值(数字或字符串)。
现在,您可以将此记录插入目标表中。
成本
笔记:
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)
这只是示例,但是生产代码可能会包含更多检查。
成本
笔记:
对于我的用例,我已经在使用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.如果这对你有用,请告诉我.如果您有更好的方法在客户端进行版本控制,请在此处发布.
您还可以通过维护两个单独的表来实现此目的。一个仅用于最新项目,另一个用于其版本。我写了一篇博客文章,其中有详细的说明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)
重要的是,任何更改记录的操作都应增加其版本号。
创建或更新资源时,请先写入资源历史表,然后再写入资源表。
我发现这样做稍微干净一点,因为您不会像在单个表上处理不可变数据时那样遇到潜在的数据丢失情况。
亚马逊已就如何在 DynamoDB 中进行版本控制提出了建议:https ://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-sort-keys.html#bp-sort-keys-version-control
使用排序键作为版本,您可以确保最新的总是在前(例如“v0_”),其余的键在此之后按顺序排列。他们还建议将 v0_latest 克隆到“v00x_”,以便它可以成为最后一个键,以便查找想要按顺序获取版本历史记录。
有关完整详细信息,请参阅该链接。