
DevelopersIO のバックエンドを ECS から Lambda Web Functions に移行して、マルチリージョン化してみた
はじめに
2026年10月12日に一般提供(GA)が始まった AWS Lambda Web Functions へ、DevelopersIO の Next.js バックエンドを移行しました。
これまでバックエンドは ECS を約10か月利用していました。
この記事では、移行後の構成、ビルドとデプロイの所要時間、4リージョンでのリクエスト処理、月額コストの試算、キャッシュミス時の応答性能、サービスクォータと監視運用を紹介します。
構成図
移行前(ECS)

CloudFront からのリクエストを、VPC origin と内部 ALB を経由して ECS(EC2 Spot)へ転送していました。コンテナイメージは ECR から pull する方式でした。
移行後(Web Functions)

前段の CloudFront は同じものを使い続けています。
アプリケーションは ZIP にまとめて S3 に置き、Web Functions へデプロイします。コンテナイメージ、ECS タスク、内部 ALB は不要になりました。
ビルドとデプロイ
工程の違い
| ECS | Web Functions | |
|---|---|---|
| ビルド用 CodeBuild | EC2(BUILD_GENERAL1_LARGE)で Docker イメージをビルド | Lambda コンピュート(BUILD_LAMBDA_10GB)でテスト、Next.js のビルド、ZIP 作成 |
| 成果物の置き場 | ECR | S3 |
| デプロイ用 CodeBuild | BUILD_LAMBDA_1GB。CloudFormation でイメージを更新し、サービスの安定を待つ | BUILD_LAMBDA_4GB。S3 の ZIP を指定して cdkd でデプロイし、デプロイ後に動作確認を実施 |
Web Functions のデプロイには、CDK アプリを AWS SDK で直接デプロイする cdkd を使っています。
buildspec の抜粋
ECS のビルドです。ECR にログインし、Docker イメージをビルドしてタグを付け、ECR へ push します。
- aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ECR_URI
- docker build . --build-arg ... -t ${IMAGE_NAME}
- docker tag ${IMAGE_NAME}:latest ${ECR_REPOSITORY_URI}:${IMAGE_TAG}
- docker push ${ECR_REPOSITORY_URI}:${IMAGE_TAG}
ECS のデプロイです。CloudFormation スタックのコンテナイメージのパラメータだけを新しいイメージの URI に更新し、スタックの更新完了と、ECS サービスの安定を待ちます。
- aws cloudformation update-stack --stack-name ${ECS_STACK_NAME} --use-previous-template --parameters ParameterKey=ContainerImage,ParameterValue="$NEW_IMAGE_URI" --capabilities CAPABILITY_NAMED_IAM
- aws cloudformation wait stack-update-complete --stack-name ${ECS_STACK_NAME}
- aws ecs wait services-stable --cluster default --services $SERVICE_ARN
Web Functions のビルドです。ZIP を put-object で S3 に置くと、S3 のバージョン ID が返ります。この ID をデプロイ時に使うため、バケットのバージョニングを有効にしています。
pnpm run check
pnpm run typecheck
pnpm run test
pnpm run build
(cd "$OUT" && zip -r -q "$ZIP_PATH" . -x "*.DS_Store" -x "*/.git/*" -x "app.zip")
aws s3 sync "$STATIC_OUT/" "${DEST}static/${ZIP_HASH}/" \
--cache-control "public, max-age=31536000, immutable" --only-show-errors
ZIP_VERSION=$(aws s3api put-object --bucket "$ARTIFACT_BUCKET" --key "$ZIP_KEY" --body "$ZIP_PATH" \
--content-type application/zip --query VersionId --output text)
Web Functions のデプロイです。ZIP のバケット、キー、バージョン ID を CDK の context で渡し、cdkd が Revision をその ZIP から作り直します。
$CDKD deploy "$STACK" -c stage="$STAGE" -c stackName="$STACK" -c codeBucket="$CODE_BUCKET" -c codeKey="$CODE_KEY" -c codeVersionId="$CODE_VERSION"
所要時間
dev と prd のビルドとデプロイにかかった時間の中央値です。成功した実行だけを対象にしています。ビルドとデプロイの表の括弧内は実行回数です。
| ビルド(秒) | ECS | Web Functions |
|---|---|---|
| dev | 155.6(7回) | 63.1(13回) |
| prd | 123.9(5回) | 62.8(4回) |
Web Functions のビルドは、ECS の4〜5割程度の時間で終わりました。dev のビルド時間をフェーズ別の中央値(秒)で見ると、短縮分の大半を占めたのは、ソース取得(download_source)、インストール(install)、事前処理(pre_build)です。
| ビルドのフェーズ | ECS | Web Functions |
|---|---|---|
| provisioning | 7 | 2 |
| download_source | 45 | 1 |
| install | 22 | 0 |
| pre_build | 13 | 4 |
| build | 53 | 44 |
| post_build | 5 | 7 |
| デプロイ(秒) | ECS | Web Functions |
|---|---|---|
| dev | 255.1(7回) | 44.4(11回) |
| prd | 256.0(5回) | 76.2(4回) |
dev の ECS では、CloudFormation スタックの更新完了と、ローリングアップデート後の ECS サービスの安定を待つのに 248 秒かかっていました。Web Functions では、cdkd が Revision を置き換えてエンドポイントを更新するため、この安定待ちが不要になりました。その結果、シングルリージョンの dev は約44秒、4リージョンの prd は約76秒でデプロイが完了するようになりました。
Web Functions のデプロイ時間には、デプロイ後の動作確認が含まれます。ECS の動作確認は別のステージ(約32秒)で、上の表には含まれていません。
グローバルエンドポイントの動作確認
グローバルエンドポイントには、北米と欧州からのリクエストが多く届きました。

稼働する環境数は、リクエスト数に応じて自動で増減します。上のグラフで eu-central-1 のリクエスト数が増えた時間帯には、下のグラフで eu-central-1 の環境数が増えています。
サービスクォータ
このアカウントの Lambda Web Functions のサービスクォータは、GetWebAccountSettings で確認しました。
| リージョン | maxTotalArmVCpus |
maxTotalRateLimit(1秒あたりのリクエスト数) |
|---|---|---|
| ap-northeast-1 | 2,000 | 100,000 |
| us-east-1 | 200 | 10,000 |
| us-west-2 | 200 | 10,000 |
| eu-central-1 | 200 | 10,000 |
Web Functions のクォータは、次のページの Lambda Web Functions の節にまとまっています。
過去24時間のピーク時の vCPU 消費数は、CloudWatch の EndpointActiveEnvironments の1分ごとの最大値に、1環境あたりの vCPU 数(2)をかけて算出しました。東京リージョンの値には、dev と stg の環境も含まれます。
| リージョン | vCPU の上限 | ピーク時の vCPU 消費数 |
|---|---|---|
| ap-northeast-1 | 2,000 | 72 |
| us-east-1 | 200 | 20 |
| us-west-2 | 200 | 34 |
| eu-central-1 | 200 | 40 |
us-east-1 の vCPU の引き上げ(200 から 500)を Service Quotas に申請したところ、「The specified quota is not adjustable.」と返され、受け付けられませんでした。公式ドキュメントによると、vCPU とリクエストレートのクォータは、利用状況に応じて AWS が自動で引き上げます。そのため、引き上げの申請はせず、現状のまま利用する予定です。
コスト
Lambda の月額は 200 USD 前後と見込んでいます。根拠は、2026年10月17日と18日(UTC)に確認できた Cost Explorer(Estimated)の情報です。メモリ、vCPU、リクエストの料金の合計を課金対象のリクエスト数で割ると、100万リクエストあたり約 13.5 USD でした。この値に、オリジンの月間リクエスト数 1,500万をかけると、約 203 USD になります。
| 構成 | 月額(USD) |
|---|---|
| Lambda Web Functions | 200 前後(概算) |
| ECS(EC2 Spot)、2026年9月の推定費用 | 222(EC2 Spot 185、ALB 24、EBS 13) |
Lambda Web Functions でも、Spot インスタンスを活用してコストを最適化していた移行前の EC2 環境と同程度の月額になる見込みです。単価は次の料金ページにあります。
キャッシュミス時の応答性能
CloudFront のアクセスログを使い、キャッシュミスしたリクエストの time-taken(CloudFront がリクエストを受けてから最終バイトをクライアントへ送り終えるまでの時間)を、エッジの空港コード別に比較しました。
キャッシュミスは、CloudFront にキャッシュがなく、リクエストがオリジン(ECS または Web Functions)まで届いた場合です。
対象は、2026年10月17日に公開したこの記事のページ(GET、200)です。直近1週間の /articles 配下で最もリクエストが多かった記事です。ECS と Web Functions のミス件数の合計が9件以上あった17空港を、成田(NRT)からの距離順に並べています。time-taken は中央値(p50)で、単位は秒です。
| 空港 | NRT からの距離(km) | ミス件数(ECS→Web Functions) | ミスの中央値(ECS→Web Functions) | 倍率(ECS ÷ Web Functions) | ヒットの中央値(ECS→Web Functions) |
|---|---|---|---|---|---|
| NRT | 0 | 1→8 | 0.461→0.118 | 3.9 | 0.004→0.004 |
| ICN | 1,258 | 14→15 | 0.236→0.214 | 1.1 | 0.004→- |
| SIN | 5,357 | 3→15 | 0.272→0.371 | 0.7 | 0.004→0.005 |
| MAA | 6,492 | 6→7 | 0.728→0.654 | 1.1 | 0.009→0.022 |
| SEA | 7,656 | 28→42 | 0.442→0.089 | 4.9 | 0.005→0.006 |
| HIO | 7,721 | 11→31 | 0.533→0.161 | 3.3 | 0.017→0.012 |
| HEL | 7,829 | 3→14 | 1.195→0.127 | 9.4 | 0.004→0.031 |
| SFO | 8,227 | 15→12 | 0.632→0.178 | 3.6 | 0.007→0.007 |
| LAX | 8,754 | 11→10 | 0.585→0.157 | 3.7 | 0.005→0.015 |
| AMS | 9,318 | 6→3 | 0.948→0.109 | 8.7 | 0.004→0.012 |
| FRA | 9,367 | 15→12 | 1.196→0.124 | 9.6 | 0.004→0.009 |
| LHR | 9,591 | 7→14 | 0.685→0.121 | 5.6 | 0.005→0.005 |
| ORD | 10,074 | 35→31 | 0.748→0.129 | 5.8 | 0.006→0.005 |
| DFW | 10,322 | 14→11 | 0.903→0.153 | 5.9 | 0.032→0.028 |
| JFK | 10,830 | 2→8 | 0.707→0.090 | 7.9 | 0.008→0.013 |
| IAD | 10,844 | 30→35 | 0.633→0.075 | 8.4 | 0.006→0.007 |
| ATL | 11,002 | 22→10 | 1.094→0.090 | 12.2 | 0.020→0.021 |
ミスの中央値は、北米と欧州の13空港で、ECS の約0.4〜1.2秒から Web Functions の0.075〜0.178秒になり、倍率は3.3〜12.2倍でした。日本から離れた空港ほど、差は大きくなりました。ミスの中央値が0.2秒未満まで短縮された空港では、Web Functions のグローバルエンドポイントにより、遅延が最小のリージョンへリクエストが届いたと考えられます。ICN と MAA はほぼ変わらず、SIN は0.272秒から0.371秒へやや長くなりました。NRT は ECS 側のミスが1件のみのため、比較には使っていません。
一方、ヒット件数が多かった NRT、SIN、SEA、IAD では、ヒットの中央値が移行前後でいずれも0.004〜0.007秒でした。
監視運用
名前空間 AWS/Lambda/Web の標準メトリクス(リクエスト数、CPU、メモリ、同時実行、4XX、レスポンスサイズ)では応答時間を取得できません。そのため、アプリから CloudWatch の OTLP Traces endpoint へトレースを送り、aws/spans のスパンを Logs Insights で集計しています。

確認した時間帯のスパンでは、記事ルート(245件)の p50 は 398 ms、p95 は 1,086 ms で、関連記事 API の p50 は 213 ms でした。前節の time-taken とは、計測の区間と対象が異なります。
まとめ
約10か月間 ECS で稼働していた DevelopersIO のバックエンドを、Lambda Web Functions に移行しました。グローバルエンドポイントにより、送信元に近いリージョンで処理できるようになり、昨今増加している北米・欧州からの AI 検索ボットのリクエストを、東京以外にオフロードできました。
また、Docker イメージのビルドや ECS サービスの安定待ちが不要になり、ビルドとデプロイにかかる時間を短縮できました。加えて、EC2 と ECS タスクの管理が不要になったことで、スパイクアクセスを見越したサイジング、ECS タスクの定期的な更新、リタイア対応などの運用負荷も軽減できる見込みです。
Lambda Web Functions によって、スパイクアクセスへの耐性も向上したと考えていますが、その実証ができたら、あらためて紹介させていただきます。











