PowerShellでDynamoDBにJSONドキュメントを格納する

こんにちは、AWS担当のwakです。

さて最近はMongoDBとお友達になりつつあったのですが、「AWS担当の」と言いつつDynamoDBを全く触っていないというのは良くないのではないかと思い始めました。皆様ご存知の通り、DynamoDBはAWSの提供するNoSQLデータベースサービスで、

  • フルマネージド:難しいことはAWSが引き受けてくれる
  • 安定性:膨大な量のサイズのデータを格納しても自動的にシャーディングが行われ(しかもオンラインで)、レイテンシは変わらない
  • JSONドキュメントをそのまま格納できる
  • 高可用性:自動的に3拠点間で多重化される

といった特徴を持つ、まさにクラウドの申し子のような代物です。また個人的には無料利用枠が割と大きめなのが重要な点で、巨大というには程遠く、手で管理するには大きすぎるJSONデータをカジュアルに管理するため、MongoDBの代わりに利用する選択肢になり得ます。今回はPowerShellでこのDynamoDBを触る方法について説明します。

f:id:nurenezumi:20151204113226j:plain

猫キャッチャー

サンプルコード

まず最初に完全な形のサンプルコードを示します。

Add-Type -Path "C:\Program Files (x86)\AWS Tools\PowerShell\AWSPowerShell\AWSSDK.Core.dll"
Add-Type -Path "C:\Program Files (x86)\AWS Tools\PowerShell\AWSPowerShell\AWSSDK.DynamoDBv2.dll"

$accessKey = "********************";
$secretAccessKey = "****************************************";
$endPoint = [Amazon.RegionEndpoint]::APNortheast1;

$client = New-Object Amazon.DynamoDBv2.AmazonDynamoDBClient($accessKey, $secretAccessKey, $endPoint);
$tableName = "YOUR-TABLE-NAME" # 自分で作ったテーブル名
$table = [Amazon.DynamoDBv2.DocumentModel.Table]::LoadTable($client, $tableName);

$document = [Amazon.DynamoDBv2.DocumentModel.Document]::FromJson('{...}') # 引数はJSON形式のテキスト
$table.PutItem($document)

前提条件は以下の通りです。

  • DynamoDBでテーブルを作成してあること
  • IAMユーザーを作成し、適切な権限が割り当ててあること
  • IAMユーザーのAPIキーを作成してあること
  • AWS Tools for Windows PowerShellがインストール済みであること
  • PowerShell 3.0以上

以下はこのコードの説明です。

DLLを直接読み込めば大抵のことはできる

上述の AWS Tools for Windows PowerShell をインストールすると、自動的に環境変数PSModulePath*1SDKのパスが追加され、特に何かを意識しなくてもGet-EC2InstanceStart-EC2InstanceといったAWSのためのコマンドレットが使えるようになります。しかしリファレンスを参照すると分かるように、現時点では項目の追加・削除・編集のためのコマンドレットは用意されていません。そのためには(上のコードのように)直接SDKの中のコードを呼び出す必要があります。

そんなときはAdd-Typeで直接DLLを読み込んでしまいましょう。

PS C:\> [Amazon.RegionEndPoint]::APNortheast1
型 [Amazon.RegionEndPoint] が見つかりません。この型を含むアセンブリが読み込まれていることを確認してください。
発生場所 行:1 文字:1
+ [Amazon.RegionEndPoint]::APNortheast1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (Amazon.RegionEndPoint:TypeName) []、RuntimeException
    + FullyQualifiedErrorId : TypeNotFound

PS C:\> Add-Type -Path "C:\Program Files (x86)\AWS Tools\PowerShell\AWSPowerShell\AWSSDK.Core.dll"
PS C:\> [Amazon.RegionEndPoint]::APNortheast1

SystemName                                                  DisplayName
----------                                                  -----------
ap-northeast-1                                              Asia Pacific (Tokyo)

このようにアセンブリが直接ロードできて使えるようになります。

DynamoDBの準備をする

まずDynamoDBのテーブルを作成します。パーティーションキー、ソートキーを適当に設定しました。なお、このキーは1度作ったらもう変更できません(変えたければテーブルを作り直して移行する必要があります)。

f:id:nurenezumi:20151204102329p:plain

これができたら、このテーブルを触る権限を持ったIAMユーザーを準備し、APIキーを取得しておきます。

レコードを登録・更新・削除する

新規登録

まず空のテーブルへレコードを実際に登録してみます。レコードをまずPowerShell連想配列@{Key1=値1; Key2=値2; ...}で作成し、JSON形式のテキストを介してAmazon.DynamoDBv2.DocumentModel.DynamoDBEntryクラスのインスタンスに変換してからDynamoDBへ送信しています。

# 投入するデータ
$source = @(
 @{name = "Tama"; timestamp = "2015-12-04T00:00:00.000Z"; cuteness = 90;  mood = "暇"},
 @{name = "Tama"; timestamp = "2015-12-04T01:00:00.000Z"; cuteness = 100; mood = "とても暇"},
 @{name = "Tama"; timestamp = "2015-12-04T02:00:00.000Z"; cuteness = "INF"; mood = "睡眠"},
 @{name = "Wak";  timestamp = "2015-12-04T00:00:00.000Z"; cuteness = 0;   mood = "腹減った"; memo = "ラーメン" })

$source | % {
  $json = $_ | ConvertTo-Json -Compress
  $document = [Amazon.DynamoDBv2.DocumentModel.Document]::FromJson($json)
  $table.PutItem($document)
}

登録結果はManagement Consoleから確認することができます。cutenessの型は数値だったり文字列だったり、memoはあったりなかったりしますが、それでもDynamoDBは柔軟に受け入れてくれています。

f:id:nurenezumi:20151204104059p:plain

更新する

テーブルを作るときに決めたキー(ここではnametimestamp)が両方とも完全に一致するレコードを登録すると更新になります。つまりPutItemSQLのINSERT文ではなくMERGE/UPSERT文に相当するわけです。したがって次のコードはデータ部分以外は上と同じです。memoに入れ子にした連想配列をセットしてみました。

$source = @(
 @{name = "Tama"; timestamp = "2015-12-04T02:00:00.000Z"; cuteness = "INF"; mood = "睡眠"; memo = "あおむけ"}, # 更新
 @{name = "Tama"; timestamp = "2015-12-04T03:00:00.000Z"; cuteness = 100; mood = "ごはん"; memo = @{menu = "高級かりかり"; ammount = 2 }})

$source | % {
  $json = $_ | ConvertTo-Json -Compress
  $document = [Amazon.DynamoDBv2.DocumentModel.Document]::FromJson($json)
  $table.PutItem($document)
}
f:id:nurenezumi:20151204134009p:plain

1行は更新、1行は新規追加になりました。memoカラムにはJSONが生で書いてあるように見えますが、よく見ると"N""S"といった見覚えのないキーが出てきています。これはDynamoDBが自動的に与えた型情報です。こちらもきちんと入れ子構造を持ったドキュメントとして管理されている証だと思ってください。

削除

ついでに削除も試しておきます。こちらも構文はまったく同じで、とにかくキーを指定してレコードを特定できればそれが処理対象になると考えればいいです。

$removed = @{name = "Wak"; timestamp = "2015-12-04T00:00:00.000Z"} | ConvertTo-JSON -Compress
$document = [Amazon.DynamoDBv2.DocumentModel.Document]::FromJson($removed)
$table.DeleteItem($document)
f:id:nurenezumi:20151204142233p:plain

レコードを検索する

次は検索を行います。検索に相当するコマンドレットも用意されていないので、やはりこちらもSDKを直接呼び出します。たとえば SELECT * FROM helloDynamoDB WHERE cuteness > 80 に相当するスキャン*2なら、カラム名演算子、値を順に指定して

$scanFilter = New-Object Amazon.DynamoDBv2.DocumentModel.ScanFilter
$scanFilter.AddCondition("cuteness", [Amazon.DynamoDBv2.DocumentModel.ScanOperator]::GreaterThan, 80)
$search = $table.Scan($scanFilter)

で検索ができます。この時点で得られる$searchはカーソルのようなもので、実際の結果はGetNextSet()を呼び出して取得します(カーソルとは違って1件ずつではなく、最大1MB分のデータがまとめて返されます)。

PS C:\> $search = $table.Scan($scanFilter)
PS C:\> $documentList = $search.GetNextSet()
PS C:\> $documentList

Key                                                         Value
---                                                         -----
timestamp                                                   2015-12-04T00:00:00.000Z
mood                                                        暇
name                                                        Tama
cuteness                                                    90
timestamp                                                   2015-12-04T01:00:00.000Z
mood                                                        とても暇
name                                                        Tama
cuteness                                                    100

確かに2件の結果が取得できました。

実際の用途

今回はPowerShellからDynamoDBを利用する方法について書きました。次回はAWS ConfigのログをDynamoDBに格納し、PowerShellから検索して変更履歴を出力するという(多少は)実用的な用途に使ってみたいと思います。お楽しみに!

*1:ここで指定されたパスに配置されたモジュールは自動的に読み込まれるようになります。環境変数PATHのPowerShell版だと思えばだいたい合っています

*2:インデックスを使わないテーブルスキャンのようなものだと考えてください。したがって低速ですがすべての項目が検索対象に指定できます。テーブル作成時に指定したnameを使う検索なら、「スキャン」ではなく高速な「クエリ」が利用できます