【cluster】外部通信するクラフトアイテムの開発ノウハウ(3)・・・SAM の解説と運用コスト見積もり例
第3回、SAMの解説と運用コスト見積もり編です。
想定読者:
プログラミングや AWS 利用の経験はあるけど、SAM は馴染みのない方。
(N&C's べいべー誕生を勝手に祝って贈るシリーズ記事です)
SAM (Serverless Application Model) とは
AWS のインフラを使って(サーバーレスアーキテクチャの)アプリケーション開発と運用をラクに行えるようにする為の、管理ツールのような位置づけのものです。
必要なリソースを記述した設定ファイルと、その中で動かしたいプログラムのソースコードを用意して `sam deploy` コマンドを実行するだけで、インターネット上に自作の API を公開することができます。
SAM 内で使うリソース・サービス
大まかには次の3つのサービスを組み合わせて利用することになります。
API Gateway
インターネット回線から到着したリクエストを Lambda に受け渡す、中継(ゲートウェイ)の役割を担うサービスです。
API Gateway が URL を発行してくれるので、それを CCK の『外部通信(callExternal)接続URL』に設定します。
Lambda
ちょっとしたソースコード(Lambda 関数)を置いておくと、必要に応じて(何らかのトリガーを受けて)実行してくれるサービスです。
多数のプログラミング言語に対応しているので、自身に馴染みのある言語で開発することができます。
DynamoDB
サーバーレスアーキテクチャに適した、データベースのサービスです。Lambda 関数から DynamoDB に対して書き込み/読み込みを行うことで、目的とするロジックを実装します。
RDBとは異なるパラダイムのデータベースなので、初学者はまず「ハッシュキー」と「レンジキー」の役割(原理的なところ)と使い方を把握することから取り組んでみてください。
通信の伝達経路・処理の流れ
cluster アプリ内で動作するスクリプト(アイテム)から外部通信を行うと、次のような経路でリクエストが伝達・処理されます。
cluster アプリ → インターネット回線 → cluster 社サーバー → インターネット回線 → API Gateway → Lambda → DynamoDB → Lambda → API Gateway → インターネット回線 → cluster社サーバー → インターネット回線 → cluster アプリ
SAM テンプレートの解説
前回記事の template.yml
https://note.com/kepanda/n/nf1b9e59e72ad#a611b1eb-d17a-4658-9bd3-72712a3bdba7
の内容を、パートごとに解説します。
参照されるパラメーターの定義
Parameters:
DdbRegion:
Type: String
Default: ap-northeast-1
DdbCraftMilestoneTable:
Type: String
Default: CraftMilestone
VerifyToken:
Type: String
Default: TO_BE_CONFIGURED
後に参照されるパラメーターを3つ定義しています。いずれにもデフォルト値を指定していますが、実際には(sam deploy コマンド実行時に)コマンドライン引数で上書きされるものもあります。(特に VerifyToken がそう。)
API Gateway のリソース定義
Resources:
HttpApi:
Type: AWS::Serverless::HttpApi
API Gateway を使用するための定義です。
Lambda のリソース定義
BetaFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs20.x
Timeout: 5
MemorySize: 2048
Architectures:
- arm64
CodeUri: dist/beta/
Handler: index.handler
Events:
Track:
Type: HttpApi
Properties:
ApiId: !Ref HttpApi
Method: POST
Path: /beta
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref DdbCraftMilestoneTable
Environment:
Variables:
DDB_REGION: !Ref DdbRegion
DDB_TABLE_CRAFT_MILESTONE: !Ref DdbCraftMilestoneTable
VERIFY_TOKEN: !Ref VerifyToken
DEBUG: "1"
Lambda 関数を使用するための定義です。
動作させるプログラムは Javascript で用意するので、実行ランタイムは Node.js 20.x を選択しています。
タイムアウトは cluster の外部通信の仕様に合わせて5秒にしています。
メモリーサイズは処理時間(レスポンスの速さ)に直接影響するので、適度に大きくコスト的なバランスも考えて 2048 MB に設定しています。(小さいとレスポンスが遅い割に、それほど安くなるわけでもありません。大きすぎるとコスパが悪くなります。)
アーキテクチャはコスパに優れた ARM64 を選択。
実行されるソースコートは dist/beta/index.js にある handler 関数に設定しています。
トリガーイベントとして、先に定義した HttpApi を参照しています。
ポリシーは、DynamoDB のテーブルの読み書きに必要です。
環境変数には、ソースコード内で必要とするものを記述しています。
DynamoDB のリソース定義
CraftMilestoneDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Ref DdbCraftMilestoneTable
KeySchema:
- AttributeName: PartitionKey
KeyType: HASH
- AttributeName: SortKey
KeyType: RANGE
AttributeDefinitions:
- AttributeName: PartitionKey
AttributeType: S
- AttributeName: SortKey
AttributeType: S
BillingMode: PAY_PER_REQUEST
DynamoDB を使用するための定義です。
ハッシュキーの名前を PartitionKey、レンジキーの名前を SortKey として、いずれも型は文字列型に設定しています。
課金タイプを PAY_PER_REQUEST にすることで従量課金になります。
デプロイする方法
基本としては AWS SAM CLI をインストールして、ターミナル等から `sam deploy` コマンドを実行する必要がある・・・という事になっています。
ただし実際には、デプロイコマンドを実行する環境はローカルマシンではなく、より安全に管理された環境を用意する方が良く、GitHub Actions はその要件に叶う手段のひとつなので、それを活用します。
GitHub Action のワークフロー
といっても、前回の記事にある設定ファイル
https://note.com/kepanda/n/nf1b9e59e72ad#280a1581-e736-49e6-9740-04ed16330040
をプロジェクト内で git 管理し、GitHub の main ブランチにプッシュ或いはマージするだけです。
なおワークフロー内にある `${{ secrets.* }}` は機密情報に対する参照なので、それらは GitHub リポジトリの設定 Settings > Secrets and variables > Actions から設定しておきます。
SAM の運用コストについて
この方法で使うリソース3つ、API Gateway / Lambda / DynamoDB はいずれも、ほぼ従量課金ベースの料金体系になっています。
詳しくはそれぞれの料金体系を参照する必要がありますが、単純化すればおよそ
(通信の回数)×(各リソースの単価合計)
のような形になるので、自作のアイテムが通信する回数がどれくらいになるのかを見積もる必要があります。
仕様上限(1分間に5回)の通信をし続けるアイテムの場合、どれだけ通信することになるか
例として、現在の cluster の仕様上限である「1分間に5回」の通信をし続けるアイテムを考えてみます。
現実味はありつつもアグレッシブ(安全寄り)な仮定
1つのワールドに当該アイテムを平均5つ設置する。
アイテム設置ワールドの平均的なプレイ回数を1,000再生/月とする。
アイテム購入者が公開している平均的なワールド数を10とする。
該当ワールドに来訪するプレイヤーの平均的な滞在時間を20分とする。
全てソロプレイと仮定する。
この条件で通信回数を単純計算すると・・・
5 x 1,000 x 10 x 20 x 5 = 5,000,000回
つまり、この仮定においてはアイテム購入者1名あたり500万回/月の通信を想定することになります。
AWS 料金見積もりツール
こちらで料金の見積もりができます。
上で計算した、500万リクエスト/月の仮定で見積もってみます。
API Gateway (東京リージョン)
リクエスト 500万回/月
平均リクエストサイズ 2KB
→ 6.45 USD
Lambda (東京リージョン)
リクエスト 500万回/月
各リクエスト実行時間 100ms
割当メモリ量 2048MB
→ 8.80 USD/月
DynamoDB (東京リージョン・オンデマンドキャパシティ)
1リクエストにつき書き込みが1回、読み込みが1回発生するとするとします。
テーブルクラス 標準
データストレージサイズ 1GB
平均項目サイズ 1KB
標準書き込み数 500万回/月
結果整合読み取り数 500万回/月
→ 8.12 USD/月
合計
23.37 USD/月
かなりアグレッシブに通信するアイテムが前提でありますが、結構しますよね。このアイテムを無計画にストアで販売してしまうと、すぐに赤字になってしまうことがわかります・・・!!
前述の仮定の懸念点
クラフトアイテムをストアで販売/配布すると、それを一度入手した cluster ユーザーにとっては「使い放題」です。つまりアイテムがどれだけ利用されることになるのかは不確定で、推測するしかありません。
推測のベースとしては、実際に cluster 内にあるメジャーなクラフトワールドがどれくらいプレイされているか(=ワールドの再生数)や、各ユーザーがどれくらいのペースでワールドを建設しているか、そして cluster そのもののプレイ人口が今後どのくらい伸びるか、を考える必要がありそうです。
例えば超人気クリエイターがアイテムを購入してくれて、そのアイテムを今後建設するワールドでたくさん使ってくれて、それらのワールドがたくさんプレイされると、それに比例するように運用コストが大きくなることになります。
今のところクラフト製ワールドで再生数10,000を突破したところは(たぶん)まだ無さそうですが、今後はわかりません。現在の平均的なクラフトワールドのプレイ回数が1,000再生くらいだと仮定していても、単純に cluster のプレイ人口が10倍になれば、それが10,000再生になる可能性も拭えません。
極端な話
仮に cluster がずーっと永遠にサービス継続して、ワールドクラフトも運営され続けたとすれば、そこで使われ続けるアイテムによる通信先を提供し続ける運営コストは当然のごとく無限大になりますので、そもそも買い切りの販売価格をつけるのは無理がある話ではあります。
現実的な落とし所
つまり、アイテムのサービス寿命を有限期間に設定しておく方が賢明で、アイテムの販売にあたって予め「このアイテムの機能は◯年◯月◯日でサービス終了する予定です」と断っておくことがリスク管理のために必要なのかなと思います。
筆者の場合は、そもそも人柱的な取り組みだと自分の中で割り切った上で、「赤字になりそうな時は何かしら代案など提供した上でサービス停止するかもだけど、許してね・・・」というスタンスです。(汗
想定寿命はどれくらい?
こればかりはわかりませんが、これまでの cluster の進化速度を見ていると、数年後には全く別物なプラットフォームになっている可能性もあって、その頃には当該アイテムも陳腐化しすぎてもう使われていない・・・という可能性も充分にあります。
まぁ、とりあえず3年くらいでしょうかね・・・。(困惑)
cluster への要望
将来がほぼわからないのに、前払い・買い切りで終身サービスを販売するという無理ゲー状態なのはツライところなので・・・
今後の開発のロードマップ、いや、そこまで正式なものでなくても良いので、方向性を読み取れる意見表明のようなもので良いので、積極的に情報を出してほしいな・・・と思います。(切実)