データテーブルでローカルトランザクションを有効化した後、指定されたパーティションキー値に基づいてローカルトランザクションを作成し、そのトランザクション内でデータの読み取りおよび書き込み操作を行います。ローカルトランザクションでは、1 行以上に対するアトミックな読み取りおよび書き込み操作がサポートされます。
シナリオ
ローカルトランザクション機能により、1 行以上のデータに対してアトミックな読み取りおよび書き込み操作を実行できます。以下に、代表的な利用シナリオを示します。
シンプルなシナリオ:データの読み取りと書き込み
以下の 2 つの方法を用いて、読み取り・変更・書き込み(RMW)操作を実行できます。各方法には特定の制限があります。
-
条件付き更新:1 回のリクエストにつき、単一の行のみを処理できます。複数行を対象とするリクエストや、複数の書き込み操作を含むリクエストには使用できません。詳細については、「条件付き更新」をご参照ください。
-
アトミックカウンター:1 回のリクエストにつき、単一の行のみを処理でき、列値の増分のみをサポートします。詳細については、「アトミックカウンター機能の使用」をご参照ください。
上記の制限を解消するには、指定されたパーティションキー値の範囲内で RMW 操作を実行できるよう、ローカルトランザクションを作成します。
-
StartLocalTransaction 操作を呼び出して、指定されたパーティションキー値に基づくローカルトランザクションを作成し、ローカルトランザクション ID を取得します。
-
GetRow または GetRange 操作を呼び出してデータを読み取ります。このリクエストにはローカルトランザクション ID を含める必要があります。
-
クライアント側でデータを変更します。
-
PutRow、UpdateRow、DeleteRow、または BatchWriteRow 操作を呼び出して、変更後のデータを書き戻します。このリクエストにはローカルトランザクション ID を含める必要があります。
-
CommitTransaction 操作を呼び出して、ローカルトランザクションを確定します。
複雑なシナリオ:メールボックス
特定のユーザーのメールに対してアトミックな操作を実行するために、ローカルトランザクションを作成します。
ローカルトランザクション機能を正しく使用するには、データテーブル上に 2 つのインデックステーブルを作成する必要があります。以下の表に、これらのテーブルのプライマリキー列を示します。表中の「Type」列は、データテーブルとインデックステーブルを区別します。各インデックス行では、「IndexField」列を使用して特定の意味を持つフィールドを格納しますが、データテーブルには「IndexField」列は存在しません。
|
テーブル |
プライマリキー列 |
|||
|
UserID |
Type |
IndexField |
MailID |
|
|
データテーブル |
User ID |
"Main" |
N/A |
Email ID |
|
フォルダインデックステーブル |
User ID |
"Folder" |
$Folder |
Email ID |
|
送信時刻インデックステーブル |
User ID |
"SendTime" |
$SendTime |
Email ID |
具体的には、ローカルトランザクション機能を用いて、メールに対して以下の操作を実行できます。
シナリオ 1:ユーザーが送信した直近の 100 通のメールを一覧表示
-
ユーザー ID を使用してローカルトランザクションを作成し、ローカルトランザクション ID を取得します。
-
GetRange 操作を呼び出して、送信時刻インデックステーブルから 100 通のメールをクエリします。このリクエストにはローカルトランザクション ID を含める必要があります。
-
BatchGetRow 操作を呼び出して、データテーブルから該当する 100 通のメールの詳細情報をクエリします。このリクエストにはローカルトランザクション ID を含める必要があります。
-
CommitTransaction 操作を呼び出してローカルトランザクションを確定するか、AbortTransaction 操作を呼び出してローカルトランザクションを中止します。
シナリオ 2:フォルダ内のすべてのメールを別のフォルダに転送
-
ユーザー ID を使用してローカルトランザクションを作成し、ローカルトランザクション ID を取得します。
-
GetRange 操作を呼び出して、フォルダインデックステーブルからメールをクエリします。このリクエストにはローカルトランザクション ID を含める必要があります。
-
BatchWriteRow 操作を呼び出して、フォルダインデックステーブルに対して書き込み操作を行います。このリクエストにはローカルトランザクション ID を含める必要があります。
メールを転送する際には、1 回の書き込み操作で 2 行が処理されます。具体的には、元のフォルダを示す行がフォルダインデックステーブルから削除され、新しいフォルダを示す行がフォルダインデックステーブルに追加されます。
-
CommitTransaction 操作を呼び出して、ローカルトランザクションを確定します。
シナリオ 3:フォルダ内の既読メールおよび未読メールの件数をカウント
-
ユーザー ID を使用してローカルトランザクションを作成し、ローカルトランザクション ID を取得します。
-
GetRange 操作を呼び出して、フォルダインデックステーブルからメールをクエリします。このリクエストにはローカルトランザクション ID を含める必要があります。
-
BatchGetRow 操作を呼び出して、データテーブルから各メールの既読状態をクエリします。
-
CommitTransaction 操作を呼び出してローカルトランザクションを確定するか、AbortTransaction 操作を呼び出してローカルトランザクションを中止します。
このソリューションは最適ではありません。このシナリオでは、クエリを高速化するためにさらにインデックステーブルを追加することを推奨します。ローカルトランザクション機能は、データテーブルとインデックステーブル間の状態整合性を保証するため、開発が簡素化されます。たとえば、このメール件数カウントのソリューションでは多数のメールを読み取る必要があり、オーバーヘッドが高くなります。オーバーヘッドを低減し、クエリを高速化するには、既読および未読メールの件数を格納する新しいインデックステーブルを使用してください。
事前準備
データテーブルを作成する際に、ローカルトランザクションを有効化する必要があります。Tablestore コンソール、Tablestore SDK for Java V5.11.0 以降、または最新版の Tablestore SDK for Go を使用してください。詳細については、「データテーブルの作成」をご参照ください。
既存のデータテーブルでローカルトランザクションが必要であるものの、テーブル作成時にこの機能を有効化しなかった場合は、チケットを送信してください。また、DingTalk グループ 36165029092(Tablestore 技術検討グループ-3)に参加して支援を受けることも可能です
注意事項
-
自動採番主キー列機能とローカルトランザクション機能を同時に使用することはできません。
-
ローカルトランザクション内の同時実行操作は、悲観的ロックによって制御されます。
-
ローカルトランザクションの有効期間は最大 60 秒です。
ローカルトランザクションが 60 秒以内に確定または中止されない場合、Tablestore サーバーはトランザクションがタイムアウトしたと判断し、自動的に中止します。
-
タイムアウトエラーが返された場合でも、Tablestore サーバー上でトランザクションが作成される可能性があります。この場合、作成されたトランザクションがタイムアウトした後に、再度トランザクション作成リクエストを送信できます。
-
ローカルトランザクションが確定されない場合、無効になることがあります。この場合、トランザクション内の操作を再試行してください。
-
ローカルトランザクション内のデータに対して書き込み操作が行われていない場合、確定操作と中止操作は同じ効果を持ちます。
-
Tablestore では、ローカルトランザクション内のデータに対する読み取りおよび書き込み操作に以下の制限が課されます。
-
ローカルトランザクション ID は、トランザクション作成時に使用したパーティションキー値に基づいて指定された範囲を超えてデータにアクセスするために使用できません。
-
同一トランザクション内のすべての書き込みリクエストのパーティションキー値は、トランザクション作成時に使用したパーティションキー値と同じである必要があります。この制限は読み取りリクエストには適用されません。
-
ローカルトランザクションは、一度に 1 つのリクエストでのみ使用できます。トランザクションが使用中の場合、同じローカルトランザクション ID を使用する他の操作は失敗します。
-
ローカルトランザクション内のデータに対する連続する読み取りまたは書き込み操作の最大間隔は 60 秒です。
ローカルトランザクション内のデータに対して 60 秒以上読み取りまたは書き込み操作が行われない場合、Tablestore サーバーはトランザクションがタイムアウトしたと判断し、自動的に中止します。
-
各トランザクションへの書き込み可能データ量の上限は 4 MB です。各トランザクションへの書き込みデータ量は、通常の書き込みリクエストと同様に計算されます。
-
セルにバージョン番号が指定されていない場合、Tablestore サーバーは、トランザクションへの書き込み時に通常通りセルにバージョン番号を自動的に割り当てます(トランザクション確定時ではなく)。
-
BatchWriteRow リクエストにローカルトランザクション ID が含まれている場合、リクエスト内のすべての行は、ローカルトランザクション ID に対応するテーブルにのみ書き込まれます。
-
ローカルトランザクションを使用すると、トランザクション作成時に指定したパーティションキー値のデータに対して書き込みロックが設定されます。ローカルトランザクション ID を含む書き込みリクエストのみが成功し、それ以外の非トランザクション型リクエストや、他のローカルトランザクション ID を含む書き込みリクエストは失敗します。トランザクションが確定または中止された場合、あるいはタイムアウトした場合、ローカルトランザクション内のデータのロックは解除されます。
-
ローカルトランザクション ID を含む読み取りまたは書き込みリクエストが拒否された場合でも、ローカルトランザクションは有効なままです。リクエストを再送信するためのリトライルールを指定するか、トランザクションを中止してください。
-
操作手順
ローカルトランザクションの管理は、Tablestore SDK のみで行ってください。
ローカルトランザクションを利用するには、まずパーティションキー値に基づいてローカルトランザクションを作成し、次にトランザクション内でデータの読み取りおよび書き込みを行い、最後に必要に応じてトランザクションを確定または中止します。以下の例では、Tablestore SDK for Java を使用しています。
ローカルトランザクション機能を用いたデータ行の書き込み
以下のサンプルコードは、テーブル内の指定されたパーティションキー値に基づいてローカルトランザクションを作成し、そのローカルトランザクション内でデータ行を書き込む方法を示しています。
private static void transactionPutRow(SyncClient client) {
// データテーブルの名前を指定します。
String tableName="<TABLE_NAME>";
// 指定されたパーティションキー値に基づいてローカルトランザクションを作成します。
PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
// パーティションキーの列名、データの型、および列値を指定します。
primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("pkvalue"));
PrimaryKey primaryKey = primaryKeyBuilder.build();
// ローカルトランザクション作成リクエストを構築します。
StartLocalTransactionRequest request = new StartLocalTransactionRequest(tableName, primaryKey);
// ローカルトランザクション作成リクエストを送信し、ローカルトランザクション ID を取得します。
String txnId = client.startLocalTransaction(request).getTransactionID();
// ローカルトランザクション内でデータ行を書き込みます。
// 行のプライマリキー情報を設定します。
PrimaryKeyBuilder primaryKeyBuilder1 = PrimaryKeyBuilder.createPrimaryKeyBuilder();
// プライマリキーの列名、データの型、および列値を指定します。テーブルのプライマリキーが複数のプライマリキー列で構成されている場合、プライマリキー列の情報を順に設定します。
primaryKeyBuilder1.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("pkvalue"));
primaryKeyBuilder1.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong(10001));
PrimaryKey primaryKey1 = primaryKeyBuilder1.build();
// 行の属性列を設定します。
RowPutChange rowPutChange = new RowPutChange(tableName, primaryKey1);
// 各属性列の列名、データの型、および列値を指定します。必要に応じて、さらに属性列を追加します。
rowPutChange.addColumn(new Column("col1", ColumnValue.fromString("colvalue")));
rowPutChange.addColumn(new Column("col2", ColumnValue.fromLong(10)));
PutRowRequest request1 = new PutRowRequest(rowPutChange);
// ローカルトランザクション ID をリクエストに渡します。
request1.setTransactionId(txnId);
client.putRow(request1);
// ローカルトランザクションを確定または中止します。
// ローカルトランザクションを確定して、ローカルトランザクション内のすべてのデータ変更を有効にします。
CommitTransactionRequest commitRequest = new CommitTransactionRequest(txnId);
client.commitTransaction(commitRequest);
// ローカルトランザクションを中止して、ローカルトランザクション内のすべてのデータ変更を無効にします。
//AbortTransactionRequest abortRequest = new AbortTransactionRequest(txnId);
//client.abortTransaction(abortRequest);
}
ローカルトランザクション機能を用いたデータ行の読み取り
以下のサンプルコードは、テーブル内の指定されたパーティションキー値に基づいてローカルトランザクションを作成し、そのローカルトランザクション内でデータ行を読み取る方法を示しています。
private static void transactionGetRow(SyncClient client) {
// データテーブルの名前を指定します。
String tableName="exampletabled";
// 指定されたパーティションキー値に基づいてローカルトランザクションを作成します。
PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
// パーティションキーの列名、データの型、および列値を指定します。
primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("111"));
PrimaryKey primaryKey = primaryKeyBuilder.build();
// ローカルトランザクション作成リクエストを構築します。
StartLocalTransactionRequest request = new StartLocalTransactionRequest(tableName, primaryKey);
// ローカルトランザクション作成リクエストを送信し、ローカルトランザクション ID を取得します。
String txnId = client.startLocalTransaction(request).getTransactionID();
// ローカルトランザクション内でデータ行を読み取ります。
// 行のプライマリキー情報を設定します。
PrimaryKeyBuilder primaryKeyBuilder1 = PrimaryKeyBuilder.createPrimaryKeyBuilder();
// プライマリキーの列名、データの型、および列値を指定します。テーブルのプライマリキーが複数のプライマリキー列で構成されている場合、プライマリキー列の情報を順に設定します。
primaryKeyBuilder1.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("111"));
primaryKeyBuilder1.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong(10001));
PrimaryKey primaryKey1 = primaryKeyBuilder1.build();
SingleRowQueryCriteria criteria = new SingleRowQueryCriteria(tableName, primaryKey1);
// 最新バージョンのデータを読み取ります。
criteria.setMaxVersions(1);
GetRowRequest request1 = new GetRowRequest(criteria);
// ローカルトランザクション ID をリクエストに渡します。
request1.setTransactionId(txnId);
GetRowResponse getRowResponse = client.getRow(request1);
// ローカルトランザクションを確定または中止します。読み取り操作の場合、確定または中止は同じ効果を持ちます。
// ローカルトランザクションを確定して、ローカルトランザクション内のすべてのデータ変更を有効にします。
CommitTransactionRequest commitRequest = new CommitTransactionRequest(txnId);
client.commitTransaction(commitRequest);
// ローカルトランザクションを中止して、ローカルトランザクション内のすべてのデータ変更を無効にします。
//AbortTransactionRequest abortRequest = new AbortTransactionRequest(txnId);
//client.abortTransaction(abortRequest);
// データ行を取得して表示します。
Row row = getRowResponse.getRow();
System.out.println("データ行は以下のように読み取られ、表示されます:");
System.out.println(row);
}
SDK 統合
以下の SDK を使用してローカルトランザクションを利用できます。
課金
-
StartLocalTransaction、CommitTransaction、および AbortTransaction 操作それぞれについて、1 つの書き込み容量単位(CU)が消費されます。
-
読み取りおよび書き込み操作の課金は、標準の読み取りおよび書き込みリクエストと同様です。課金に関する詳細については、「課金の概要」をご参照ください。
エラーコード
|
エラーコード |
説明 |
|
OTSRowOperationConflict |
パーティションキー値が既に他のローカルトランザクションによって占有されています。 |
|
OTSSessionNotExist |
指定されたトランザクション ID に対応するトランザクションが存在しないか、無効またはタイムアウトしています。 |
|
OTSSessionBusy |
トランザクションに対する直前のリクエストが完了していません。 |
|
OTSOutOfTransactionDataSizeLimit |
トランザクション内のデータ量が上限を超えています。 |
|
OTSDataOutOfRange |
データ操作が、トランザクション作成時に使用したパーティションキー値で指定された範囲を超えています。 |