Python(boto3)での DynamoDB TransactWriteItems
トランザクションは、boto3 の 2 つの API が最も激しく分かれる場面の 1 つです。transact_write_items は低レベルクライアントにしか存在しないので、Table が与えてくれるネイティブ Python 型の便利さはここでは使えません。そしてトランザクションが失敗したとき、必要なものは、boto3 のコードがまず覗かない例外の片隅にあります。(トランザクションが何を買ってくれるのかは、どの SDK でも同じです。)
コード
import boto3
client = boto3.client("dynamodb")
# Move one award between two songs — atomically. If the first song has no
# award to give, NEITHER update happens.
try:
client.transact_write_items(
TransactItems=[
{
"Update": {
"TableName": "Music",
"Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
"UpdateExpression": "SET #upd0 = #upd0 - :one",
"ConditionExpression": "#upd0 >= :one",
"ExpressionAttributeNames": {"#upd0": "Awards"},
"ExpressionAttributeValues": {":one": {"N": "1"}},
}
},
{
"Update": {
"TableName": "Music",
"Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "A Mis Abuelos"}},
"UpdateExpression": "SET #upd0 = if_not_exists(#upd0, :zero) + :one",
"ExpressionAttributeNames": {"#upd0": "Awards"},
"ExpressionAttributeValues": {":one": {"N": "1"}, ":zero": {"N": "0"}},
}
},
]
)
print("Transaction committed")
except client.exceptions.TransactionCanceledException as e:
# One reason per action, in TransactItems order. Code "None" means that
# action was fine — some OTHER action sank the transaction.
codes = [reason["Code"] for reason in e.response["CancellationReasons"]]
print(f"Transaction canceled: {codes}") # e.g. ['ConditionalCheckFailed', 'None']解説
TransactItems—Put、Update、Delete、ConditionCheckの dict のリストで、すべての値が DynamoDB JSON、例外はありません。型付きの形が任意でない唯一の boto3 呼び出しがこれであり、このページの末尾のセクションが存在する理由でもあります。上限は CLI のページにあります。CancellationReasonsはErrorの中にはありません。botocore はモデル化されたエラーフィールドをレスポンス dict のトップに持ち上げるので、捕捉した例外が携えるe.responseにはCancellationReasons、Error、Message、ResponseMetadataが横並びで入っています。e.response["Error"]の下を探しても何も見つかりません。e.response["Error"]が保持しているのは要約のコードとメッセージだけです。Noneのエントリに"Message"はありません — 成功したアクションの理由はキー 1 つの dict{"Code": "None"}なので、自然に書いた[r["Message"] for r in reasons]は、よりによって成功したアクションでKeyError: 'Message'を送出します。r.get("Message")を使いましょう。- 生成された例外クラス — botocore は
client.exceptions.TransactionCanceledExceptionをサービスモデルから実行時に構築します。だからそれはクライアントインスタンスにぶら下がっており、from botocore.exceptions import ...できないのです。クライアントがスコープに無いヘルパーでは、botocore.exceptions.ClientErrorを捕捉してe.response["Error"]["Code"]で分岐しましょう。生成されたクラスはそのサブクラスです。 - 構造的な誤りはキャンセルとしては届かない ので、スニペットの
except節はそれらを一度も見ません。同じアイテムを狙った 2 つのアクションは、コードがValidationExceptionでe.responseにCancellationReasonsキーを持たない素のClientErrorを送出します。どのアクションも実行される前にトランザクションが拒否されたからです。それらも同じ文脈でログに残したいなら、外側でClientErrorを捕捉しましょう。 - アクションに付けた
ReturnValuesOnConditionCheckFailure: "ALL_OLD"は、そのアクションの理由のItemキーの下に、負けたアイテムを DynamoDB JSON で入れてくれます。競り負けたあとの追加のget_itemが要らなくなります。 ClientRequestTokenは boto3 が埋めてくれます。ワイヤ上で捕捉したところ、同一内容のtransact_write_items呼び出し 2 回は別々の UUID を持って出ていきました。つまりトークンがカバーするのは単一の呼び出しであって、自前の catch-and-retry ループではありません。リトライがプロセスより長生きしうるなら、安定したトークンを自分で渡しましょう。TransactionConflictではリトライし、ConditionalCheckFailedでは決してリトライしない — 前者は他の誰かが一瞬アイテムを握っていたと告げ、後者は前提条件が偽であり次回も偽のままだと告げます。たいていのハンドラーが区別する必要があるのはこの 2 つのコードだけで、全体は TransactionCanceledException のページで読み解いています。- コスト — トランザクション書き込みは、同じ書き込みをトランザクションの外で行う場合のおよそ 2 倍を課金します(CLI のページで計測しています)。単一アイテムのアトミック性だけが必要なら、条件付き書き込みがそれを半額で買ってくれます。
これにリソース API 版はありません
boto3.resource("dynamodb").Table(...) に transact_write_items 属性はありません。持っているのは resource.meta.client だけです。したがって Table とネイティブ Python 型に落ち着いたコードベースも、トランザクションのためには型付きの DynamoDB JSON に戻るか、boto3.dynamodb.types.TypeSerializer で手作業でシリアライズするしかありません。
from boto3.dynamodb.types import TypeSerializer
serialize = TypeSerializer().serialize
values = {k: serialize(v) for k, v in {":one": 1, ":zero": 0}.items()}TypeSerializer はリソース API と同じルールを適用します。つまり float を拒否し、小数には decimal.Decimal を求めます。DynamoDB JSON コンバーターは、リテラルをスクリプトに貼りたいだけのときに、同じ変換をブラウザーで行います。トランザクションが触れるアイテムを、どちらの形も手書きせずに編集するには、DynoTable をダウンロードしてください。
関連する例
- Node.js での DynamoDB TransactWriteItems — AWS SDK v3 による同じトランザクション。
- AWS CLI での DynamoDB TransactWriteItems — シェルからの同じトランザクション。
- Python での DynamoDB 条件付き書き込み — 2 倍のコストなしの単一アイテムのアトミック性。
- DynamoDB のトランザクション — 分離性、べき等性、そしてトランザクションが割に合う場面。
- DynamoDB TransactionCanceledException — すべてのキャンセル理由コードを読み解きます。
- "Too many actions in a TransactWriteItems call" — 100 アクションと 4 MB のトランザクション上限。
- "Transaction request cannot include multiple operations on one item" — 1 トランザクションにつき、1 アイテムに 1 アクション。
参考資料
- TransactWriteItems — Amazon DynamoDB API Reference
- DynamoDB.Client.transact_write_items — Boto3 documentation
- Amazon DynamoDB transactions: how it works — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
最終検証日 2026-07-28、上記にリンクした公式 AWS ドキュメントに照らして確認しました。