RareJob Tech Blog

レアジョブのエンジニア・デザイナーによる技術ブログです

AppSync + DynamoDBの構成でマスターデータを入れる

どうも、@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 スクリプトを実装して実行

  1. PHPスクリプト + AWS SDKでDynamoDBへ直接書き込む
  2. jsスクリプト + Lambda + DynamoDBで Lambda経由で書き込む etc...

いろいろな手段はありますが、今回はサーバレスでのアプリケーション構築を前提としていたため、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ですが、複数のデータのエクスポートやインポートをしようとすると小中規模のソリューションはがもうちょっと欲しいなと思いました。