DynamoDBテーブルのAthena参照をServerlessFrameworkで構築


以前、参考記事通りにCDKで組んだことがあったのですが、CDKでできるならCFでできないか?エクスポート周りもCLI(シェル)でやってたので、SDKで組めないか?

そこから今回のを組みました。

クラメソさんの記事が思惑に完全一致でした。大感謝。

Exportのコスト回りや仕組みについては、下記に詳しくまとめている方がいたので、こちらを見ると非常にわかりやすいです。

全体設計

  • dynamoのエクスポートが毎回新しいファイルを新しい階層に作る
  • 参照不要なファイルが多々ある

って形でしたので、

  • エクスポート→必要なファイルだけコピー→コピー先を参照

ってことしてます。

API設計

API(3つ)

  • [GET]対応リージョン取得
  • [GET]テーブル一覧取得(query: region)
  • [POST]エクスポート実行(body: region & table)
    • 1: エクスポート処理中チェック (DynamoDB: listExports)
    • 2: 過去データ削除 (S3: listObjectsV2 & deleteObject)
    • 3: ポイントインタイムリカバリ、オン(DynamoDB: updateContinuousBackups)
    • 4: DynamoDBエクスポート開始(DynamoDB: exportTableToPointInTime)
    • 5: ポイントインタイムリカバリ、オフ(DynamoDB: updateContinuousBackups)

※ロジックは割愛。上述のAPI使えばいけます。

S3イベント(1つ)

  • ファイルコピー用Lambda
    • Exportされたら発火。ファイルをコピーする

環境

nodejs14.x
Serverless Framework: 2.57.0

実装

serverless.yaml
※ポイントのみ、記載

・・・
iamRoleStatements:
    - Effect: 'Allow'
      Action:
        - "dynamodb:ListTables"
        - "dynamodb:ExportTableToPointInTime"
        - 'dynamodb:ListExports'
        - 'dynamodb:UpdateContinuousBackups'
        - 'dynamodb:ExportTableToPointInTime'
      Resource: '*'
    - Effect: 'Allow'
      Action:
        - 's3:GetObject'
        - 's3:CopyObject'
        - 's3:DeleteObject'
        - 's3:AbortMultipartUpload'
        - 's3:PutObject'
        - 's3:PutObjectAcl'
      Resource:
        - "arn:aws:s3:::${env:DYNAMO_ANALYTICS_BUCKET}/*"
    - Effect: 'Allow'
      Action:
        - 's3:ListBucket'
      Resource:
        - "arn:aws:s3:::${env:DYNAMO_ANALYTICS_BUCKET}"
・・・
layers:
  node:
    path: layer/modules/node
    name: ${self:service.name}-${sls:stage}-nodeLayer
    description: Description of what the lambda layer does 
    compatibleRuntimes:
      - nodejs14.x
    package:
      patterns:
        - node_modules/**
・・・
functions:
  api-export:
    handler: dist/table-export/src/index.handler
    events:
      - http:
          method: ANY
          path: '{proxy+}'
          private: true
      - http:
          method: ANY
          path: '/'
    layers:
      - { Ref: NodeLambdaLayer }
  dynamoExport:
    handler: dist/table-export/src/standalone/main.dynamoExportHndler
    layers:
      - { Ref: NodeLambdaLayer }
    events:
      - s3:
          bucket: ${env:DYNAMO_ANALYTICS_BUCKET}
          event: s3:ObjectCreated:*
          rules:
            - prefix: exports/
            - suffix: .json.gz
・・・
resources:
  Resources:
    GlueDatabase:
      Type: AWS::Glue::Database
      Properties:
        CatalogId: !Ref AWS::AccountId  
        DatabaseInput:
          Name: !Sub glue-database-${sls:stage}
    GlueTable:
      Type: AWS::Glue::Table
      Properties:
        CatalogId: !Ref AWS::AccountId
        DatabaseName: !Ref GlueDatabase
        TableInput: 
          Name: !Sub glue-table-${sls:stage}
          TableType: EXTERNAL_TABLE
          StorageDescriptor:
            Location: !Sub s3://${env:DYNAMO_ANALYTICS_BUCKET}/athena/data
            InputFormat: org.apache.hadoop.mapred.TextInputFormat
            OutputFormat: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat
            Parameters:
              EXTERNAL: true
            Columns:
              - Comment: "id test"
                Name: 'Item'
                Type: 'struct<id:struct<S:string>,name:struct<S:string>>'
            SerdeInfo:
              Parameters:
                paths: "Item"
              SerializationLibrary: org.apache.hive.hcatalog.data.JsonSerDe

  • Glueテーブルは必要に応じて拡張させる。(Typeに増やすの微妙ですが、そこに追加していけばです。)

確認

連携がうまくいけば、画像の感じに確認できます。

参照が、Item.id.sと微妙ですが、view作っちゃえば綺麗に使えます。

まとめ

Glueの定義で正解がわからず、少し苦労しました。
Blackbelt調べたらあったので、そのうち見てみようかなと。

あと、ListBucket(削除時に利用)。どの権限が必要かで詰まったのと、フェッチ量が多いと1回で取れないってのが気付くまでに時間食いました。(1回目がうまくいってたが故にハマった。。。)
そう言うのは経験積んで減らしていきたいです。

Athenaでも、使いすぎるとお金かかるかもですが、小規模ならほぼ無料みたいなものなので、そういうところはAWS様様です。

参考