サーバーレスでのDB利用についての検討
サーバレスでアプリを作ろうとした
サーバーレスをしたい理由は
管理コストを減らしたい
コンピューティング費用を減らしたい
特に後者のコンピューティング費用を減らしたいというのが強かった。
処理部をEC2からLambdaに移せば、常時起動するEC2のコンピューティング費用は落とせる
問題はDB
RDBとLambdaの相性問題
https://gyazo.com/d6958286219c9998b64214af2fe8cdb5
しかしAWSのRDBサービスである, RDS, Auroraでは常時インスタンスのコンピューティング費用がかかってしまう。
またそもそもサーバレス構成とRDBが相性悪いらしい
コネクションプーリングの問題
Lambdaを使う場合は同じコネクションを使い回せないので、スケール数に限度がある
制限を越えるためにはインスタンスサイズを上げる必要がある。
最低スペックのdb.t3.microだと無料でいけるけど、上記を考慮すると怖い
Auroraでも最低スペックがdb.t3.smallで
$ 0.063 \times 24 \times 30 \times 100 = 4536円/月
Aurora Serverlessにすると良いみたいなのも見た
Aurora ServerlessはDynamoのキャパシティユニットに近いけど、時間当たりの金額が違いすぎる
最低(1ACU)でも1時間当たり 0.1USD ...高すぎる
しばらくアクセスがなかったら、0ACUになるらしいけど、この状態でアクセスがきた場合、コールドスタート発生する
そもそもWebサービスだと、しばらくアクセスないってのはあまりないかも..
そんなこんなでDynamoDBを利用することを決めた
https://gyazo.com/14139b5f2a1d9e78f328d0914821bc90
コンピューティング費用はかからず、書き込み/読み込みキャパシティユニットだけで課金されるから
厳密にはストレージ料金とネットワーク転送量もかかるけどDynamoDBに保存されるデータ量はたかが知れているので、ここはあまりかからないはず..
VPCに置かなくてすむのも良い
DynamoDBの課題
RDBみたいな使い方ができない, 基本的にKeyと一致する1アイテムを取得くらいしかできない
テーブルにはパーティションキー or パーティションキー+ソートキーの複合キー が設定できる
どちらに対してもGET処理はパーティションキー完全一致が必須(ソートキーがある場合は、ソートキーの範囲条件が設定できる)
全てのアイテムから、あるキーがXという値のアイテムだけ取得という処理
RDBでいう1対多の検索するような
あるユーザーがいいねした記事一覧を返すような処理みたいな
Scanという処理であるテーブルの全データを取得した後にFilterをかける。
ここで消費されるキャパシティユニットは、Filter後のデータ量でなく、テーブルの全データ量になる。
仮に1アイテムの平均が100バイトとして、アイテムが1万のテーブルがあるとすれば
1RCU=4KBなので、 100 * 10000 / 4000 = 250 RCU が1回の処理で消費されることになる。。
セカンダリインデックスをはったとしても、パーティションキーを何かしら設定する必要がある
ダミーのパーティションキー(全て同じ)と検索条件にしたいキーをソートキーにしてグローバルセカンダリインデックスをはるという方法もある.
書き込みのたびにインデックス張り替えのコストがかかる
全記事一覧を投稿日時順でページネーションしたいとかやろうとしたら、RDBのoffsetみたいなのができなくて、NextTokenとかになるらしい(一回は全データを取得しないと行けなさそう)
結局Scanと変わらなさそう
考えた案
アンチパターンだと思う
頻繁に参照されるデータはキャッシュしておいて、Lambda側で加工やソートをやる方針
https://gyazo.com/8ab1e13123f3694d242e52a310272f3e
あげた例だと、記事一覧がよく使われる情報としたので、記事テーブルを scanして特に加工せずに返す関数を1つ作る
API Gatewayで上記関数を呼べるようにして、これのキャッシュ時間をある程度取っておいて、なんども呼ばれないようにする
あるユーザーがいいねした記事一覧を返すような処理の場合は
DynamoDBのユーザーテーブルから、ユーザーidとかでGET
全記事取得api呼び出し(キャッシュ利用)
いいね記事idで全記事をフィルタして、返す
記事を投稿日時順でソートして、ページネーション して返す
引数で、ソート条件と、ページ番号を受け取って
全記事取得api呼び出し(キャッシュ利用)
ソート条件で全記事をソート
ページ番号と1ページあたりのデータ数で配列の何番目から何番目を返せばいいかを計算
返す範囲だけスライスして、返す
みたいなのを考えた。
コストを考えるなら記事一覧のキャッシュ時間を1時間くらいにすればいいし、ユーザビリティを考慮するなら、新しい記事が追加されたら一度キャッシュをクリアするとかでもありかと思う
仮に10分くらいキャッシュを持つとして、1回のscanを上記の通り250RCUとすれば、
$ 250 \times 6 \times 24 \times 30 = 1080000RCU/月
オンデマンド利用だとしても、100万RCUで0.285USDなので、結構安いかなと
仮にキャッシュが1分だとしても10倍で3USDくらいなので、まぁおk
これは1アイテムが少ないテーブルに限るかとは思う
例で記事一覧とかしたけど、記事本文とか載せるとNGだと思う
記事本文と記事idだけのテーブルだけ用意しておくのが方針としては良さそう