LambdaとRDS の理想と現実
こちらの LambdaとRDSで爆死してみる の続編となります。
こちらでは、LambdaとRDSの相性悪い問題を具体例をもって説明するために壮大に爆死してみました。
ただ現実問題として、LambdaとRDSを組み合わせて使いたい人は数多くいると思います。
理想はLambdaにこだわるならDynamoDBといったコネクションの上限数などを気にしなくてすむアーキテクチャかと思います。
それでも、それでも、現実解としてLambdaとRDSを組み合わせて使う構成をいくつか検討してみました。
ありがちなアンチパターン
まず改めてアンチパターンの紹介です。
API Gatewayとの組み合わせ
クライアントがAPI Gatewayへリクエストを投げて、それがLambdaに渡り、RDSへinsertするパターンですね。
実際よく検討されているパターンかと思います。リクエストの数が少ないと問題ない場合もありますが、
一気にリクエストが来ると以下の状態となり簡単に障害が発生します。
ただこれは負荷テストをやるとすぐに見つかる問題なので、検討はしても実際に本番運用されているのはそれほど多くないと思います。(だってみんな障害嫌いだし)
IoT Core との組み合わせ
これもほとんど同様ですね。
違いは、インターフェイスがAPI Gatewayの場合は HTTP、IoT Coreの場合は MQTT なのと
Lambdaの呼び出しは、API Gatewayの場合は、同期処理、IoT Coareの場合は非同期処理である部分が大きな違いかと思います。
ただ、どちらもリクエスト数が増えると、障害が発生する可能性が高いという点は同じです。
その他のパターン
S3のイベントトリガーでLambdaを呼び出しRDSへinsert などいろいろありますが、ここでは上記の2パターンに絞って記載します。
理想的なパターン
冒頭に述べましたが、RDSの部分をDynamoDBに置き換える パターンです。置き換えるだけなので、図は記載しません。
今回は現実解について書くので、このパターンの詳細は書きません
現実解のパターン
まず、ここでの現実解の定義ですが、
Lambdaを使いたいならRDSはやめるべきなのは重々承知してるけど、それでもやはり使いたい。でも、障害は起こしたくない。というざっくりとした要望に対する解とします。
その1 : SQSを使うパターン
API Gatewayもしくは IoT Coreで受け取ったリクエストをLambdaが処理する部分まではアンチパターンと変わりませんが、
直接RDSに書き込むのではなく、SQSにキューとして入れます。
そして、SQSからキューを取り出すworkerとして、EC2 もしくは Lambda を利用します。(EC2の部分は、コンテナでもいいと思います。)
そして、EC2(Lambda)内の処理で、RDSへ書き込みを行います。
よくある疑問
Q:Lambda使うパターンだと、結局 Lambda が RDS に直接書き込んでるじゃん。コネクション枯渇問題は発生しないの?
A:発生する可能性はありますが、アンチパターンよりは低いです。
具体的な動きとしては、SQSにたまっているキューをLambdaは1度に最大で10個取り出し、処理を行います。
アンチパターンだと、Lambdaは一度の起動で1つのデータしか処理しませんので、ざっくりとした計算で、Lambdaの同時実行数は1/10になり、コネクション数の枯渇がアンチパターンに比べると起こりにくくなっています。
しかし、キューにたまっている数が多すぎると、Lambdaも同時実行数も増えますので、RDSのコネクション数が枯渇することももちろんあるので、その場合はRDSへ書き込むのはLambdaではなくEC2でという選択肢になります。
その他の注意点
API Gatewayの場合、データを送るクライアントから見ると、SQSにデータが登録された段階でACKが返ってくるので、RDSへの書き込みは非同期処理となります。
また、SQSの性質上、同じキューが複数回取り出されたりする可能性もあるので、冪等性の確保が必要となります。
その他の類似パターン
IoT CoreだとLambdaを仲介せず直接SQSへデータをキューとして登録できるので、このようなパターンもあります。
その2:Kinesis Data Streamsを使うパターン
その1のパターンと比較すると、SQSの部分がKinesis Data Streamsに変わったのと、RDSに書き込むのはLambdaだけになっている部分です。仕組みもほぼ同じで、入ってきたデータをLambdaが受け取り、Lambdaの処理でKinesis Data Streamsに保存します。(API Gatewayの場合は、この処理でクライアントにはACKが返ります)
その後、Lambdaが Kinesis Data Streamsをポーリングして、保存されたデータを取得し、RDSに書き込みを行います。
よくある疑問
Q:結局 Lambda が RDS に直接書き込んでるじゃん。コネクション枯渇問題は発生しないの?
A:発生する可能性はありますが、極めて低いです。
Kinesis Data Streams と Lambdaの相性は実は抜群によく、LambdaとRDSの相性の悪さを場合によっては帳消しにしてくれることもあります。具体的には、Kinesis Data Streams と Lambdaを組み合わせて利用した場合、Lambdaの同時実行数は必ずKinesis Data Streamsのシャードの数と一致します。1シャードは、1000 TPS or 1MB/s のデータを書き込みできるので、超莫大なリクエストが来ない限りは、シャードが増えすぎることはありません。つまり、Lambdaの同時実行数を抑えることが可能です。
そして、同時実行数を抑えたまま、LambdaがRDSへ書き込みを行うので、コネクションの枯渇が発生することはほとんどありません。
Q:その1と同様に LambdaじゃなくてEC2がRDSに書き込むパターンはあるの?
A:あります。が、その場合は、EC2がKinesis Data Streamsをポーリングする処理を自前で実装するかその実装がライブラリ化されたKinesis Client Library を使う必要があります。後者はそれほど難しいことではないですが、Lambdaを使う場合に比べると大変なので、敢えて図には入れていません
(Kinesis Client Libraryの説明はここでは行いません)
その他の注意点
API Gatewayの場合、データを送るクライアントから見ると、Kinesis Data Streamsにデータが登録された段階でACKが返ってくるので、RDSへの書き込みは非同期処理となります。
また、RDSに書き込むLambdaでリトライが発生する可能性があるので、冪等性の確保はやはり必要となります。
その他の類似パターン
API GatewayでもIoT Coredでも、Lambdaを仲介せず直接Kinesis Data Streamsへデータを保存できるので、上図のようなパターンもあります。
まとめ
ざっくりと、Lambdaを使ってRDSに書き込みを行う現実解について、API Gateway/IoT Coreというインターフェイスを中心に書きました。
理想はDynamoDBなのは分かってるけど、それでも Lambda と RDS を使いたい方の参考になればと思います。
個人的には、その2のパターンが一番ラクかなと思います。
Lambdaを中継するかしないかは、Lambdaでデータ整形が必要だったりすれば使って、不要であれば、Lambdaを使わないパターンでいいかなと思います。