AWSで月額ほぼ０円のWebアプリ構築を考える

この記事は、[Supershipグループ Advent Calendar 2023](https://qiita.com/advent-calendar/2023/supership) の7日目の記事になります。

## 番宣
[Supership](https://supership.jp/)ではプロダクト開発やサービス開発に関わる人を絶賛募集しております。
ご興味がある方は以下リンクよりご確認ください。
[Supership株式会社 採用サイト](https://supership.jp/recruit/)
是非ともよろしくお願いいたします。

## はじめに
Supership株式会社の @masahito-suzuki です。
去年の[Advent Calendar](https://qiita.com/masahito-suzuki/items/c2c2f5cb81cbce03f738)で記載した内容に引き続き、今回もAWS Lambdaでの関数URLに関しての記載をしたいと思います。

昨今、AWSなどのクラウドインフラ環境で、１ドル１５０円(2023/11現在)と、これまでと同じ利用なのに `サーバーコスト増` となり、supershipの特性上多くのデータ(ペタ単位)を扱う上でどうしても「サーバーコスト高」になやまされてるのが現状です。

この状況下において、たとえば社内向けの比較的シンプルなシステムとしてWebアプリで作成しようとする場合でも、それなりの金額がかかってしまいます。

※ 1USD = 150円計算.

- サーバー
  - EC2(t2.micro)
    1,963 円/月
- データ記憶領域
  - RDS(MySQL, db.t3.micro, ストレージ:10G)
    3,059 円/月
- HTTP環境
  - route53 + SSL証明
- その他
  - HTTP通信費

1USD=150円の世界だと、この環境でも月額約5,000円ぐらいかかってしまう計算になります。

これ以外にEC2を使わないAWSサービスでWebアプリを作る場合の環境を考慮すると以下があるのかと思います。

- サーバーサイト
  - lambda + API-Gatewayで、REST api実装
- フロントエンド 
  - CloudFront + react
- データ記憶領域
  - DynamoDB 
- その他
  - HTTP通信費

この構成の場合だと、lambdaやAPI-GatewayやCloudFrontは先程のEC2環境とは違い従量課金で「常時無料枠(月)」が存在するのと、データ記憶領域をDynamoDBにすれば、相当な低コストで済むし、ログイン周りをoauth2とかを利用しようとすれば、cognitoとか利用すれば、先程のEC2と比べて相当安価で構築可能です。

一方で、今回のタイトルの通り「AWSで月額ほぼ0円のWebアプリ構築」これを考えていきたいと思います。

## 月額ほぼ０円のWebアプリ構成を考えてみる

環境構成としては以下の構成となります。
- Webアプリ環境
  - Lambda 関数URL
- データ記憶領域
  - S3
- その他
  - HTTP通信費

### Lambda 関数URLとAPI-Gatewayを比較
`Lambda 関数URL` とは、2022年4月から、lambdaに対追加された新機能です。
以前からあるserverlesのフレームワーク構成である `API-Gateway + lambda` と `Lambda 関数URL`については以下のような比較となります。

|条件|API-Gateway(REST api)|API-Gateway(HTTP)|Lambda 関数URL|
|:--|:--|:--|:--|
|価格設定|100万Request/3.5USD|100万Request/1USD|lambdaのみ料金|
|認証/認可|IAM, Lambda, Cognito|IAM, Lambda, Cognito, OAuth2|IAM or Public|
|domain利用|可能|可能|不可|
|キャッシュ|可能|不可|不可|
|timeout|29秒|29秒|15分|

これを見るとわかりますが `Lambda 関数URL` と比べると `API-Gateway` は色々とカスタマイズ可能な反面、余分にコストがかかったりします。

あとタイムアウトがAPI-Gateway=29秒に対して、Lambda関数URLのほうが15分となっています。

あと非常に小さな話ですが `API-Gateway + Lambda` 構成では、サービス間の接続に対してオーバーヘッドがある一方で `Lambda 関数URL` の場合は、それらが無いと言う「利点」もあるようです。

ただ `Lambda 関数URL` これの欠点としては `認証/認可` これが IAM か `Publicアクセスのみ` であり、実現する場合は、自前で作成する必要があります。

### S3のコストとデータ記憶領域利用可能について説明
S3は1TBあたり25USD(1usd:150円なら 3,750円)となり、S3の１ブロック単位が128kbとなっています。

つまり1つのS3objectが128kb以下の場合のコストは
- 0.000003USD(月)
で、1USD150円の場合は
- 0.00044円(月)

となっており、これをたとえば10万個のS3Objectを作成した場合のコストは
- 0.3USD(月)
で、1USD150円の場合は
- 44円(月)

となります。

あと以外と知らない人も居ますが、実はS3ってKeyValueストレージであり、なのでS3Bucket以下の `Prefix + Key` が KeyValueストレージで言う所のKeyで、ObjectがValueとなります。

そのため、KeyValue的なデータ記憶領域として利用することができます。

ただ、RDBMSのように色々な情報取得は出来ないけど、list取得などもできて、Keyでデータ取得できるので、設計次第でそれなりのシステムが作れると思います。

なので、それほどデータを必要としない、シンプル構成の社内システムならば、S3をデータ記憶領域としても問題なくシステム構築が行なえ、そしてコストがほとんど発生しないことになります。

あと、S3の便利な機能として、ライフサイクルルールがあり、これは対象のS3BucketやPrefix単位で、何日後にデータ削除すると言う設定ができるので、これらをうまく利用することで、S3コスト発生の原因である「使わないデータがいつまでも残り続ける」を回避できたりすることが可能となります。

あと、ライフサイクル自体にはコストが発生しません。

また Lambdaの場合レスポンスが最大6MByte(Body=Base64なので、それより少ない返却)なのですが、これは別途S3に対して `署名URL` を発行する事で一時的にS3のObjectをパブリック利用できるので、この辺を絡める事で、Lambda制限の6MByteを超えたダウンロードが可能となります。

## AWS LambdaでWebアプリを作る点の問題について

よくWebアプリ作りたいけど、一方でサーバーコストはかけたくないから、serverlessと言う選択最初に行うけど、採用してみると何かレスポンスが「遅いよね」的な事が問題となります。

この原因が `コールドスタート` でありこの問題があるからこそ、結局「コールドスタートがあるから」と言う理由で「serverlessの採用を見送る」なんて事があるのかと思います。

ここでは、この辺の事について深ぼっていきたいと思います。

この辺については去年の[Advent Calendar](https://qiita.com/masahito-suzuki/items/c2c2f5cb81cbce03f738)にも色々と記載ているので合わせて読んでいただければ幸いです。

今回は改めて「コールドスタート」と仲良くなる方法について記載したいと思います。

実はコールドスタート自身は思った以上に `高速` 実行されており、それらが理解できるものとして以下のjsプログラムを元に node-js のLambda環境を構築し `関数URL` を有効にして単純にアクセスすると「思った以上に高速で実行できるね」が実感できるかと思います。

構成:
- 言語 NodsJs(latest)
- CPU: ARM64
- メモリ: 512MB

`index.js` で以下を保存します。
~~~js
exports.handler = async function(event) {
    return {
        'statusCode': 200,
        'body': {hello: "world"}
    }
}
~~~

このプログラムに対して `Lambda 関数URL` で発行されたURLでブラウザでアクセスすると、コールドスタートアクセス(初回アクセス)で、大体 `600ミリ秒から800ミリ前後` で応答が返ってきます。

このように、実はコールドスタート自体はそれほど遅くないのですが、一方でコールドスタートはどのような形で起動するのかと言えば、以下のように

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2975990/b9761ede-dc07-cd65-2530-4d5dd86cf401.png)
　1.Download your code
　2.start new execution environment

のコールドスタートが行われて、その後
　3.Execute initialization code
　4.Execute handle code

これがコールドスタート後実行されます.

一方で「結果的にLambdaのコールドスタートは遅い」と評価される問題について、何処がボトルネックになっているのかについて以下より説明したいと思います。

## 何故 AWS Lambda のコールドスタートが遅いのか

上の項目にある項１、２についてはコールドスタート時においては、どのような構成であっても、大体同じ時間で動作します。
なので、コールドスタート時において、最も時間がかかるのが「項３」になります。

https://docs.aws.amazon.com/lambda/latest/operatorguide/static-initialization.html

lambdaの実行の中で項３の `Execute initialization code` は「コールドスタート以外にも毎回実行」されるものですが、これの多くは１度目に実行と比べて２度目以降の実行だと、キャッシュが効くのでその分読み込みが不要となり、これがコールドスタートが遅いと感じるわけです。

なので「ウォーム実行」時には、`項３` はたしかに実行されるが一方で `キャッシュ化` されているので、実際には再読み込みはされない、だからウォームスタート時は「高速」に動作する結果となります。

なので「lambdaで利用するライブラリの読み込み」の「 `大きさや数` 」これが「コールドスタート」での「実行時間コスト」に反映する事になると言うわけですよ。

## (serverlessのアンチパターン)Lambda URL関数のアンチパターンをまとめる

1. `Execute initialization code` に時間がかかるものを利用しない
2. JDBC的なプーリングで速度改善を稼ぐものは利用しない
3. キャッシュで大幅に速度改善するものは利用しない

端的に言えば `Execute initialization code` ここで時間を使わないもの、そしてキャッシュ等で速度改善するものは、全て `捨てるべき` と言うのが、 LambadaでWebアプリを作るにおいて、必須事項であると思うわけで、これまでの「EC2などで構築して利用するWebアプリのノウハウは捨てるべき」であると言うのが結論であるかと思います。

一方で、たとえばlambdaをnodejsで利用する場合は `AWS SDK V2` だと、これダイレクト `Execute initialization code` に抵触するわけで、一方の V3でも呼び出し単位でも `大きい` ので、そもそも 最小構成を考慮するなら `signatureV4` と `nodejs標準のhttpsClient` と `s3で利用したいWebAPI実装` だけで縛った方が `コールドスタート時の速度は上がる` 結果となります。

あと、JDBC的な接続最適化なプーリングが必要なものとして、AWSだと `RDS` がありますが、これらはコールドスタート毎にキャッシュは毎度初期化されるので、RDS利用自体が `アンチパターン` に結果的になります。

結局の所 Lambda URL関数で `月額０円` or `普通に利用できるWebアプリ` の実現を突き詰めれば
- 専用の `lambda URL関数向けのフレームワーク`
- RDSなどの `データ記憶領域` は使わない
- npmのモジュールは使わない

と言う事を意識する必要があります。

## `Lambda URL関数` で足りない事を説明

`Lambda URL関数` これは非常に簡単に、グローバルHTTPSアクセス化できる反面、一方でログイン等での「`認証／認可`」これが非常に弱い部分があります。

この辺はAPI-Gatewaryで利用できるように、様々な `認証/認可` できるようにあるが、一方で `Lambda URL関数` だとAWSサービスを絡める事ができません。

なので、この辺は自前で実装する必要があります。

あと、lambdaでは `Execute initialization code` を増やさない実装をすると、コールドスタートをあまり考慮する必要がないレベルの、レスポンスが得られます。

なので、たとえば NodeJsの場合は `express` などの既存のフレームワークや npm などのライブラリ利用は思った以上に多くのライブラリを読み込むので、それらがコールドスタート時に重くのしかかる事になるので注意が必要です。

結局の所、安い環境を使う場合は、その分実装でカバーが必要となり、既存のフレームワークだと、コールドスタートが重くのしかかるので、この辺を考慮したものにする必要があり、この辺が敷居が高いものだと言えます。

ただ、私としてはどうしてもこの超低コストでWebアプリを作りたいと思い、自分時間でコツコツと将来の社内システムで利用するNodeJS用に [LFU](https://github.com/maachang/LFU)と言うフレームワークを開発しています。
![lfu_figure.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2975990/69234638-05bb-58a4-249a-be9d0eef72a0.jpeg)

現在実装出来ているのは、ログイン周りの認証・認可機能で、これらは実際にテスト用の[GithubRepogitory](https://github.com/maachang/testLFU) を用いて、以下のログインページを実装しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2975990/9c16503a-9c29-b0b1-ef37-f99c7f44bf39.png)

ここではHTMLやCSSやJSファイルを読み込んで表示していますが `コールドスタート` 時の表示で大体２秒弱で表示が可能となっています。
![lfu_cold_start.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2975990/664a4602-fedb-2752-eb6c-d56b869349f6.jpeg)

実際この処理を行うにあたって、ログイン中なのかを判別する処理等が入っており、そこそこ重い実行となっていますが、それでも約２秒弱でさらに `ウォームスタート` 
![lfu_warm_start.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2975990/fa7cf5ae-98b9-044c-c3b0-4e3a765391dd.jpeg)
なら、lambdaなのか？って思うぐらい、高速な画面表示が可能となっています。

そして、これらログインセッションやログインユーザ情報の確認などは、全てS3上で行われていますが、別段オーバーヘッド等は感じないわけで、改めてS3の凄さに驚きです。

あと弊社では GoogleWorkspace を利用しており、それらの環境でGAS(GoogleAppsScript)が使えるので、これを利用して「なんちゃってoauth」的な実装も行っています。

このように、ちょっと面倒ですが lambda に合わせたフレームワークを作成して、簡易的な認証・認可の実装を行えば、普通にWebアプリ環境として利用できるわけで、そして何よりこの環境を普通の社内システム程度の利用頻度だと、毎月のコストがほとんど０円になるわけで、今の１USD=150円の円安時代にとって、非常に「刺さる」内容であると考えています。

## 最後に
結局作り込みが必要と言うのはありますが、こんな感じで環境構築すれば、普通にWebアプリが使えて、それがほぼ０円で運用できるわけで、いやあ素晴らしいものをAWSさんは作ってくれたなあって思いました。

最後まで読んでいただいてありがとうございました。
あと、株式会社supershipをよろしくお願い致します。