そのRDS Proxyホントに必要?立ち止まって一度考えよう

Lambda × RDSだからといって必ずしもRDS Proxyが必須になるわけではありません
2022.12.27

CX事業本部@大阪の岩田です。

一般的にアンチパターンとされているLambdaとRDS(もしくはもう少し対象範囲を広げてRDB)の組み合わせですが、VPC Lambdaの改善やRDS Proxyの登場によって採用しても問題ないケースが増えました。ここ数年でLambda × RDSというアーキテクチャが選定されることも増えたように感じています。その一方で、RDS Proxyのメリット・デメリットをよく精査せずに、Lamba × RDSだから...という理由だけでRDS Proxyを導入し、あまりRDS Proxyのメリットを享受できないような構成も目にする機会が増えました。

このブログではRDS Proxyのメリットが薄くなる構成の具体例を紹介しつつ、RDS Proxy導入の手助けができればと思います。RDS Proxyというサービスの概要やメリット・デメリットについては既に色々な記事が出ているのでこれらの記事も参考にして下さい。

例1. Kinesis -> Lambda -> RDS Proxy

RDS Proxyのメリットが薄くなる構成の具体例1つ目はこんなパターンです

Kinesis Data Stream(以後単にKinesisとします)から起動するLambdaの最大同時実行数はシャード数 × 並列化係数によって計算されます。例としてKinesisのシャード数が50、並列化係数が4の場合、Kinesisから起動するLambdaの同時実行数は200になります。通常の利用用途であればLambda実行環境1つにつき必要なDB接続は1つになるので、RDSの最大同時接続数が200以上であればRDS Proxyを介してDB接続を論理的に多重化する必要がありません。Lambdaが消費するDB接続の上限が簡単に予想可能である場合、最大同時接続数超過に対する保険としてRDS Proxyを導入するメリットは薄いでしょう。

もし シャード数 × 並列化係数で計算されたLambdaの最同時実行数 > RDSの最大同時接続数となる場合はRDS Proxyの導入以外に以下のような対策が取れないかを検討して下さい。

  • Lambdaのロジックやメモリ割り当てを調整することで並列化係数を下げてもKinesisが「詰まらない」ようにできないか
  • RDSのパラメータグループで最大同時接続数を増やす
    • LambdaがRDSに対して実行しているワークロードがライトな場合は最大同時接続数をデフォルト値より大きくしても問題ないケースが多いでしょう
  • RDSのインスタンスサイズを増強する

またKinesisから起動するLambdaは非同期実行になります。非同期実行のLambdaは最大再試行回数で指定された回数分自動でリトライさせることが可能ですし、非同期呼び出しの送信先の設定を利用すると呼び出しのレコードをSQSやSNSに送信することもできます。仮にLambdaの最同時実行数 > RDSの最大同時接続数となる構成で最大同時接続数を超過するリスクがある場合でも、Lambdaの自動リトライによる処理成功が期待でき、処理失敗時もレコードをSQSやSNSに送信できればクリティカルな問題に発展しないようなユースケースではLambdaの最同時実行数 > RDSの最大同時接続数という設定を許容するという選択肢も有りえます。

(補足)

  • API GW -> Lambdaのような構成でもLambdaの設定で「予約された同時実行数」を設定すればLambdaが消費するDB接続の上限は予想可能ですが、APIのルートごとに最適な「予約された同時実行数」を設定するのは難しい作業です。こういったケースではRDS Proxyを導入して考え方をシンプルにするのもよいでしょう。
  • Lambdaの最同時実行数 > RDSの最大同時接続数となる構成でLambdaの自動リトライによる処理成功を期待する場合は、リトライ時の成功確率が高くなるようにLambdaの実行毎にDB接続を閉じるような実装にしておきましょう
  • 今回はKinesisを例にあげて紹介していますが、DynamoDB StreamsやSQSからLambdaを起動するような構成でも同様の考え方が適用できます

RDS Proxyが有効活用できそうなパターン

KinesisからLambdaを起動する構成でもLambdaの処理内容次第ではRDS Proxyが有効活用できる可能性はあります。例えば、Lambdaの実装が以下のようなケースについて考えてみましょう。

// 1. DB接続
// 2. ループ
//     2-1. SQL発行(非トランザクション)
//     2-2. 長時間かかる外部APIの呼び出し処理
//     2-3. 長時間かかるDBアクセスを伴わない色々な処理
//     2-4. SQL発行(非トランザクション)
// 3. ループ終了
// 4. DB接続クローズ

こういった実装であればRDS Proxyを導入することでシャード1から起動したLambdaが2-2,2-3の処理を実行している間に、シャード2から起動したLambdaが2-1や2-4の処理を実行する といった具合にうまく1つのDB接続を共有利用できる可能性が高くなります。Lambdaの最同時実行数 > RDSの最大同時接続数となるような設定値の場合でも、RDS Proxyによって最大同時接続数超過の問題を回避するといった対策が有効になります。

また、この例は少し構成を局所化して紹介していますが、実際にKinesis × LambdaからRDSに書き込むような構成を取る場合は他にもRDSに接続しにくるLambdaなりECSなりが構成に含まれることが多いでしょう。これらのサービスが消費するDB接続数を考慮するとKinesisから起動するLambdaの最同時実行数 < RDSの最大同時接続数であってもRDS Proxyを導入した方が良いパターンも考えられます。

例2. Serverless Expressのようなフレームワークを利用している場合

2つ目はこんなパターンです。API GW × Lambdaという定番の組み合わせからRDS Proxyを介してRDSにアクセスする構成ですが、Lambda上でServerless Expressを動かしているのが特徴です。

Serverless ExpressやMangumといったフレームワークを利用すると、従来EC2やFargate上で動かしていたアプリケーションを簡単にLambda上で動かすことができます。これらのフレームワークを利用する場合はAPIの複数ルートを全て1つのLambda Functionで処理することになります。APIルートごとにLambdaを用意する構成と比較するとアイドル状態のLambda実行環境が無駄にDB接続を消費するということ状況が発生しないというメリットがあります。

APIルートごとにLambda Functionをデプロイする構成を取り、handler外でDB接続を確立して永続化するとこういった状況になります。

アイドル状態のLambda実行環境がDB接続を無駄に消費して最大同時接続数超過エラーを引き起こしやすいため、RDSの前段にRDS Proxyを置くことが同時接続数超過エラーの対策となります。Lambdaの実装を修正し、handler内でDB接続をOPEN/CLOSEすることも考えられますが、パフォーマンス面やスパイク耐性の面から懸念があります。

一方でServerless Expressを利用する場合はこうなります。

アクセス頻度の低いルートから起動するLambda実行環境が無駄にDB接続を消費することが無くなります。全ルートへのアクセスが全てServerless Expressを実行するLambda Functionsに振り分けられるようになるため、このLambda Functionについて予約された同時実行数 < RDSの最大同時接続数であれば最大同時接続数超過のエラーは発生しないことになります。前述した通りAPIのルートごとに最適な「予約された同時実行数」を設定するのは難しい作業ですが、Serverless Expressを利用する場合APIのルートは1つだけなので、特に難しいことは考えずInit処理の中でDB接続をOPENして永続化してしまいましょう。

RDS Proxyが有効活用できそうなパターン

ではServerless Expressを利用する場合RDS Proxyは不要なのでしょうか?必ずしもそうとは言い切れません。スパイクアクセスが予想される場合はRDS Proxyを採用することでスパイク耐性を高めることができます。例えばLambdaの最大同時実行数がデフォルトの1,000でRDSの最大同時接続数数が1,100に設定されている環境があるとします。この環境でLambdaが1,000個並列に同時起動するとどうなるでしょうか?

最大同時接続数超過のエラーは出ないにしろDB接続のOPENという「重い」処理が集中することでRDSの負荷が高くなり、接続タイムアウトなどの問題を誘発する可能性があります。BASEさんの事例でも紹介されているようにmax_connections >= Lambdaの最大同時起動数 > back_logとなるような環境であればSYNパケットの再送が頻発することになるでしょう。

※RDSのデフォルト設定だとインスタンスサイズ次第でmax_connections > back_logとなります。

こういったケースでもRDS ProxyにOPEN済みの接続をプールしておくことでスパイクアクセスが発生した際に接続OPEN処理が「詰まる」事象を回避できます。LambdaのInit処理でDB接続をOPENして永続化しておけばRDS Proxy無しでもある程度スパイク耐性を上げることはできますが、アイドル状態のLambda実行環境が破棄されるサイクルはRDS ProxyがDB接続をプールする期間に比べるてはるかに短く、スパイク耐性という観点からはRDS Proxyを導入するほうが安全と言えます。逆にスパイクアクセスが発生しないことが読めている場合やスパイクアクセスが発生してもRDS Proxy無しで問題なく処理しきれることが分かっている場合はRDS Proxyを導入する必要は無いでしょう。同じコストをかけるのであればRDSのインスタンスサイズを上げる方がメリットが出せるはずです。

まとめ

LambdaからRDSにアクセスする構成において、どの程度RDS Proxyのメリットが享受できるかはケースバイケースです。大きな効果が得られる場合もあれば、ほとんどメリットが得られない場合も有り得ます。Lambda × RDSの構成だからRDS Proxyも必要だよね~... と何となく判断するのではなく、何のためにRDS Proxyを導入したいのか?RDS Proxyを導入することで本当に目的が達成できるのか?RDS Proxy導入以外にもっと効果的な選択肢は無いのか?といったことを考慮した上でRDS Proxyの導入要否を決定するようにしましょう。