DynamoDBとKinesis Firehoseで可用性の高い非同期Web APIを作る

AWSで非同期処理のWeb APIを作るとき、パッと思いつくのは「API Gateway → Lambda → SQS → Lambda」という構成です。

ただ、今回ある非同期処理システムを作るときに、「API Gateway → DynamoDB → Kinesis → Lambda」のアーキテクチャを選んでみました！この構成を選んだ理由をまとめていきます。

## アーキテクチャの詳細

![アーキテクチャー図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109543/93e43177-80cd-19d9-5821-100e2679f496.jpeg)



まず、今回採用しているアーキテクチャ「API Gateway → DynamoDB → Kinesis → Lambda」の流れについて、もう少し細かく解説します。

全体構成から見ていくと、データは以下のように流れていきます

1. API Gatewayが外部リクエストを受け取り、DynamoDBにデータを書き込む
2. Kinesis Data Streamsがデータの変更を検知し、Kinesis Firehoseにデータを渡す
3. Kinesis Firehoseがデータをバッファリングしながら、Lambdaに渡す
4. Lambdaが最終的なデータ処理を実行

個々の要素について少し見ていきます。

### 1. API Gateway

このアーキテクチャのフロントエンドとして機能するのがAPI Gatewayです。ユーザーやクライアントからのリクエストを受け取り、非同期でDynamoDBにデータを書き込む役割を担います。DynamoDBへの書き込みはLambdaではなく、API Gateway自身の機能で行われます。

### 2. DynamoDBとKinesis Data Streams

API Gatewayが受け取ったデータがDynamoDBに保存されると、Kinesis Data Streamsがそのデータの変更イベントを検出し、自動的にKinesis Firehoseに送信します。

DynamoDBは言わずも知れたAWSのフルマネージドなNoSQLデータベースです。Amazonプライムデーの大量トラフィックを捌いていることでも有名で、スケーラビリティと高可用性を誇ります。
Kinesis Data StreamsはDynamoDBに書き込まれたデータの変更（挿入・更新・削除）をキャプチャーして、ストリームに変換してくれます。

### 3. Kinesis Firehose

Kinesis Data Streamsによってキャプチャーされたイベントは、次にKinesis Firehoseへ送信されます。
Kinesis Firehoseは、リアルタイムのストリーミングデータを効率よく処理するためのサービスであり、データのバッファリングや変換、別のサービスへの転送が可能です。この構成では、Kinesis Firehoseがストリームデータをバッファリングしながら、後続のLambda関数にデータを渡す役割を果たします。

### 4. Lambda

最後に、Lambdaが実際のデータ処理を行います。Kinesis Firehoseから送られてきたデータは、Lambda関数でビジネスロジックに基づいて処理されます。

## ドメイン処理以外でLambdaが不要

![アーキテクチャー図 (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109543/d3907eca-6a9b-8abe-7385-70fae7aebcb1.jpeg)


非同期APIの設計において、「API Gateway → Lambda → SQS → Lambda」の構成はよく使われますが、この構成にはリスクがあります。それは、API Gatewayが呼び出すLambdaでエラーが発生し、SQSにデータが正しく送信されないことで、結果的にデータが欠損する可能性があるという点です。

Lambda自体は強力ですが、そこに自分で書いたコードが含まれている限り、コードのエラーや外部依存のミスにより、SQSにメッセージを送信できないリスクがあります。例えば、トラフィックが急増した時や、Lambdaの実行環境で何らかのエラーが発生した場合、データがSQSに届かず、そのまま欠損してしまうことも考えられます。

一方、「API Gateway → DynamoDB Streams → Kinesis Firehose → Lambda」のアーキテクチャでは、実際の処理部分以外にLambdaを使わず、すべてフルマネージドサービスがデータフローを担当しています。これにより、Lambdaによるエラーハンドリングやコードのエラーリスクを排除し、可用性が向上します。

API Gatewayで受け取ったリクエストは、直接DynamoDBに保存され、Kinesis Data Streamsを経由して自動的に処理が進行します。このように、ユーザーがコードを書かなくても済む部分が増えることで、システムにおける障害発生のリスクが大幅に減少し、データの欠損リスクも低く抑えることができるのです。

これにより、運用者がエラーハンドリングやリトライのロジックを個別に設計する必要がなくなり、よりシンプルかつ高可用なアーキテクチャを実現できます。

## データ欠損のリスクが低く、障害時のリカバリが容易

![アーキテクチャー図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109543/733ca44e-2386-4610-20bc-a6071ce503f3.jpeg)

システムの運用において、データの欠損や障害発生時の対応は非常に重要です。このアーキテクチャでは、障害発生ポイントが3つありますが、それぞれに対して高精度な対策が取られており、データ欠損のリスクが極めて低く抑えられています。

### 障害発生ポイント1: 意図しない入力データ

最初の障害ポイントは、API Gatewayに対する意図しないデータの入力です。外部から予期しないフォーマットや内容のリクエストが送信された場合、後続の処理に問題が発生する可能性がありますが、このアーキテクチャでは、API Gatewayで受け取った全てのリクエストデータがDynamoDBに記録されるため、問題が発生した場合でもデータが確実に保持されます。DynamoDBに記録されたデータは、障害調査の際にログとして機能し、不正リクエストや異常な入力の特定が容易になります。

### 障害発生ポイント2: Kinesisの障害

次の障害ポイントは、AWS起因の障害です。具体的にはKinesis Data StreamやKinesis Firehoseで障害が発生するパターンを考えます。
通常Kinesisで障害が発生した場合でも起きることは「遅延」だと思います。処理が詰まってしまったり、期待したthroughputが出せないなどです。この場合、Kinesis自身の機能でのちにリカバリーすることができます。
非常に稀だと思いますが、Kinesis側でデータ欠損するような障害が発生した場合でもDynamoDBがデータを保持し続けてくれます。このデータから対応することができるでしょう。

### 障害発生ポイント3: Lambdaの処理失敗

最後に、Lambdaがアプリケーションコードのバグや他の理由で処理に失敗するという障害ポイントがあります。これは、非同期処理において最も発生確率の高い問題です。しかし、このアーキテクチャは、Lambdaの障害に対して二段階の対策を取っています。

まず、Kinesis Firehoseが自動的に再送機能を備えており、Lambdaが一時的に機能しなくても、Firehoseがデータをバッファして再送を試みます。このため、Lambdaが復旧した際にバッファされたデータが安全に再処理されます。さらに、DynamoDBにもデータが保存されているため、仮にFirehoseがすべてのデータを再送できなかった場合でも、DynamoDBに記録されたデータを基にして、後から再処理を行うことが可能です。これにより、Lambdaの障害時にもデータ欠損を防ぎつつ、迅速なリカバリが実現されています。

## このアーキテクチャを採用すべきでない場合

このアーキテクチャは、可用性やデータ信頼性の高いシステムを構築する上で非常に有効ですが、全てのユースケースに適しているわけではありません。以下のような場合、このアーキテクチャは採用しない方が良いかもしれません。

### リアルタイム性が厳しく求められるケース

Kinesis Firehoseはバッファリング機能を持つため、バッファの設定によっては処理に若干の遅延が発生します。そのため、リアルタイムでデータ処理を行う必要があるシステムや、即時応答が求められるAPIには不向きです。

### 低コストなシステムを目指す場合

フルマネージドサービスを活用することで運用負担は軽減されますが、その分サービス利用料が発生します。小規模なシステムや予算に制約のあるプロジェクトでは、コストの観点から別のアーキテクチャを検討する方が良いかもしれません。
特に、Kinesis Data Streamはデータ量ではなく起動時間で課金されます。なのでトラフィックが少ない場合ではコストが過剰に増えてしまう可能性があります。

### シンプルさが求められるシステム

このアーキテクチャは、複数のAWSサービスを連携させて動作します。そのため、全体的な設計が複雑になり、シンプルなシステムを構築したい場合にはオーバーヘッドとなる可能性があります。シンプルな非同期処理が必要なだけであれば、LambdaとSQSのみで十分な場合もあります。

## まとめ
アーキテクチャ設計は、単に「システムが動くかどうか」を考えるだけではなく、**「システムがどのように壊れるか、壊れたときにどう対応するか」**を考慮する「障害対応設計」が非常に重要です。どのような障害が発生する可能性があるか、またそれに対してどのように対策を取るのかを、設計の初期段階でしっかり検討することが、信頼性の高いシステム構築に繋がります。

さらに、できるだけコードを書かないことが、システムの可用性向上において大きな鍵となります。AWSのフルマネージドサービスを活用することで、私たちが書くコードよりもはるかにテストされ、実績のあるシステムを利用できるため、障害発生のリスクを大幅に減らすことができます。いかに自分で書くコードの量を減らし、AWSが提供する高可用性のサービスに依存するかが、システムの信頼性を最大限に引き上げるポイントです。

このアーキテクチャにおいても、フルマネージドサービスを活用し、できるだけ自分で書くコードを減らすことによって、システムの可用性や信頼性を向上させ、障害時の対応を効率化しています。これが、今後のアーキテクチャ設計においても重要な指針となるでしょう。

## 参考

* [Using Amazon API Gateway as a proxy for DynamoDB | AWS Compute Blog](https://aws.amazon.com/blogs/compute/using-amazon-api-gateway-as-a-proxy-for-dynamodb/)
* [Using Kinesis Data Streams to capture changes to DynamoDB - Amazon DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/kds.html)
* [Managed Streaming Data Service | Amazon Kinesis Data Streams Pricing | Amazon Web Services](https://aws.amazon.com/kinesis/data-streams/pricing/)