
個人ブログを Lambda Web Functions + CloudFront に移行して、実際の料金を確認してみた
はじめに
個人ブログの API と Next.js を、Lambda Web Functions + CloudFront OAC に移行しました。本番環境を切り替えたのは、2026年10月23日16時ごろ(JST)です。
移行前に気になっていたのが料金です。この記事では、移行後のログと Cost Explorer を使い、当初の見積もりと実際の課金を比較します。
移行した構成
ブログフロントエンド(Next.js 16)は Vercel から、ブログサーバーサイド(Rust / Axum)は API Gateway + Lambda から、それぞれ Web Functions に移しました。移行前のサーバーサイドでは、HTTP リクエストを扱うために Lambda Web Adapter(LWA)を使っていました。移行後は LWA を使わず、Web Functions で HTTP を直接受けます。
CloudFront から OAC で2つの Web Function に接続し、/api/* はサーバーサイド、それ以外はフロントエンドに振り分けています。

Rust は Node.js からバイナリを起動し、静的アセットは Next.js から返して CloudFront でキャッシュします。webhook の非同期処理は従来の Event Function に残し、admin-api は移行対象外です。実装は PR #817 にまとめています。
移行後の全体構成図
配信経路に加え、DB への接続、webhook の非同期処理、監視基盤を含む構成です。図中の tidb-proxy は、AWS と自宅の TiDB をつなぐ中継サーバーです。

結論
移行で料金は増えました。Vercel は無料の Hobby プランだったため、Web Functions の費用が新たに加わります。
AWS 全体では月 $17.23 から約 $23 へ、月約 $6 増える見込みです。当初の見積もりより低かったのは、メモリ課金を誤解していたためで、移行による節約ではありません。
| 観点 | 移行後に分かったこと |
|---|---|
| AWS 全体の料金(税抜) | 月 $17.23 → 約 $23 の見込み(月約 $6 増)。旧 Vercel は Hobby で $0 |
| Web Functions の料金 | 開発+本番で約 $5.8 / 30日。本番約 $4.7、開発約 $1.2(按分・丸めあり) |
| メモリ課金 | 実行環境の容量は2 GBだが、課金対象は実際に使ったメモリ。今回は API が約170 MB、Next.js が約240 MB |
| 実行環境・コールドスタート | コールドスタートとみられる起動は約11回→95回/日(全リクエストの1.3%→21%)。移行後は前のリクエストから6分以上空くと必ず起きており、ヘルスチェックを30分間隔にした影響が大きい可能性 |
| アクセス規模 | CloudFront は1日3,000〜3,900リクエストで無料枠内。GA4 は3日間で35表示(別の計測指標) |
結果
メモリ課金は2 GB固定ではなく、使った分だった
実行環境の容量が 2 GB なので、当初は 2 GB × 稼働時間 でメモリ代を数えていました。東京リージョン・Arm では、1台が30日間動き続けると約 $22 という計算です。
実際に使っていたのは blog-api が約 170 MB、Next.js が約 240 MB。この使用量なら、同じ条件のメモリ代はそれぞれ約 $1.9、約 $2.6 になります。リクエスト処理中だけでなく、実行環境が存在する間の使用メモリが課金対象でした。
確認には、1分ごとの platform.invokeSummary にある maxMemoryUsageMb を使いました。4関数の合計を × 60 / 1024 で GB 秒に直し、Cost Explorer の APN1-Lambda-Web-Memory-GB-Second-ARM と照合しました。

ログと Cost Explorer の値は近く、2 GB 固定の計算とは約1桁違いました。残る差の理由は未確認です。リクエスト数は、10月24日に両方とも 3,019件で一致しました。
集計に使ったクエリとコマンド
ログ側は CloudWatch Logs Insights で次のように集計できます。対象の4つのロググループと期間を選択して実行します。
filter type = "platform.invokeSummary"
| stats count(*) as envMin,
sum(record.metrics.requests) as req,
sum(record.metrics.cpuTimeMs) as cpuMs,
sum(record.metrics.maxMemoryUsageMb) as memMbMin
by @log, bin(1d) as day
memMbMin を上の式で GB 秒に変換します。Cost Explorer は次のコマンドで、使用タイプごとの金額と使用量を取得しました。End は集計対象に含まれないため、24〜25日の比較では26日を指定します。
aws ce get-cost-and-usage --region us-east-1 \
--time-period Start=2026-10-24,End=2026-10-26 \
--granularity DAILY \
--metrics UnblendedCost UsageQuantity \
--group-by Type=DIMENSION,Key=USAGE_TYPE
Web Functions は1日約 $0.19
料金の大半はメモリでした。Web Functions には Lambda の無料枠が適用されません。

日次の実績と Cost Explorer のスクリーンショット
| 日付(UTC) | メモリ | vCPU | リクエスト | 合計 |
|---|---|---|---|---|
| 2026-10-24 | $0.185 | $0.020 | $0.001 | $0.206 |
| 2026-10-25 | $0.160 | $0.019 | $0.001 | $0.180 |


画面の金額は小数点以下2桁に丸められています。上の表は一次資料のより細かい値を使っています。スクショは2026年10月27日に撮影し、アカウント情報のあるヘッダーを除いています。
ログで按分すると、本番は約 $4.7、開発は約 $1.2 / 月でした。全体の換算とは集計期間と丸めが異なり、合計は厳密には一致しません。
関数ごとの使用量と料金
関数ごとの料金は、10月24〜26日のログをもとに按分しました。
| 環境・関数 | 平均メモリ | 30日換算 |
|---|---|---|
| 本番 Next.js | 237 MB | 約 $2.5 |
| 本番 blog-api | 172 MB | 約 $2.2 |
| 開発 Next.js | 233 MB | 約 $0.9 |
| 開発 blog-api | 173 MB | 約 $0.3 |
PV で見るとどのくらいの規模か
10月24〜26日の閲覧規模も確認しました。GA4 は 35表示・27アクティブユーザー、Clarity は 25セッション・21ユニークユーザーでした。Clarity ではボット判定の3セッションが除外されています。
AWS のリクエスト数とは計測対象が異なります。タイムゾーンや計測範囲の一致も未確認のため、PV あたりの費用には換算していません。
GA4 と Clarity のスクリーンショット


コールドスタートは1日約11回から約95回に増えた
AWS は、複数リクエストを1つの環境で同時処理することでコールドスタートを減らせると説明しています。今回の移行後、本番 API では、リクエストを待たせた起動(コールドスタート)が 1日約11回から約95回 に増えました。全リクエストに占める割合は、1.3%から21%です。ただし、この差は Event Functions と Web Functions の違いだけによるものではありません。後述のとおり、ヘルスチェックの間隔を変えた影響が大きいとみています。

移行前は10月20〜22日、移行後は24〜26日(UTC)の1日平均です。
コールドスタートは、使える環境がなく、リクエストが起動を待たされることです。ログに直接の記録はないため、起動完了と同時に最初のリクエストを受けた環境を数えました。待たされたリクエストは、起動完了の瞬間に渡されるからです。
6分以上空くと、必ずコールドスタートになる
Web Functions の環境は、最後のリクエストから約6分で終了していました。前のリクエストからの間隔で分けると、境目ははっきりしています。
| 前のリクエストからの間隔 | 移行前:コールドスタート / リクエスト | 移行後:コールドスタート / リクエスト |
|---|---|---|
| 5分未満 | 25回 / 2,381件(1%) | 4回 / 1,050件(0.4%) |
| 5〜6分 | 7回 / 146件(5%) | 28回 / 51件(55%) |
| 6分以上 | 0件 | 253回 / 253件(100%) |
別の検証でも、約6分で環境が終了する結果が出ています。移行後は、リクエストの約2割が前のリクエストから6分以上空いていました。そのたびに、環境が1つもない状態から起動していたことになります。
移行前は5分間隔のヘルスチェックがあったため、6分以上空くことが一度もありませんでした。環境は起動から約1時間使われ続け、コールドスタートは3日間で32回でした。Web Functions の同時処理も働いてはいます(437環境中137環境で2件を同時処理)。ただ、効くのはリクエストが重なったときだけです。
待ち時間
移行前は、API Gateway の1分ごとの最大 Latency が、コールドスタートのあった分で中央値454ms、通常の分で55msでした。移行後は入口の記録がなく(CloudFront のログは無効)、待ち時間は分かりません。初期化時間(platform.initReport の totalDurationMs)は中央値464msです。上のコールドスタートの判定からも、リクエストが何ms待ったかまでは分かりません。待ち始めが初期化の途中なら、待ち時間は初期化時間より短くなります。
次回はアプリ内でコールドスタートを判定する予定ですが、その方法でも何ms待ったかは直接測れません。初期化時間は、待ち時間の上限として扱う予定です。
比べ方と集計方法
取れる記録の違い
Event Functions は呼び出し1回ごとに platform.report を出します。一方、Web Functions のシステムログは、環境の起動・1分ごとの集計・終了を記録する形です。
| 知りたいこと | 移行前:Event Functions | 移行後:Web Functions |
|---|---|---|
| リクエストが起動を待ったか | 記録なし | 記録なし |
| 環境の終了 | 記録なし | platform.spinDownStart |
| 1つの環境の同時処理数 | 常に1 | platform.invokeReport の concurrentRequests |
移行後の platform.invokeStart は、すべての環境で Listening on の2〜10ms後に出ていました。処理完了ログの latency はアプリが測った処理時間なので、引いた時刻がアプリがリクエストを受けた時刻になります。
移行前の platform.report には、初期化した環境の最初の呼び出しに initDurationMs が付きます。ただ、これが付いた79環境のうち47環境は、起動から1秒以上たってから最初の呼び出しを受けていました(最大約11分後)。事前に初期化された環境でも付くため、判定には使っていません。
コールドスタートの判定
起動完了と、最初のリクエストを受けた時刻は、次のログから取りました。
| 起動完了 | 最初のリクエストを受けた時刻 | |
|---|---|---|
| 移行前:Event Functions | アプリの Listening on |
platform.start |
| 移行後:Web Functions | platform.invokeStart |
アプリの処理完了ログの時刻 − latency |
差が1秒未満なら「同時」としました。差は数ミリ秒か1分以上にはっきり分かれるため、境目を10秒にしても結果はほとんど変わりません。
| 起動完了から最初のリクエストまで | 20ms未満 | 20ms〜1秒 | 1〜60秒 | 1分以上 | リクエストなし |
|---|---|---|---|---|---|
| 移行前(158環境) | 29 | 3 | 9 | 38 | 79 |
| 移行後(437環境) | 284 | 2 | 17 | 74 | 60 |
ヘルスチェックを除いても傾向は同じで、コールドスタートの割合は移行前1.4%、移行後20%でした。
リクエスト数
| 1日平均 | 移行前 | 移行後 |
|---|---|---|
| 合計 | 842件 | 452件 |
| └ ヘルスチェック | 288件(5分間隔) | 48件(30分間隔) |
└ 非同期処理(/events) |
5件 | 0件(旧 Event Function に残した) |
| └ その他の HTTP | 549件 | 404件 |
移行前の LWA は、起動直後に / へ readiness check を送ります。この158件はリクエストに数えていません。Listening on は次のコードが出力します。
移行後の実行環境
最後のリクエストから終了開始までは、処理した377環境のうち316環境(約84%)が5〜7分でした。環境が2台以上あった時間は3日間の34%で、環境が存在した時間の約半分です。2台になる理由は未確認です。実行環境の上限は開発2台・本番3台で、DB のヘルスチェックは開発で停止、本番では30分間隔にしています。
使ったクエリ
CloudWatch Logs Insights で取得し、ログストリームごとに待ち受け開始と最初のリクエストの間隔を計算しました。
# アプリのログ(移行前後で共通)
fields @timestamp, @logStream, fields.message, span.uri, fields.latency
| filter @message like /Listening on/ or fields.message = "finished processing request"
# 移行前:呼び出しの記録
fields @timestamp, @logStream, type, record.metrics.initDurationMs
| filter type in ["platform.start", "platform.report"]
# 移行後:環境の記録
fields @timestamp, @logStream, type, record.instanceId,
record.metrics.totalRequests, record.metrics.concurrentRequests
| filter type in ["platform.initStart", "platform.initReport", "platform.invokeStart",
"platform.invokeReport", "platform.spinDownStart"]
AWS 全体では月約 $23 の見込み
CloudFront は1日3,000〜3,900リクエストで無料枠内、旧 API Gateway REST API の利用はなくなりました。共通基盤を移行前と同額とすると、AWS 全体は次の見込みです。

| 項目 | 移行前(2026年9月の実績) | 移行後(30日換算の見込み) |
|---|---|---|
| Lambda Event Functions | $0(無料枠内) | $0(無料枠内) |
| Lambda Web Functions | 利用なし | 約 $5.8(Lambda の無料枠は対象外) |
| CloudFront | 利用なし | $0(無料枠内) |
| API Gateway REST API | $0.09 | 利用なし |
| 共通基盤 | $17.14 | $17.14(同額と仮定) |
| AWS 合計・税抜 | $17.23 | 約 $23 |
無料枠に収まっている Event Functions と CloudFront は、上のグラフでは金額が $0 のため棒に現れません。
共通基盤の $17.14 は、AWS と自宅の TiDB をつなぐ中継サーバー(Fargate)、DNS(Route 53)、ストレージ(S3)などの費用です。旧 Lambda は無料枠内で $0、Vercel も Hobby プランで $0 でした。移行で API Gateway の月 $0.09 がなくなる一方、Web Functions の約 $5.8 が加わるため、全体でも月約 $6 の増加を見込んでいます。
おわりに
ヘルスチェックを5分間隔から30分間隔に変えたのは、Web Functions の料金が高額になるのを避けるためでした。今回は条件を揃えていないため、比較が少し複雑になってしまいました。Next.js を Vercel から Web Functions に移し、API Gateway を外して CloudFront 経由にし、既存の API 用 Lambda も Web Functions に移しています。複数の変更を同時に行ったため、料金や挙動の変化がどの変更によるものかは切り分けられていません。
まずは2026年11月を現状の構成で運用し、12月にサーバー側の blog-api だけを Event Functions に戻して、料金や起動回数を比較したいと思います。その際はヘルスチェックの間隔を変えずに比較するつもりです。
コールドスタートは、アプリが起動完了(listen)時刻を保持し、リクエストを受けた時点で起動完了から1秒未満なら数える予定です。この記事と同じ1秒の基準をアプリ内で判定することで、ログを突き合わせずに記録し、今回の約95回/日と比較したいと思います。ただし、今回は環境ごとに最初の1件を数えたのに対し、次回は1秒未満に受けた2件目以降も数えるため、その違いを踏まえて比較します。CloudFront の時刻には頼らないので、Event Functions に戻しても同じ方法で数えられます。
CloudFront のログも有効にし、キャッシュの当たり外れの確認や、x-amz-cf-id によるアプリのログとの突き合わせに使う予定です。








