web-dev-qa-db-ja.com

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}

これを達成する方法はありますか?

17

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も取得されます。これがうまくいくかどうか教えてください。クライアント側でバージョン管理を行うためのより良い方法がある場合は、ここにも投稿してください。

10
Erben Mo

私は、バージョンのログ記録中に更新が発生する競合状態を考慮し、データの重複を回避することを考慮して、読み取り/書き込み単位とコストの観点から最も効率的なものを実験して計算してきました。考えられる解決策をいくつか絞り込みました。あなたはあなたの最良のバリエーションを考慮しなければならないでしょう。

基本的な概念は、バージョン0を最新バージョンと見なすことを中心に展開されます。また、このアイテムの前に存在するリビジョンの数を一覧表示するrevisionsキーを使用しますが、アイテムの現在のバージョン(version = revisions + 1)を判別するためにも使用されます。バージョンがどのように存在するかを計算できることは要件であり、私の意見では、revisionsはそのニーズと、ユーザーに提示できる値を満たします。

したがって、最初の行はversion: 0revisions: 0で作成されます。これは技術的には最初のバージョン(v1)ですが、アーカイブされるまでバージョン番号は適用されません。この行が変更されると、version0に留まり、これは最新を示し、revisions1にインクリメントされます。新しい行は、以前のすべての値で作成されますが、その行はversion: 1を示します。

要約する:

アイテムの作成について:

  • revisions: 0version 0でアイテムを作成します

アイテムの更新または上書き時:

  • インクリメントrevisions
  • 以前とまったく同じように古い行を挿入しますが、version: 0version: 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として追加する必要があります。

利点:

  • すべてのデータが1つのテーブルに存在します

短所:

  • テーブルソートキーの使用を制限する可能性があります
  • バージョニングでは、プライマリテーブルと同じ書き込み単位が使用されます
  • ソートキーは、テーブルの作成中にのみ構成できます
  • おそらくv0に対してクエリを実行するためにコードを再調整する必要があります
  • 以前のバージョンもインデックスの影響を受けます

セカンダリテーブルの使用:

  • 両方のテーブルにrevisionキーを追加します
  • ソートキーを使用しない場合は、versionというセカンダリテーブルのソートキーを作成します。プライマリテーブルには常にversion: 0があります。プライマリテーブルでのこのキーの使用は必須ではありません。
  • すでにソートキーを使用している場合は、上記の「代替案#2」を参照してください。

利点:

  • プライマリテーブルは、キーを変更したり、再作成したりする必要はありません。 getリクエストは変更されません。
  • プライマリテーブルはそのソートキーを保持します
  • セカンダリテーブルは、独立した読み取りおよび書き込み容量ユニットを持つことができます
  • セカンダリテーブルには独自のインデックスがあります

短所:

  • 2番目のテーブルの管理が必要

データのパーティション分割方法に関係なく、リビジョン行の作成方法を決定する必要があります。ここにいくつかの異なる方法があります:

オンデマンドの同期アイテム-上書き/更新およびリビジョン挿入

概要:行の現在のバージョンを取得します。現在の行で更新を実行し、1つのトランザクションで前のバージョンを挿入します。

競合状態を回避するには、TransactWriteItemsを使用して同じ操作で更新と挿入の両方を記述する必要があります。また、リクエストがデータベースサーバーに到達するまでに、更新するバージョンが正しいバージョンであることを確認する必要があります。これは、2つのチェックのいずれか、または両方によって実現されます。

  1. UpdateTransactItemsコマンドで、ConditionExpressionは、更新される行のrevisionがのrevisionと一致することを確認する必要があります。以前にGetを実行したオブジェクト。
  2. PutTransactItemsコマンドでは、ConditionExpressionが行がまだ存在していないことを確認します。

コスト

  • 1 Get onv0の4Kあたりの読み取り容量ユニット
  • 1TransactWriteItemを準備するための書き込み容量ユニット
  • 1v0でのプット/アップデートの1Kあたりの書き込み容量ユニット
  • 1改訂を行うための1Kあたりの書き込み容量単位
  • 1TransactWriteItemをコミットするための書き込み容量ユニット

注:

  • アイテムは400KBに制限されています

オンデマンド、非同期item-get、item-overwrite/update、およびrevision-insert

概要:現在の行を取得して保存します。行を上書きまたは更新するときは、現在のリビジョンと照合してrevisionsをインクリメントします。以前に保存した行をバージョン番号とともに挿入します。

updateを実行します

{
  UpdateExpression: 'SET revisions = :newRevisionCount',
  ExpressionAttributeValues: {
    ':newRevisionCount': previousRow.revisions + 1,
    ':expectedRevisionCount': previousRow.revisions,
  },
  ConditionExpression: 'revisions = :expectedRevisionCount',
}

以前に存在した行を上書きするときに、同じConditionExpressionputと一緒に使用できます。

応答では、ConditionalCheckFailedExceptionを監視しています。これが返された場合は、リビジョンが別のプロセスによってすでに変更されていることを意味し、プロセスを最初から繰り返すか、完全に中止する必要があります。例外がない場合は、バージョン属性の値(数値または文字列)を更新した後、前に保存された行を挿入できます。

コスト

  • Get onv0の4Kあたり1つの読み取り容量ユニット
  • v0のPut/UpdateItemの1KBあたり1書き込み容量ユニット
  • Putonリビジョンの1KBあたり1書き込み容量ユニット

オンデマンドの非同期ブラインドアイテム-更新とリビジョン-挿入

概要:revisionsをインクリメントし、古い属性を要求しながら、v0行で「ブラインド」更新を実行します。戻り値を使用して、バージョン番号で新しい行を作成します。

update-itemを実行します

{
  UpdateExpression: 'ADD revisions :revisionIncrement',
  ExpressionAttributeValues: {
    ':revisionIncrement': 1,
  },
  ReturnValues: 'ALL_OLD',
}

ADDアクションはrevisionsが存在しない場合は自動的に作成し、0と見なします。 ReturnValuesの優れた利点の1つは、次のとおりです。

小さなネットワークと、より大きな応答を受信するための処理オーバーヘッドを除けば、戻り値の要求に関連する追加コストはありません。読み取り容量ユニットは消費されません。

更新応答では、Attributes値は古いレコードのデータになります。このレコードのバージョンは、Attributes.revisions + 1の値です。必要に応じて、バージョン属性の値を更新します(数値または文字列)。

これで、このレコードをターゲットテーブルに挿入できます。

コスト

  • v0での更新の場合、1KBあたり1書き込み容量ユニット
  • Putonリビジョンの1KBあたり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);
}

これは単なるサンプルですが、本番コードにはさらに多くのチェックが含まれる可能性があります。

コスト

  • v0に乗るための4Kあたり1つの読み取り容量ユニット(上書き時のみ)
  • v0でのPut/Updateの1KBあたり1書き込み容量ユニット
  • GetRecordsコマンドごとに1つのDynamoDBストリーム読み取りリクエストユニット
  • 改訂版の1KBあたり1書き込み容量単位

注:

  • DynamoDBストリームシャードデータは24時間後に期限切れになります
  • DynamoDBストリーム読み取り要求ユニットは、テーブル読み取り容量ユニットから独立しています
  • Lambda関数の使用には独自の価格設定があります
  • ストリームビュータイプを変更するには、ストリームを無効にしてから再度有効にする必要があります
  • Write、Put、BatchWriteItems、TransactWriteItemsコマンドで動作します

私のユースケースでは、すでにDynamoDBストリームを使用しており、ユーザーがバージョン管理された行をそれほど頻繁に要求することはないと思います。また、非同期であるため、リビジョンの準備ができるまでユーザーに少し待つこともできます。そのため、2番目のテーブルと自動化されたラムダプロセスを使用することが、私にとってより理想的なソリューションになります。

非同期オプションの場合、いくつかの障害点があります。それでも、オンデマンドリクエストですぐに再試行するか、DynamoDBストリームソリューションで後でスケジュールすることができます。

他に解決策や批評がある場合は、コメントしてください。ありがとう!

25
ShortFuse

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テーブルに書き込みます。

単一のテーブルで不変のデータを処理しているときのように、潜在的なデータ損失のシナリオに遭遇することはないため、これは少しクリーンであることがわかりました。

8
Efe Karakus

Amazonは、DynamoDBでバージョン管理を行う方法について推奨事項を提示しています: https://docs.aws.Amazon.com/amazondynamodb/latest/developerguide/bp-sort-keys.html#bp-sort-keys -バージョン管理

ソートキーをバージョンとして使用すると、常に最新のものが最初になり(たとえば、「v0_」)、残りのキーはその後に順番に並べられます。また、v0_latestを「v00x_」に複製して、バージョン履歴を順番に取得したいルックアップの最後のキーになるようにすることも提案しています。

詳細については、そのリンクを参照してください。

4
kos