個人ブログを Lambda Web Functions + CloudFront に移行して、実際の料金を確認してみた

個人ブログを Lambda Web Functions + CloudFront に移行して、実際の料金を確認してみた

個人ブログをLambda Web Functions + CloudFront OACに移行し、料金と性能がどう変わったか実測データで比較します。見積もりより低かった理由、コールドスタートが9倍に増えた背景、関数ごとの料金内訳まで、詳しく解説します。
2026.10.27

はじめに

個人ブログの 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/* はサーバーサイド、それ以外はフロントエンドに振り分けています。

移行前は Vercel と API Gateway + Lambda、移行後は CloudFront OAC 配下の2つの Web Function に統合した構成

Rust は Node.js からバイナリを起動し、静的アセットは Next.js から返して CloudFront でキャッシュします。webhook の非同期処理は従来の Event Function に残し、admin-api は移行対象外です。実装は PR #817 にまとめています。

移行後の全体構成図

配信経路に加え、DB への接続、webhook の非同期処理、監視基盤を含む構成です。図中の tidb-proxy は、AWS と自宅の TiDB をつなぐ中継サーバーです。

移行後の全体構成。CloudFront、Web Functions、Event Function、Fargate 上のプロキシ、自宅の 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 と照合しました。

10月24日のメモリ使用量は、2 GB固定なら440,160 GB秒、ログでは44,714 GB秒、Cost Explorerでは43,318 GB秒

ログと 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 の無料枠が適用されません。

移行後の Web Functions は1日平均約0.19ドル、30日換算で約5.8ドル。AWS全体では月約6ドル増える見込み

日次の実績と 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

Cost Explorer で2026年10月24〜25日の Web Functions の料金をメモリ・vCPU・リクエスト別に表示

Cost Explorer の日次料金の内訳表

画面の金額は小数点以下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 のスクリーンショット

GA4 のページとスクリーンで2026年10月24〜26日の表示回数35回、アクティブユーザー27人を確認

Clarity の2026年10月24〜26日の25セッション、21ユニークユーザーとボット除外の表示

コールドスタートは1日約11回から約95回に増えた

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

本番APIの1日平均起動回数を3種類に分類。コールドスタートは移行前10.7回、移行後95.3回。全リクエストに占める割合は1.3%から21%

移行前は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 は次のコードが出力します。

https://github.com/shuntaka9576/shuntaka-dev/blob/bdf50b93eddfa58cfc933a1935b1916dc7b78f64/apps/blog-api/src/bin/app.rs#L140

移行後の実行環境

最後のリクエストから終了開始までは、処理した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 全体は次の見込みです。

AWS全体の税抜料金は移行前の2026年9月が17.23ドル、移行後は30日換算で約23ドルの見込み。Vercelは含まない

項目 移行前(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 によるアプリのログとの突き合わせに使う予定です。

この記事をシェアする

関連記事