近年、NoSQL データベースをリレーショナルデータベース管理システム (RDBMS) の制約から解放するソリューションと見なす組織が増えているため、NoSQL データベースの使用が大幅に増加しています。NoSQL データベースの柔軟性、俊敏性、およびパフォーマンスは、移行をトリガーする主な利点ですが、組織の重要な要件の一部によって RDBMS は同じままでした。
RDMBS は、最も広く知られ議論されている機能であるトランザクションサポートのために、重要なデータを操作する場合 NoSQL データベースよりも好まれます。
Amazon DynamoDB Transactions 使用する理由
多くのアプリケーションでは、1 つ以上の項目に対して「アトミック」またはオールオアナッシングのデータベース操作を必要とするビジネスロジックが常に必要です。これにより、間違った操作が 1 つ発生した場合、すべてのデータベース操作をロールバックできます。通常、このニーズに対処しようとすると、接続されたすべての操作を追跡し、操作を元に戻すという点で、アプリケーションの実装が難しくなります。
Transactions は、re:Invent 2018 で DynamoDB の新機能として発表されました。データベース内のトランザクションのネイティブサポートを提供し、単一の AWS アカウントとリージョンにある複数の項目に ACID (原子性、一貫性、分離性、耐久性) を提供します。
従来、DynamoDB は単一の項目に対してのみ、これらのプロパティをサポートしていました。データの可用性と耐久性を確保するために設計されましたからです。Transactions は、複数の項目で 1 つ以上のテーブルに原子性 (オールオアナッシング) と分離性 (Transactions 同士は影響なし) を追加しました。
さらに、Transactions の ClientRequestToken キーを使用すると、API 呼び出しがべき等になる可能性があるため、複数の同一の呼び出しが同じ操作を再生せず、呼び出しが 1 つの場合と同じ効果が得られます。
Transactions を使用すると、データベース内でロールバック操作について心配したり、苦労したりする必要がなくなります。Transactions は、複数の項目とテーブルにわたってアクションを調整することにより、データの整合性を維持するのに役立ちます。
仕組みの説明
DynamoDB Transactions は現在、TransactWriteItems および TransactGetItems と呼ばれる 2 つの主要な API アクションを提供します。複数のアクションを組み合わせて、単一のオールオアナッシング操作として送信できます。
たとえば、ホテルの予約管理アプリケーションをリレーショナルデータベースから DynamoDB に移動することにしました。次の図は、ER 構造を表しています。図には、宿泊客、予約、部屋の 3 つのエンティティがあります。宿泊客と予約の関係は多対多で、予約と部屋の関係も多対多になります。
ここでレガシーテーブルのアプローチを適用し、3 つのレガシーテーブルを 1 つの DynamoDB テーブルにマッピングします。サンプルデータを使用した DynamoDB のホテル予約データモデルを次のテーブルに示します。
HotelManagement |
記録タイプ |
ID (PK) |
属性 |
宿泊客 |
“John” |
ActiveReservations : { “501” } OccupiesRooms : { “20014” } |
予約 |
“501” |
GuestId: “John” ReservationStatus: “FULFILLED” FulfilledByRoom: “20014” |
部屋 |
“R20014” |
RoomStatus: “OCCUPIED” RentedToReservation : “501” |
サンプルコード
宿泊客は、予約の作成、チェックイン、チェックアウトの 3 種類の操作を実行できます。この投稿では、次の図に示すように、3 つの異なる Java メソッドにラップされた 3 つのシナリオを取り上げています。これによって、DynamoDB のトランザクション機能を示します。 ユーザー名の図では、3 つの操作を順番に実行できます。1 つ目は予約の作成、2 つ目はチェックイン、3 つ目はチェックアウトです。
まず、作業するテーブルを作成する必要があります。
private static void createTable(String tableName) {
try {
// 文字列を保持する 'Id' という名前のプライマリハッシュキーを持つテーブルを作成します
CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(tableName)
.withKeySchema(new KeySchemaElement().withAttributeName("Id").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Id").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
// テーブルがまだ存在しない場合は作成します
TableUtils.createTableIfNotExists(dynamoDB, createTableRequest);
// テーブルが ACTIVE 状態になるのを待ちます
TableUtils.waitUntilActive(dynamoDB, tableName);
// 新しいテーブルについて記述します
DescribeTableRequest describeTableRequest = new DescribeTableRequest().withTableName(tableName);
TableDescription tableDescription = dynamoDB.describeTable(describeTableRequest).getTable();
System.out.println("Table Created Successfully.Table Description: " + tableDescription);
} catch (InterruptedException e) {
System.out.println("Occupied thread is interrupted");
}
}
予約を作成する
次に、システム内のサンプル予約を表すレコードのセットを作成します。
private static void createReservation() {
// 宿泊客の項目を作成します
HashMap<String, AttributeValue> guestItem = new HashMap<String, AttributeValue>();
guestItem.put("Id", new AttributeValue("John"));
guestItem.put("ActiveReservations", new AttributeValue("500"));
// 部屋の項目を作成します
HashMap<String, AttributeValue> roomItem = new HashMap<String, AttributeValue>();
roomItem.put("Id", new AttributeValue("R20014"));
roomItem.put("RoomStatus", new AttributeValue("FREE"));
// 予約の項目を作成します
HashMap<String, AttributeValue> reservationItem = new HashMap<String, AttributeValue>();
reservationItem.put("Id", new AttributeValue("500"));
reservationItem.put("GuestId", new AttributeValue("John"));
reservationItem.put("ReservationStatus", new AttributeValue("PENDING"));
Put createGuest = new Put().withTableName(TABLE_NAME).withItem(guestItem);
Put createRoom = new Put().withTableName(TABLE_NAME).withItem(roomItem);
Put createReservation = new Put().withTableName(TABLE_NAME).withItem(reservationItem);
Collection<TransactWriteItem> actions = Arrays.asList(
new TransactWriteItem().withPut(createGuest),
new TransactWriteItem().withPut(createRoom),
new TransactWriteItem().withPut(createReservation));
TransactWriteItemsRequest createReservationTransaction = new TransactWriteItemsRequest()
.withTransactItems(actions)
.withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL);
// トランザクションを実行し、結果を処理します。
// 次のコードスニペットは、前に定義したアクションを単一のオールオアナッシング操作として実行する方法を示しています
dynamoDB.transactWriteItems(createReservationTransaction);
System.out.println("Create Reservation transaction is successful");
}
予約を作成操作後のテーブルを以下に示します。
HotelManagement |
記録タイプ |
ID (PK) |
属性 |
宿泊客 |
“John” |
ActiveReservations : { “500”} OccupiesRooms : null |
予約 |
“500” |
GuestId: “John” ReservationStatus: “PENDING” FulfilledByRoom: null |
部屋 |
“R20014” |
RoomStatus: “FREE” RentedToReservation : null |
チェックイン
次の操作はチェックインです。このトランザクション中に、テーブル内の 3 つの項目 (宿泊客、予約、部屋) がすべて更新されます。
private static void checkIn() {
//updateGuest オブジェクトを作成します
HashMap<String, AttributeValue> guestItemKey = new HashMap<String, AttributeValue>();
guestItemKey.put("Id", new AttributeValue("John"));
HashMap<String, AttributeValue> guestExpressionAttributeValues = new HashMap<String, AttributeValue>();
guestExpressionAttributeValues.put(":occupies_rooms", new AttributeValue("R20014"));
Update updateGuest = new Update()
.withTableName(TABLE_NAME)
.withKey(guestItemKey)
.withUpdateExpression("SET OccupiesRooms = :occupies_rooms")
.withExpressionAttributeValues(guestExpressionAttributeValues);
//updateRoom オブジェクトを作成します
HashMap<String, AttributeValue> roomItemKey = new HashMap<String, AttributeValue>();
roomItemKey.put("Id", new AttributeValue("R20014"));
HashMap<String, AttributeValue> roomExpressionAttributeValues = new HashMap<String, AttributeValue>();
roomExpressionAttributeValues.put(":room_status", new AttributeValue("OCCUPIED"));
roomExpressionAttributeValues.put(":rented_to_reservation", new AttributeValue("500"));
Update updateRoom = new Update()
.withTableName(TABLE_NAME)
.withKey(roomItemKey)
.withUpdateExpression("SET RoomStatus = :room_status, RentedToReservation = :rented_to_reservation")
.withExpressionAttributeValues(roomExpressionAttributeValues);
//updateReservation オブジェクトを作成します
HashMap<String, AttributeValue> reservationItemKey = new HashMap<String, AttributeValue>();
reservationItemKey.put("Id", new AttributeValue("500"));
HashMap<String, AttributeValue> reservationExpressionAttributeValues = new HashMap<String, AttributeValue>();
reservationExpressionAttributeValues.put(":reservation_status", new AttributeValue("FULLFILLED"));
reservationExpressionAttributeValues.put(":fullfilled_by_room", new AttributeValue("R20014"));
Update updateReservation = new Update()
.withTableName(TABLE_NAME)
.withKey(reservationItemKey)
.withUpdateExpression("SET ReservationStatus = :reservation_status, FullfilledByRoom = :fullfilled_by_room")
.withExpressionAttributeValues(reservationExpressionAttributeValues);
//Transactions の実行
Collection<TransactWriteItem> actions = Arrays.asList(
new TransactWriteItem().withUpdate(updateGuest),
new TransactWriteItem().withUpdate(updateRoom),
new TransactWriteItem().withUpdate(updateReservation));
TransactWriteItemsRequest createReservationTransaction = new TransactWriteItemsRequest()
.withTransactItems(actions)
.withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL);
dynamoDB.transactWriteItems(createReservationTransaction);
System.out.println("Check-in transaction is successful");
}
チェックイン操作後のテーブルを以下に示します。
HotelManagement |
記録タイプ |
ID (PK) |
属性 |
宿泊客 |
“John” |
ActiveReservations : { “500”} OccupiesRooms : { “R20014” } |
予約 |
“500” |
GuestId: “John” ReservationStatus: “FULFILLED” FulfilledByRoom: “20014” |
部屋 |
“R20014” |
RoomStatus: “OCCUPIED” RentedToReservation : “500” |
チェックアウト
最後の操作はチェックアウトです。null または空の属性は DynamoDB では許可されないため、一部の属性は削除されます。
private static void checkOut() {
//updateGuest オブジェクトを作成します
HashMap<String, AttributeValue> guestItemKey = new HashMap<String, AttributeValue>();
guestItemKey.put("Id", new AttributeValue("John"));
Update updateGuest = new Update()
.withTableName(TABLE_NAME)
.withKey(guestItemKey)
.withUpdateExpression("REMOVE OccupiesRooms, ActiveReservations");
//属性に値がないため、削除されます
//updateRoom オブジェクトを作成します
HashMap<String, AttributeValue> roomItemKey = new HashMap<String, AttributeValue>();
roomItemKey.put("Id", new AttributeValue("R20014"));
HashMap<String, AttributeValue> roomExpressionAttributeValues = new HashMap<String, AttributeValue>();
roomExpressionAttributeValues.put(":room_status", new AttributeValue("FREE"));
Update updateRoom = new Update()
.withTableName(TABLE_NAME)
.withKey(roomItemKey)
.withUpdateExpression("SET RoomStatus = :room_status REMOVE RentedToReservation") //Since there is no value in the attribute, it is removed
.withExpressionAttributeValues(roomExpressionAttributeValues);
//updateReservation オブジェクトを作成します
HashMap<String, AttributeValue> reservationItemKey = new HashMap<String, AttributeValue>();
reservationItemKey.put("Id", new AttributeValue("500"));
HashMap<String, AttributeValue> reservationExpressionAttributeValues = new HashMap<String, AttributeValue>();
reservationExpressionAttributeValues.put(":reservation_status", new AttributeValue("CLOSED"));
Update updateReservation = new Update()
.withTableName(TABLE_NAME)
.withKey(reservationItemKey)
.withUpdateExpression("SET ReservationStatus = :reservation_status")
.withExpressionAttributeValues(reservationExpressionAttributeValues);
//Transactions の実行
Collection<TransactWriteItem> actions = Arrays.asList(
new TransactWriteItem().withUpdate(updateGuest),
new TransactWriteItem().withUpdate(updateRoom),
new TransactWriteItem().withUpdate(updateReservation));
TransactWriteItemsRequest createReservationTransaction = new TransactWriteItemsRequest()
.withTransactItems(actions)
.withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL);
dynamoDB.transactWriteItems(createReservationTransaction);
System.out.println("Check-out transaction is successful");
}
チェックアウト操作後のテーブルを以下に示します。
HotelManagement |
記録タイプ |
ID (PK) |
属性 |
宿泊客 |
“John” |
ActiveReservations : {} |
予約 |
“500” |
GuestId: “John” ReservationStatus: “CLOSED” FulfilledByRoom: “20014” |
部屋 |
“R20014” |
RoomStatus: “FREE” |
まとめ
DynamoDB トランザクション API は、追加費用なしで DynamoDB テーブルの項目に ACID を提供することにより、開発者のエクスペリエンスを簡素化します。Transactions は単一リージョン DynamoDB テーブルではデフォルトですべて有効に、グローバルテーブルではオプションで無効に設定されています。接続されたデータベース操作に対する長年のニーズに対応し、DynamoDB のスケール、パフォーマンス、およびエンタープライズに対応する利点をより広範囲のワークロードセットに拡張します。
Transactions の詳細については、Amazon DynamoDB 開発者ガイドをご覧ください。
著者について
Baris Yasin は、アマゾン ウェブ サービスのシニアソリューションアーキテクトです。
Serdar Nevruzoglu はアマゾン ウェブ サービスのソリューションアーキテクトです。