【AWS】Step Functionsを使ってDynamoDBテーブルを操作してみよう!【ハンズオン】
こんにちは!開発統括部のAKIHISAです!
DynamoDBテーブルの操作はLambdaやEC2、CLIをはじめとした様々なサービスから行うことができます。
Step Functionsからも操作を行うことができ、コンピューティングサービスを使わずお手軽にDynamoDBを扱うことができるんです!
やってみたら簡単だったのでナレッジを共有します。
本記事ではハンズオン形式にてStep FunctionsからDynamoDBテーブルの以下の操作を実践します。
- Scan
- GetItem
- PutItem
- UpdateItem
- DeleteItem
- Query
Step Functionsをガチャガチャ触っているだけでも楽しいので、是非試してみてください!
Step Functionsとは?
AWS Step Functionsとは、様々なプロセスを一つの「ワークフロー」として管理・実行することができるサービスです。
プロセス一つ一つを「ステート」といい、ステートの状態によって操作が変わるワークフローを定義している「ステートマシン」と呼ばれるリソースによってワークフローを「実行」しています。
ステートマシンに定義されたワークフローは、下図のように視覚的にわかりやすく表現されたものが生成されます。
ステートマシンは同時に複数の実行を行うことが可能です。
渡すパラメータを変えたりワークフロー内の条件が変わったりした場合でも、一連のワークフローを独立して実行することができ互いの結果には影響を及ぼしません。
ここまで説明した「ステート」「ワークフロー」「ステートマシン」「実行」を整理すると下図のようになります。
Step Functionsステートマシンのセットアップ
ステートマシンの作成
Step Functionsの概要がわかったところでハンズオンで使用するステートマシンを作成していきます。
Step Functionsコンソールを開き、左ペインから [ステートマシン] を選択します。
ステートマシンの一覧が表示されますので、ステートマシンの作成 を選択します。
ステートマシンの作成方法を聞かれますが、ワークフローを視覚的に作成できるWorkflow Studioを使って進めていきます。
最初から選択されているのでそのまま [次へ] を選択します。
Workflow Studioが開かれました。
ここでタスクや選択、待機等、様々なステートを駆使してワークフローを作成します。
まずは一番簡素なワークフローを作成していくため左ペインから [フロー] タブを選択します。
フローに分類されるステートの一覧が表示されます。
ステートの分類
ステートを選択する画面では、アクション・フロー・パターンの3つの分類に分かれています。
「アクション」に分類されるステートは、ステートマシン上で何らかの作業を行う「タスク」と呼ばれるステートです。主にLambdaやその他AWSサービスのAPIを呼び出し、ワークフロー内で処理を実行します。「フロー」に分類されるステートは、タスクステート以外のステートです。「Pass」「選択」「待機」「成功」「失敗」「並行」「マッピング」ステートが存在します。処理の分岐や並列処理等、柔軟なワークフローを構築するために必要な要素です。
「パターン」はステートではなく、特定の目的に沿ってステートを組み合わせたものです。例えばS3 object keysパターンでは、S3バケット内のオブジェクトに対してLambdaを呼び出して一括処理を行う一連の処理が定義されています。
参考: https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/concepts-states.html
ワークフローが空の状態だとステートマシンが作成できないので、何もしない処理であるPassステートをワークフローに投入します。
左ペインからPassステートをドラッグ&ドロップでワークフロー内に移動します。
ワークフロー内にPassステートが設定されました。
Passステートのみ設定された状態でステートマシンを作成するため [次へ] を選択します。
生成されたコードとしてASL(Amazon States Language)に変換されます。
先ほどはビジュアルエディタでワークフローを描きましたが、Step Functions内部ではASLに変換された設定を読み込んで処理を行っています。
ASLを直接編集することでもワークフローを編集することができます。
生成されたASL
{ "Comment": "A description of my state machine", "StartAt": "Pass", "States": { "Pass": { "Type": "Pass", "End": true } } }
生成されたASLはこれで問題ないため [次へ] を選択してください。
ステートマシンの各種設定が表示されます。
今回はデフォルトで問題ないので、ステートマシン名に好きな名前(DynamodbOperateStateMachine)を入力してからスクロールし [ステートマシンの作成] を選択します。
ステートマシンが正常に作成されました。
ステートマシンの実行
作成したステートマシンを実行するため [実行を開始] を選択します。
ステートマシンへの入力を指定して実行する画面が表示されました。
各ステートへの入出力はJSON形式のペイロード(payload)で管理されており、入力欄で指定した値が最初のステートに渡されます。
今回はPassステートのみ設定しており、入力は特に使用していないためそのまま [実行を開始] を選択します。
実行詳細画面へ遷移し、実行ステータスを見ると成功していることがわかります。
少し下にスクロールするとグラフビューエリアが現れ、実行されたステートマシンのワークフローが表示されています。
ワークフロー内のステートに対してわかりやすく色がついており、Passステートが成功しています。
ステートの結果確認
ステートの結果欄に表示される各項目について説明します。
ステートの結果を確認するためにPassステートを選択します。
入力
実行時にステートがペイロードから受け取った入力を確認できます。
Passステートはワークフローの最初のステートなので、実行時の入力をペイロードから受け取っています。
出力
実行時にステートが処理した結果をペイロードに渡します。
ペイロードから受け取った値や新しい値を出力に追加することもできます。
詳細は公式リファレンスのパラメータセクションをご確認ください。
Passステートはデフォルトではペイロードから受け取った値をそのまま返却するので、実行時の入力がそのまま返ってきています。
詳細
実行時のステートの詳細を表します。
基本的には表示されているようなステータス、タイプ、開始後、期間が表示されますが、ステートのタイプによって項目は異なります。
期間にはステートの処理にかかった時間が表示されています。
Passステートは何もしない処理なので0が入っています。
定義
ASLの中から実行したステートの定義を取り出したものが表示されます。
ASL全体
{ "Comment": "A description of my state machine", "StartAt": "Pass", "States": { "Pass": { "Type": "Pass", "End": true } } }
ASL全体を見るとStates.Passを取り出されたものが表示されています。
イベント
実行時にステートの中で起こったイベントが記録されます。
今回の実行では、Passステートが開始されたイベント(PassStateEntered)、終了したイベント(PassStateExited)が表示されています。
DynamoDBテーブルのセットアップ
続いてステートマシンから操作されるDynamoDBテーブルを作成していきます。
DynamoDBコンソールの左ペインから [テーブル] を選択します。
テーブルの一覧が表示されるので [テーブルの作成] を選択します。
テーブルを作成するための設定を入力します。
好きなテーブル名(例: StepFunctionsOperatedTable)を入力し、パーティションキーには "id" 、型は [文字列] 型、ソートキーには "created_at"、型は [文字列] 型を設定します。
スクロールしてテーブル設定を [設定をカスタマイズ] に変更します。
すると設定項目が増えるので、下にスクロールして読み込み/書き込みキャパシティーの設定を [オンデマンド] に変更します。
読み込み/書き込みキャパシティーの設定について
DynamoDBテーブルで読み書きを行う際に使用する容量の設定を行うことができます。
参考: https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html
デフォルトではプロビジョンドに設定されていますが、これは常に一定の読み込み/書き込み負荷を行うための容量を確保しており、確保している容量分だけ支払う料金がかかります。
オンデマンドは実際に実行する読み取り/書き込みに合わせたリクエストごとに支払う料金のみかかります。
一見オンデマンドの方がコスト効率が優れているように見えますが、一定負荷であれば読み込み/書き込み容量あたりの料金はプロビジョンドの方が安いので、使用するワークロードに合わせて最適な設定を行いましょう。
下にスクロールして [テーブルの作成] を選択します。
少し待ちますが、テーブルの作成が正常に完了しました。
作成したテーブルを選択します。
テーブルの設定が表示されます。
テーブルの中身を確認するため、[テーブルアイテムの探索] を選択します。
続いて、 項目のスキャンまたはクエリ を展開し、 [実行する] を選択することでテーブルのスキャンを行います。
スキャンが完了しましたが、テーブルは作成されたばかりなので項目は一つも入っていません。
[項目を作成] を選択し、項目を入れていきます。
設定したパーティションキーの "id" 及びソートキーの "created_at" が属性名として登録されています。
今回は新たな属性として "value" (文字列型)属性を作成します。
[新しい属性の追加] から、 [文字列] を選択して項目を追加します。
"NewValue"という名前の属性が追加されました。
名前を "value" に変えた後、各属性に値を入れ [項目を作成] を選択します。
登録した項目が追加されています。
これでDynamoDBテーブルの準備は完了です。
Step Functionsステートマシンからテーブルの項目を操作してみる
テーブル操作権限の付与
DynamoDBテーブルの操作を行っていきますが、その前に権限の設定を追加する必要があります。
テーブル操作の権限をステートマシンに追加しないまま実行すると失敗してしまいます。
権限を追加するには、IAMロールにDynamoDBテーブルを操作する権限を持ったIAMポリシーを付与します。
ステートマシンを作成する時、新しいロールを作成する設定(デフォルト)だったのでIAMロールが自動で生成されています。
Step Functionsコンソールで作成したステートマシンを選択し、詳細欄にある [IAM ロール ARN] を選択します。
IAMコンソールにてステートマシンのロール設定が表示されているので、 [許可を追加] > [ポリシーをアタッチ] を選択してポリシーを追加していきます。
ポリシーのフィルタに DynamoDBと入力しEnter キーを入力すると名前にDynamoDBとついたIAMポリシーの一覧が表示されます。
DynamoDBに関する全ての権限を付与する [AmazonDynamoDBFullAccess] が現れるので、左側のチェックボックスにチェックを入れ、[許可を追加] を選択します。
AmazonDynamoDBFullAccessポリシーがIAMロールにアタッチされています。
今回はハンズオンなのでステートマシンにDynamoDBの全ての権限を与えています。実務では必要な権限に絞って付与しましょう。
Scan
テーブルのすべての項目を読み込みを行う操作です。
Step Functionsコンソール上から、作成したステートマシンの編集を行います。
Passステートはもう使わないので削除します。
対象のステート上で右クリックして [状態を削除] を選択するか、Passステートを選択した後 Deleteキーを入力すればステートの削除を行うことができます。
続いて左ペインの検索窓に DynamoDB と入力してフィルタを行うとDynamoDBに関するステートの一覧が表示されます。
その中から DynamoDB Scan ステートを選択してステートマシン内にドラッグ&ドロップします。
右ペインにDynamoDB Scanステートの詳細が表示されています。
設定タブのAPIパラメータを変更することで任意のテーブルにScan操作を実行することができます。
TableNameに作成したDynamoDBテーブルの名前を設定します。
DynamoDB Scan API Parameter
{ "TableName": "StepFunctionsOperatedTable" }
APIパラメータの設定を変更し、[適用して終了] を選択します。
ASLのStatesからPassステートが削除され、設定したScanステートが追加されています。
ステートマシンを保存したら入力パラメータはそのままで [実行の開始] を選択します。
実行が正常に完了しました。
出力を確認するとItemsにテーブルに登録されている項目が表示されています。
DynamoDB Scan Output
{ "Count": 1, "Items": [ { "created_at": { "S": "2023-06-29 00:00:00" }, "id": { "S": "0001" }, "value": { "S": "hoge" } } ], "ScannedCount": 1 }
DynamoDBコンソール上から、テーブルに新しい項目を登録します。
再度ステートマシンを実行します。
出力を確認すると、登録されている項目3件全て取得できています。
DynamoDB Scan Output
{ "Count": 3, "Items": [ { "created_at": { "S": "2023-06-29 00:00:00" }, "id": { "S": "0001" }, "value": { "S": "hoge" } }, { "created_at": { "S": "2023-06-29 01:00:00" }, "id": { "S": "0001" }, "value": { "S": "fuga" } }, { "created_at": { "S": "2023-06-29 00:00:00" }, "id": { "S": "0002" }, "value": { "S": "hoge" } } ], "ScannedCount": 3 }
Step Functionsコンソール上からステートマシンを編集します。
項目から"id"が"0001"のもののみ取得するようAPIパラメータを修正します。
ScanFilterパラメータで"id"が"0001"と一致するものを返すように定義しています。
DynamoDB Scan API Parameter
{ "TableName": "StepFunctionsOperatedTable", "ScanFilter": { "id": { "AttributeValueList": [ { "S": "0001" } ], "ComparisonOperator": "EQ" } } }
ステートマシンを保存したら入力パラメータはそのままで [実行の開始] を選択します。
出力を確認すると2件出力されており、"id"が "0001" のもののみ取得できました。
DynamoDB Scan Output
{ "Count": 2, "Items": [ { "created_at": { "S": "2023-06-29 00:00:00" }, "id": { "S": "0001" }, "value": { "S": "hoge" } }, { "created_at": { "S": "2023-06-29 01:00:00" }, "id": { "S": "0001" }, "value": { "S": "fuga" } } ], "ScannedCount": 3 }
Scan操作のフィルタ
出力結果をよく見るとScannedCountの値が3になっています。
テーブルに存在する3つの項目を全て取得したという意味で、Scan操作ではテーブルから全ての項目を取得した上でフィルタを行っています。
多くの項目が存在するテーブルでScan操作を行うと全ての項目を取得してしまい時間がかるので、全ての項目が必要な時以外はなるべくScan操作を行わない方が無難です。
GetItem
テーブルから項目を読み込む操作です。
パーティションキー及びソートキーを指定することで一致する項目を取得できます。
Step Functionsコンソール上からステートマシンを編集します。
Scanステートを削除しGetItemステートを追加します。
APIパラメータにはテーブル名、取得する項目のパーティションキー、ソートキーに一致する値を指定します。
DynamoDB GetItem API Parameter
{ "TableName": "StepFunctionsOperatedTable", "Key": { "id": { "S": "0001" }, "created_at": { "S": "2023-06-29 00:00:00" } } }
登録されている以下の項目を取得したいと思います。
ステートマシンを保存したら入力パラメータはそのままで [実行の開始] を選択します。
実行すると以下の結果が出力されました。
DynamoDB GetItem Output
{ "Item": { "created_at": { "S": "2023-06-29 00:00:00" }, "id": { "S": "0001" }, "value": { "S": "hoge" } }, "SdkHttpMetadata": { "AllHttpHeaders": { "Server": [ "Server" ], "Connection": [ "keep-alive" ], "x-amzn-RequestId": [ "XXXXXXXXXXXXXXXXXXXX" ], "x-amz-crc32": [ "XXXXXXXXXX" ], "Content-Length": [ "90" ], "Date": [ "Thu, 29 Jun 2023 06:42:57 GMT" ], "Content-Type": [ "application/x-amz-json-1.0" ] }, "HttpHeaders": { "Connection": "keep-alive", "Content-Length": "90", "Content-Type": "application/x-amz-json-1.0", "Date": "Thu, 29 Jun 2023 06:42:57 GMT", "Server": "Server", "x-amz-crc32": "XXXXXXXXXX", "x-amzn-RequestId": "XXXXXXXXXXXXXXXXXXXX" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { "RequestId": "XXXXXXXXXXXXXXXXXXXX" } }
いくつかの値が返ってきていますが、 Item に着目します。
DynamoDB GetItem Output - Item
"Item": { "created_at": { "S": "2023-06-29 00:00:00" }, "id": { "S": "0001" }, "value": { "S": "hoge" } }
パーティションキー、ソートキーで指定したものに一致した項目が取得できました。
項目が丸ごと帰ってくるため、明示的に指定していない value 属性も取得できています。
PutItem
新しい項目を作成する操作です。
同じキーを持つ項目が既に存在した場合は、新しい項目に上書きします。
Step Functionsコンソール上からステートマシンを編集します。
GetItemステートを削除しPutItemステートを追加します。
APIパラメータにはテーブル名と登録する項目の属性を設定します。
パーティションキー及びソートキーを指定していない場合は登録に失敗します。
DynamoDB PutItem API Parameter
{ "TableName": "StepFunctionsOperatedTable", "Item": { "id": { "S": "0001" }, "created_at": { "S": "2023-06-29 02:00:00" }, "value": { "S": "piyo" } } }
ステートマシンを保存したら入力パラメータはそのままで [実行の開始] を選択します。
実行すると以下の結果が出力されました。
出力をみても登録された項目の情報は返ってきていません。
HttpStatusCodeを見ると200が返ってきているので、リクエストが成功していることがわかります。
DynamoDB PutItem Output
{ "SdkHttpMetadata": { "AllHttpHeaders": { "Server": [ "Server" ], "Connection": [ "keep-alive" ], "x-amzn-RequestId": [ "XXXXXXXXXXXXXXXXXXXXXXXXXXX" ], "x-amz-crc32": [ "XXXXXXXXX" ], "Content-Length": [ "2" ], "Date": [ "Thu, 29 Jun 2023 06:51:26 GMT" ], "Content-Type": [ "application/x-amz-json-1.0" ] }, "HttpHeaders": { "Connection": "keep-alive", "Content-Length": "2", "Content-Type": "application/x-amz-json-1.0", "Date": "Thu, 29 Jun 2023 06:51:26 GMT", "Server": "Server", "x-amz-crc32": "XXXXXXXX", "x-amzn-RequestId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }, "HttpStatusCode": 200 }, "SdkResponseMetadata": { "RequestId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" } }
DynamoDBコンソールからテーブルを確認すると、項目が新しく登録されていることがわかります。
ここで同じAPIパラメータのままステートマシンを実行します。
出力結果は先ほどと同様なので割愛します。
DynamoDBコンソールからテーブルを確認すると、テーブルに登録されている項目も変わりなく返ってきました。
UpdateItem
項目を更新する操作です。
ただし実際にはアップサート処理を行っており、項目が存在しない場合も自動的に作成します。
Step Functionsコンソール上からステートマシンを編集します。
PutItemステートを削除しUpdateItemステートを追加します。
以下のAPIパラメータを設定します。
DynamoDB UpdateItem API Parameter
{ "TableName": "StepFunctionsOperatedTable", "Key": { "id": { "S": "0001" }, "created_at": { "S": "2023-06-29 02:00:00" } }, "UpdateExpression": "SET #value = :updated_value", "ExpressionAttributeNames": { "#value": "value" }, "ExpressionAttributeValues": { ":updated_value": { "S": "updated_piyo" } } }
Key は更新するパーティションキー、ソートキーを指定しています。
ExpressionAttributeValuesは更新する値を指定しており、UpdateExpressionで変更しています。
また、更新するキーである "value" は、予約語であるためExpressionAttributeNamesを使って別名に置き換えています。
ステートマシンを保存したら入力パラメータはそのままで [実行の開始] を選択します。
出力結果は PutItem と同等のレスポンスが返っているので割愛します。
DynamoDBコンソールからテーブルを確認すると、指定した項目のvalueが更新されています。
PutItemとUpdateItemの違い
PutItemとUpdateItemは、指定方法は違えどどちらも項目の追加/更新を行う操作です。
2つの操作の主な違いは既存の項目の更新時の属性の指定にあります。
PutItemは指定した属性のみ登録され、指定されなかった属性は消えてしまいます。
UpdateItemは指定した属性を追加/更新するので、指定されなかった属性はそのまま残ります。
DeleteItem
項目を削除する操作です。
指定したキーを持つ項目を削除します。
Step Functionsコンソール上からステートマシンを編集します。
UpdateItemステートを削除しDeleteItemステートを追加します。
APIパラメータにはテーブル名と削除するパーティションキー及びソートキーを設定します。
DynamoDB DeleteItem API Parameter
{ "TableName": "StepFunctionsOperatedTable", "Key": { "id": { "S": "0001" }, "created_at": "2023-06-29 02:00:00" } }
ステートマシンを保存したら入力パラメータはそのままで [実行の開始] を選択します。
出力結果は PutItem と同等のレスポンスが返っているので割愛します。
DynamoDBコンソールからテーブルを確認すると、指定した項目が削除されています。
Query
パーティンションキー及びソートキーに基づいて項目の探索を行う操作です。
パーティションキーに指定した値に完全一致した項目のみを返し、必要に応じてソートキーと比較演算を行うことで項目絞り込むことができます。
Step Functionsコンソール上からステートマシンを編集します。
DeleteItemステートを削除しQueryステートを追加します。
以下のAPI パラメータを設定します。
DynamoDB Query API Parameter
{ "TableName": "StepFunctionsOperatedTable", "KeyConditionExpression": "id = :id and begins_with(created_at, :created_at)", "ExpressionAttributeValues": { ":id": { "S": "0001" }, ":created_at": { "S": "2023-06-29 01" } } }
ExpressionAttributeValuesはパーティションキーに完全一致する値、ソートキーと比較演算を行う値を指定、KeyConditionExpressionはクエリ文を指定します。
begins_with関数はクエリのフィルタ式内で使用できる関数で、指定された値から始まる項目に絞ります。
フィルタ式内で使える関数は公式リファレンスから確認ください。
今回はcreated_at 属性を"2023-06-29 01"から始まるものに絞っています。
ステートマシンを保存したら入力パラメータはそのままで [実行の開始] を選択します。
実行すると"id" が"0001"、 "created_at" が"2023-06-29 01"から始まるもののみ1件だけ出力されています。
Scan操作とは異なり絞った後の項目数分の1件しか取得していません。
DynamoDB Query Output
{ "Count": 1, "Items": [ { "created_at": { "S": "2023-06-29 01:00:00" }, "id": { "S": "0001" }, "value": { "S": "fuga" } } ], "ScannedCount": 1 }
DynamoDBコンソールからテーブルを確認します。
”id" が"0001"、 "created_at" が"2023-06-29 01"から始まるものは1件なので、Query操作が正しかったことが確認できます。
後片付け
作成したステートマシン、ステートマシンのIAMロール、テーブルを削除します。
Step Functions ステートマシンの削除
IAM ロールの削除
DynamoDB テーブルの削除
これで後片付けが完了しました。
ハンズオンは終了です。お疲れ様でした!
まとめ
Step Functionsを使ってDynamoDBテーブルの操作を行いました。
DynamoDBの操作をStep Functionsでできるようになれば、簡単なアプリケーションならLambdaを使わずに実装できます。
今回は触れませんでしたが、DynamoDB操作はステートマシンのペイロードから変数を受け取って動的に指定することができます。
使いこなしてバシバシアプリケーションを作っちゃいましょう!