今日は、1つ以上のテーブルの複数のDynamoDBのアイテムを取り扱うようなアトミックなトランザクションを実行するのに必要となる開発の労力を削減できる新しいクライアントライブラリを紹介いたします。 このライブラリを使えば、今まで(スケーラビリティの問題を抱えている)リレーショナルデータベースを必要としたり、原子性を実装するためにアプリケーション層で多大な労力が必要となるようなアプリケーションをDynamoDBを使ってより簡単に開発できるようになります。
詳しく見ていく前に、原子性とトランザクションの概念を見直してみましょう。
多くの場合、複数の関連したデータベースストレージを操作する際は、トランザクションとして扱われなければなりません。 トランザクションのスコープ内では、ストレージ操作の全てが成功するか、全て失敗のどちらかにならなければなりません。これを原子性と呼びます。全てがうまくいくと、トランザクションは次のように進みます。
- トランザクションを開始する
- アイテム#1をPutする
- ...
- アイテム#NをPutする
- トランザクションをコミットする
ステップ5が完了する前にエラーが検出されたり、プログラムが予期せず終了してしまった場合、トランザクションは失敗し、Put操作(ステップ2から4)は取り消され、データベースのアイテムはステップ1の前の状態に戻ります。 ある銀行口座から別の口座に送金するコードを書いている場合、操作をトランザクション内にカプセル化したいと考えるでしょう。そうしなければ、仮に都合の悪いタイミングで中断や障害が発生した場合に、お金を失ってしまいます。
DynamoDBトランザクションライブラリ
新しいライブラリはAWS SDK for Javaで既存のDynamoDBの拡張として実装されています。
このライブラリは2つのDynamoDBテーブルを使って実行中のトランザクションの状態を追跡します。ひとつ目のテーブルはトランザクションを保存し、二つ目はトランザクション内で変更されたアイテムのトランザクション前のスナップショットを保存します。
全てのトランザクションを開始する前に、このテーブルを作成するために、TableManagerクラスから、verifyOrCreateTransactionTable関数とverifyOrCreateTransactionImagesTable関数を呼ぶ必要があります。
必要以上の遅延を避けるために十分なリードとライトキャパシティをプロビジョンしていることを確認してください。
必要なテーブルを作成するコードのサンプルは次のとおりです。
TransactionManager.verifyOrCreateTransactionTable(client, "Transactions" /*tableName*/,
10 /*readCapacityUnits*/, 10 /*writeCapacityUnits*/, 10*60 /*waitTimeSeconds*/);
TransactionManager.verifyOrCreateTransactionImagesTable(client, "TransactionImages" /*tableName*/,
10 /*readCapacityUnits*/, 10 /*writeCapacityUnits*/, 10*60 /*waitTimeSeconds*/);
ThreadとReplyテーブル上でトランザクションを実行するコードは次のようになります。このコードはAmazon DynamoDB Developer Guideにも記載されています。 この例のフォーラムアプリケーションには、フォーラムに投稿された1つの質問毎に1つのアイテムを保存するThreadテーブルと、各質問に対する返答毎に1つのアイテムを保存するReplyテーブルがあります。 Thread テーブルは質問毎の返答の総数と、質問が回答されたかどうかのフラグも持っています。 次のトランザクションは、新しい返答を追加し、Threadのアイテムに紐づけられた"返答数"のカウンターをインクリメントします。
TransactionManager txManager =new TransactionManager(client, "Transactions", "TransactionImages");
...
// トランザクションマネージャから新しいトランザクションを作成します。
Transaction t1 = txManager.newTransaction();
// DynamoDBのトランザクションについて尋ねるThreadへ新しいReplyを追加するためにPutItemリクエストを作成します。
Map<String, AttributeValue> reply =new HashMap<String, AttributeValue>();
reply.put("Id", new AttributeValue("Amazon DynamoDB#Transactions?"));
reply.put("ReplyDateTime", new AttributeValue("(the current date and time)"));
reply.put("PostedBy", new AttributeValue("DavidY@AWS"));
reply.put("Message", new AttributeValue("Transactions are now available!"));
t1.putItem(new PutItemRequest()
.withTableName("Reply")
.withItem(reply));
// この時点で、新しいReplyアイテムはテーブルに存在しますが、コミットはされていません。
// トランザクションオブジェクトに異なるアイテムのための2つめのリクエストを追加します。
Map<String, AttributeValue> thread =new HashMap<String, AttributeValue>();
thread.put("ForumName", new AttributeValue("Amazon DynamoDB"));
thread.put("Subject", new AttributeValue("Transactions?"));
Map<String, AttributeValueUpdate> threadUpdates =new HashMap<String, AttributeValueUpdate>();
threadUpdates.put("Replies", new AttributeValueUpdate(new AttributeValue().withN("1"), "ADD"));
t1.updateItem(new UpdateItemRequest()
.withTableName("Thread")
.withKey(thread)
.withAttributeUpdates(threadUpdates));
// この時点で、新しいThreadアイテムがテーブル内にありますが、まだコミットされていません。
// トランザクションをコミットします。
t1.commit();
// トランザクションがコミットされました。Thread と Reply の書き込みは、両方コミットされます。
// トランザクションアイテムを削除します。
t1.delete();
アトミックな書き込みに加えて、トランザクションライブラリは完全隔離、コミッティド、アンコミッティドの3段階のリードの隔離レベルを提供します。 完全隔離のリードはトランザクションの間、取得しているロックを介して実行されます。 コミッティドリードは、結果整合性をもったリードと同じような一貫性の保証を提供し、ロックが検出された場合はアイテムの古いコピーを読むことにより実行されます。 アンコミッティドリード(ダーティリードとも呼ばれます)は、後でロールバックされたデータを返すことがあるので、最もコストが安いですが、最も危険です。 "コミッティド"な隔離レベルでリード操作を実行するコードは次のようになります。
Map<String, AttributeValue> key =new HashMap<String, AttributeValue>();
key.put("ForumName", new AttributeValue("Amazon DynamoDB"));
key.put("Subject", new AttributeValue("Transactions?"));
// 未コミットのリードがトランザクションマネージャに起こり、トランザクション上にありません。
Map<String, AttributeValue> item = txManager.getItem(new GetItemRequest()
.withKey(key)
.withTableName("Thread"),
IsolationLevel.COMMITTED).getItem();
その他のコードの例は、トランザクションライブラリと一緒に配布されているREADMEファイルで提供されています。
1つのトランザクションは、1つ以上のDynamoDBテーブルにまたがることができます。トランザクションPutは(リード、ライトキャパシティの観点で)よりコストがかかります。 他のどのputとも競合しないputは 7N + 4 ライトで実行されることが期待できます。(Nはトランザクションのリクエストの数)
今すぐはじめましょう
このライブラリを使うには、AWS SDK for Javaをダウンロードし、AWS Labs GitHub リポジトリからDynamoDBのトランザクションライブラリをpullしてきます。ドキュメントをひととおり読んだら、コーディングスタートです!