現在、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}
これを達成する方法はありますか?
DynamoDBサービスは現在、行のバージョン管理をネイティブにサポートしているとは思いません。バージョン管理機能が必要な場合は、自分で行う必要があります。
DynamoDBでは、行はその主キーによって一意に識別されます。主キーは、HashKeyのみまたは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も取得されます。これがうまくいくかどうか教えてください。クライアント側でバージョン管理を行うためのより良い方法がある場合は、ここにも投稿してください。
私は、バージョンのログ記録中に更新が発生する競合状態を考慮し、データの重複を回避することを考慮して、読み取り/書き込み単位とコストの観点から最も効率的なものを実験して計算してきました。考えられる解決策をいくつか絞り込みました。あなたはあなたの最良のバリエーションを考慮しなければならないでしょう。
基本的な概念は、バージョン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
主キー: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
として追加する必要があります。利点:
短所:
revision
キーを追加しますversion
というセカンダリテーブルのソートキーを作成します。プライマリテーブルには常にversion: 0
があります。プライマリテーブルでのこのキーの使用は必須ではありません。利点:
get
リクエストは変更されません。短所:
データのパーティション分割方法に関係なく、リビジョン行の作成方法を決定する必要があります。ここにいくつかの異なる方法があります:
概要:行の現在のバージョンを取得します。現在の行で更新を実行し、1つのトランザクションで前のバージョンを挿入します。
競合状態を回避するには、TransactWriteItems
を使用して同じ操作で更新と挿入の両方を記述する必要があります。また、リクエストがデータベースサーバーに到達するまでに、更新するバージョンが正しいバージョンであることを確認する必要があります。これは、2つのチェックのいずれか、または両方によって実現されます。
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',
}
以前に存在した行を上書きするときに、同じConditionExpression
をput
と一緒に使用できます。
応答では、ConditionalCheckFailedException
を監視しています。これが返された場合は、リビジョンが別のプロセスによってすでに変更されていることを意味し、プロセスを最初から繰り返すか、完全に中止する必要があります。例外がない場合は、バージョン属性の値(数値または文字列)を更新した後、前に保存された行を挿入できます。
コスト
概要:revisions
をインクリメントし、古い属性を要求しながら、v0行で「ブラインド」更新を実行します。戻り値を使用して、バージョン番号で新しい行を作成します。
でupdate-item
を実行します
{
UpdateExpression: 'ADD revisions :revisionIncrement',
ExpressionAttributeValues: {
':revisionIncrement': 1,
},
ReturnValues: 'ALL_OLD',
}
ADD
アクションはrevisions
が存在しない場合は自動的に作成し、0
と見なします。 ReturnValuesの優れた利点の1つは、次のとおりです。
小さなネットワークと、より大きな応答を受信するための処理オーバーヘッドを除けば、戻り値の要求に関連する追加コストはありません。読み取り容量ユニットは消費されません。
更新応答では、Attributes
値は古いレコードのデータになります。このレコードのバージョンは、Attributes.revisions + 1
の値です。必要に応じて、バージョン属性の値を更新します(数値または文字列)。
これで、このレコードをターゲットテーブルに挿入できます。
コスト
注:
Attributes
の長さは65535に制限されています。概要:revisions
をインクリメントしながら、プライマリで「ブラインド」更新と挿入を実行します。 revision
への変更を監視するLambdaトリガーを使用して、リビジョンを非同期に挿入します。
でupdate
を実行します
{
UpdateExpression: 'ADD revisions :revisionIncrement',
ExpressionAttributeValues: {
':revisionIncrement': 1,
},
}
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);
}
これは単なるサンプルですが、本番コードにはさらに多くのチェックが含まれる可能性があります。
コスト
注:
私のユースケースでは、すでにDynamoDBストリームを使用しており、ユーザーがバージョン管理された行をそれほど頻繁に要求することはないと思います。また、非同期であるため、リビジョンの準備ができるまでユーザーに少し待つこともできます。そのため、2番目のテーブルと自動化されたラムダプロセスを使用することが、私にとってより理想的なソリューションになります。
非同期オプションの場合、いくつかの障害点があります。それでも、オンデマンドリクエストですぐに再試行するか、DynamoDBストリームソリューションで後でスケジュールすることができます。
他に解決策や批評がある場合は、コメントしてください。ありがとう!
2つの別々のテーブルを維持することによってこれを達成することもできます。 1つは最新のアイテム用で、もう1つはそれらのバージョン用です。詳細な説明付きのブログ投稿を書きました https://www.efekarakus.com/2018/05/25/client-side-row-versioning-in-dynamo-db.html
resourceテーブル。ここで、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テーブルに書き込みます。
単一のテーブルで不変のデータを処理しているときのように、潜在的なデータ損失のシナリオに遭遇することはないため、これは少しクリーンであることがわかりました。
Amazonは、DynamoDBでバージョン管理を行う方法について推奨事項を提示しています: https://docs.aws.Amazon.com/amazondynamodb/latest/developerguide/bp-sort-keys.html#bp-sort-keys -バージョン管理
ソートキーをバージョンとして使用すると、常に最新のものが最初になり(たとえば、「v0_」)、残りのキーはその後に順番に並べられます。また、v0_latestを「v00x_」に複製して、バージョン履歴を順番に取得したいルックアップの最後のキーになるようにすることも提案しています。
詳細については、そのリンクを参照してください。