DevelopersIO のバックエンドを ECS から Lambda Web Functions に移行して、マルチリージョン化してみた

DevelopersIO のバックエンドを ECS から Lambda Web Functions に移行して、マルチリージョン化してみた

ECS で稼働していた DevelopersIO の Next.js バックエンドを AWS Lambda Web Functions へ移行しました。グローバルエンドポイントを採用し、アクセスの多い記事の実測で、北米・欧州のエッジでキャッシュミスしたときの応答時間が短くなりました。ビルドとデプロイの時間も短くなりました。
2026.10.19

はじめに

2026年10月12日に一般提供(GA)が始まった AWS Lambda Web Functions へ、DevelopersIO の Next.js バックエンドを移行しました。

https://aws.amazon.com/about-aws/whats-new/2026/10/aws-lambda-web-functions/

これまでバックエンドは ECS を約10か月利用していました。

https://dev.classmethod.jp/articles/developersio-public-subnet-less-ecs/

この記事では、移行後の構成、ビルドとデプロイの所要時間、4リージョンでのリクエスト処理、月額コストの試算、キャッシュミス時の応答性能、サービスクォータと監視運用を紹介します。

構成図

移行前(ECS)

移行前の構成図:CloudFront から VPC origin と内部 ALB を経由して ECS(EC2)に届く。静的ファイルは S3、コンテナイメージは ECR

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

移行後(Web Functions)

移行後の構成図:CloudFront から OAC を経由して Web Functions のグローバルエンドポイントに届き、4リージョンの Web Functions が処理する。静的ファイルと ZIP は S3 に置く

前段の 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 のリクエスト数が増えた時間帯には、下のグラフで 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 の節にまとまっています。

https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html

過去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 環境と同程度の月額になる見込みです。単価は次の料金ページにあります。

https://aws.amazon.com/lambda/pricing/

キャッシュミス時の応答性能

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 で集計しています。

https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLPEndpoint.html

ルート別の応答時間(スパン時間、1%サンプリング)

確認した時間帯のスパンでは、記事ルート(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 によって、スパイクアクセスへの耐性も向上したと考えていますが、その実証ができたら、あらためて紹介させていただきます。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事