• AWS

【AWS re:Invent 2017 レポート】サーバレスアプリケーションのデータレイヤーの最適化 #SRV301

はじめに

こんにちは。ゆめみのジホンです。

re:Invent 2017のサーバレスに関するセッション「SRV301 - Optimizing Serverless Application Data Tiers with Amazon DynamoDB」の中で、個人的に興味深かった内容をピックアップして紹介したいと思います。


Speaker

• Edin Zulich – NoSQL Solutions Architect, AWS

• Srini Uppalapati – Vice President, Consumer Bank Engineering, Capital One


Agenda

  • DynamoDB in serverless applications
  •  • A bit about Amazon DynamoDB

     • Example: transactions and queries with microservices

     • Example: high volume time series data

  • Serverless at CapitalOne
  •  • Breaking down the monolith

     • Transaction Hub journey

    ここでは、上記の中で前半のDynamoDBに関するアーキテクチャ・パターン(CQRS)をメインとして説明する予定です。


    CQRS(Command Query Responsibility Segregation)の紹介

    DynomoDBの特徴に関する簡単な説明の後、本題のCQRS(Command Query Responsibility Segregation – コマンドクエリ責務分り)というパターンが紹介されました。

    v3_01.png

    CQRSとはデータレイヤーを書き込み(Write, Command)と読み込み(Read, Query)に完全に分ける仕組みです。例えば、

    • Command – API Gateway + Lambda + DynamoDB

    • Query - DynamoDB Streams + Lambda + 他のデータベース

    のように、CommandとQueryを担当するデータベースを分けることにより、各自の責任範囲が明確に分離されデータレイヤーの柔軟性が高まります。特にマイクロサービスでは、各マイクロサービスが要求するデータ構成が違う場合が多いので、CQRSの活用性がより高まると思います。

    DynamoDBにおいて、このCQRSを意識した設計を可能にしてくれる機能の一つがDynamoDB Streamsです。

    v3_02.png

    DynamoDB側の書き込み(Command)が発生する度に、正確に一度だけDynamoDB Streamsを通してイベントがリレーされます。(Transaction Log Tailing)そのイベントがLambdaでトリガーされ、それぞれのマイクロサービスのQuery要求に最適な形のデータベースに変換して検索することができます。そのため、新しいマイクロサービスを追加する際もCommand側のデータベースを汚すことなく対応ができます。


    Example: Shopping Cart Data Model

    次は、CQRSを活用したモデルの例です。

    v3_03.png

    既存のMySQLのようなRelational Databaseが正規化(Normalized)に力を入れているのに対して、DynamoDBのようなNoSQLデータベースの場合は、関連するデータをまとめて(Aggregated)保存した方がより効果的です。

    (より専門的なDynamoDBテーブルのスキマー設計に関しては、Advanced Design Patterns for Amazon DynamoDB (DAT403-R) のセッションの方が役に立つと思います。)


    Command side – Update

    v3_04.png

    ショッピングカートに入っているアイテムにUpdate処理が発生した際は、DynamoDBの条件付きの書き込み(Conditional Write)という機能を利用します。Conditional Writeの実装はOptimistic Concurrency Control(OCC)をベースにしているため、 結果的に一貫性(Eventual consistency)を持つことができます。


    Command side – Transaction

    複数のテーブル間のトランザクション(Transaction)処理が必要な場合は、DynamoDB Streams、Conditional Write、複数のLambdaを組み合わせて対応できます。

    具体的にユーザがShopping cartに入れたアイテムを購入する(Checkout)際の処理を次の画像で見てみます。

    v3_05.png

    * 購入成功の場合

    1. Shopping cartのCheckout開始を表すフラグ(CheckOut)をtrueに変更する

    2. DynamoDB Streamsにより購入しようとするアイテムの情報がダウンストリーム(Downstream)される

    3. Product catalogの在庫を確認・更新するためのLambdaがキックされる

    4. 在庫が充分な場合は、Commitを行うLambdaが呼ばれる

    5. Shopping cartを空にする(Items:{})& Checkout開始フラグをfalseに変更する

    v3_06.png

    * 購入失敗の場合

    4. 在庫が足りなかった場合は、Rollbackを行うLambdaが呼ばれる

    5. Checkout開始フラグをfalseに変更する(Shopping cartのアイテムはそのまま)

    ただ、この一連のプロセスは非同期処理(Asynchronous)とDynamoDB Streamsの結果的一貫性(Eventual consistency)をもとにしているため、Total Latencyが増加する可能性があるので注意する必要があります。


    Query side

    v3_07.png

    前述の通り、Command側とQuery側が分離されているため、各サービスの要求に合わせてQuery側のデータベースを変更できます。

    CQRSの応用として、DynamoDBのTTL(Time to live)機能が利用できます。TTL属性にはEpoch timestampが保存され、一定の時間を過ぎるとテーブルから該当アイテムが自動的に削除されます。その際、削除イベントがストリーミングされ、アクセスの少ないデータ(Cold data)をS3側にアーカイブすることもできます。

    v3_08.png


    おわりに

    CQRSパターンはマイクロサービスを設計する際のベストプラクティスとして参考になると思います 。実は半年前にサーバレスアプリケーションのアーキテクチャを設計する際に、DynamoDB Streams、Conditional Write、TTLなどの機能を組み合わせて工夫した結果、最終的にはCQRSと似たCommandとQuery側を分離するアーキテクチャに辿り着いたことがあります。当時はそのアーキテクチャに対する理論的なベースが足りない状態だったため、このセッションを通して改めて概念が再確認できて非常に有益な時間でした。

    Pagetop