どうも、@jumboOrNot です。 弊社ではAppSyncのユースケースが少しずつ増え始めており、色々と調査しています。今回はその中の一つのデータストアがDynamoDBの場合のマスターデータのセットアップについて話します。
ユースケース
お題の通り。 AWSでのサーバレスアプリケーションのよくある構成の中で、リリース前にそれなりのボリュームのマスターデータを入れておきたいことがあります。またサンプルデータを入れたい時とかも出てくる話かと思います。
解決方法1 aws コマンド経由で実行
よくある手段の一つです。 サンプルにもあるように batch-write-item コマンドを使うと複数行のデータの挿入が可能です。
aws dynamodb batch-write-item --request-items file://ProductCatalog.json
この場合、入れたいデータがCSVで用意されており、サンプルにあるようにデータの構造を整形するのは手間だったので、別の方法を検討しました。
{ "ProductCatalog": [ { "PutRequest": { "Item": { "Id": { "N": "101" }, "Title": { "S": "Book 101 Title" }, "ISBN": { "S": "111-1111111111" }, "Authors": { "L": [ { "S": "Author1" } ] }, .......
解決方法2 CloudFormationを使ってS3経由で取り込む
公式にも記載されている方法ですね。 もし CloudFormation に慣れ親しんでいたり、S3から定常的に実行する必要があればこの手法が良いと思います。 CloudFormationを使う以外にも、AWS Data Pipeline経由で実行する方法も紹介されていますが、ちょっと手順が面倒でした。
解決方法3 スクリプトを実装して実行
いろいろな手段はありますが、今回はサーバレスでのアプリケーション構築を前提としていたため、AppSync(GraphQL) のセットアップができていたため、これ経由でinsertしてしまうのが楽だと思い、今回はこの手法で実行しました。
コマンド(Javascript + aws-sdk + aws-appsync client)--> AppSync --> DynamoDB
この方法が今後のアプリケーションのSchemeのメンテなどに追従しやすいと考えたためです。 CSVからAppSync経由でDynamoDBにinsertする実装例を下記に記載します。
実装例
const aws = require('aws-sdk') const AWSAppSyncClient = require('aws-appsync').default const gql = require('graphql-tag') const fetch = require('node-fetch') const fs = require('fs'); const csvSync = require('csv-parse/lib/sync'); // requiring sync module const file = 'example_master.csv'; let data = fs.readFileSync(file); let csvData = csvSync(data); global.fetch = fetch; const appSyncConfig = { // AppSyncから発行する } const exampleQuery = gql` mutation MyMutation( $example_id: Int!, $created_at: Int!){ put( example_id: $example_id, created_at: $created_at ) { example_id } } ` const config = { url: appSyncConfig.graphqlEndpoint, region: appSyncConfig.region, disableOffline: true, auth: { type: appSyncConfig.authenticationType, apiKey: appSyncConfig.apiKey } } const options = { defaultOptions: { watchQuery: { fetchPolicy: 'cache-and-network' } } } const client = new AWSAppSyncClient(config, options) const sleep = msec => new Promise(resolve => setTimeout(resolve, msec)); async function run(){ try { const currentUnixtime = Math.floor((new Date()).getTime()/1000) // set sleep for (var i = 0; i < csvData.length; i++){ let column = csvData[i] if (!isNaN(column[0])){ const data = { example_id: Number(column[0]), created_at: currentUnixtime } const response = await client.mutate({ mutation: exampleQuery, variables: data }) await sleep(200); } } } catch (err) { console.log('error posting to appsync: ', err) } } (async ()=>{ await run(); })();
基本的にはAppSyncに用意したQueryをjsで呼び出してCSVから渡しているだけです。
上述の batch-write-item
を使うとより早く実行できますが、今回はそこまで速度も必要なかったのでそのまま実装しています。
できそうだけどできない方法
現在のDynamoDBでは PartiQL をサポートしているため、SQLライクな構文でテーブルへのアクセスが可能です。 気持ち的にはここで複数行をまるっとinsertしたいお気持ちですが、公式にもあるようにこれはサポートされておらず、上述の2つの別のやり方をする必要があります。
気をつけること
DynamoDB には CapacityUnitの設定があり、デフォルト値は結構小さく設定されています。 そのため上述のサンプルのように sleep を入れていても、これの上限を超えて処理が中断されることがあります。 そのため実行するボリュームに応じてこの設定を変更してください。 もしくは一時的に オンデマンド に設定を変更することも可能です。
CloudWatchのMetricsを見れば実行中の消費状況が見れるので、ここで判断するのが良いかと思います。 コストトレードオフなパラメータなので注意が必要です。
まとめ
まだまだ進化し続けるDynamoDBですが、複数のデータのエクスポートやインポートをしようとすると小中規模のソリューションはがもうちょっと欲しいなと思いました。