Cloud Functions vs Traditional Backend: When Serverless Makes Sense
サーバーレス関数と従来のバックエンドを実世界で比較 — コスト、レイテンシー、開発者体験、そしてそれぞれのアプローチが優位に立つ場面を網羅。

私が始めるすべてのグリーンフィールドプロジェクトでは、常に同じ決断を迫られます。従来のサーバーを立ち上げるか、それともクラウド関数を書くか?イベント駆動型ワークフローにはFirebase Functions、APIエンドポイントにはAWS Lambda、そしてその間のすべてにはExpress/Fastifyサーバーと、両方のシステムを本番環境に投入してきた経験から、それぞれのアプローチが実際にいつ優位に立つのかについて、かなり微妙な意見を持っています。答えは、予想通り「状況による」ですが、その「状況」が具体的に何を指すのかを詳しく探る価値はあります。
2つのアプローチの定義
比較する前に、それぞれの意味を明確にしておきましょう。
従来のバックエンドとは、VM、コンテナ、またはマネージドコンピューティングプラットフォームにデプロイされた、長時間実行されるサーバープロセス(Node.js Expressアプリ、Python FastAPIサーバー、Go HTTPサーバーなど)を指します。プロセスは起動し、実行状態を維持し、リクエストが到着すると処理します。デプロイターゲットには、EC2、Cloud Run、ECS、Railway、Fly.io、またはベアVPSが含まれます。
クラウド関数(サーバーレス関数)は、イベントに応答して実行される個々の関数ハンドラーです。プラットフォームがコンピューティングインフラストラクチャを完全に管理します。コードは実行され、応答を返し、実行環境は次の呼び出しのために永続化される場合とされない場合があります。プロバイダーには、AWS Lambda、Google Cloud Functions、Firebase Functions、Azure Functions、Cloudflare Workersなどがあります。
この区別は言語やフレームワークに関するものではなく、実行モデルに関するものです。従来のバックエンドは自身のプロセスライフサイクルを所有します。クラウド関数はそうではありません。
コールドスタートの現実
コールドスタートは、サーバーレスの最も議論される制限であり、2026年現在の現実は、議論が示唆するよりも微妙です。
コールドスタート中に実際に何が起こるか
クラウド関数が最近呼び出されていない場合(または同時呼び出しが利用可能なウォームインスタンスを超えた場合)、プラットフォームは次のことを行う必要があります。
- 新しい実行環境(マイクロVMまたはコンテナ)をプロビジョニングする
- デプロイパッケージをダウンロードして展開する
- ランタイム(Node.js、Pythonなど)を初期化する
- 初期化コード(モジュールインポート、SDKセットアップ、DB接続)を実行する
- 実際の関数ハンドラーを実行する
ステップ1〜3はプラットフォームのオーバーヘッドです。ステップ4は、コードの選択が非常に重要になる部分です。
実際のコールドスタート時間
実際のプロジェクトで、さまざまなプラットフォームと構成で測定したコールドスタート時間は次のとおりです。
| プラットフォーム | ランタイム | バンドルサイズ | コールドスタート |
|---|---|---|---|
| AWS Lambda | Node.js 20 | 5 MB | 250-400ms |
| AWS Lambda | Node.js 20 | 50 MB | 800-1200ms |
| AWS Lambda | Python 3.12 | 10 MB | 400-600ms |
| Firebase Functions v2 | Node.js 20 | 15 MB | 600-1000ms |
| Cloudflare Workers | JavaScript | 1 MB | 0-5ms |
| Google Cloud Functions v2 | Node.js 20 | 10 MB | 300-500ms |
Cloudflare Workersは、コンテナではなくV8アイソレートを使用するため、コンテナの起動ペナルティを完全に排除しており、例外です。トレードオフとして、より制約されたランタイム環境になります。
コールドスタートの軽減策
実際に役立ついくつかの戦略があります。
プロビジョニングされた同時実行(Lambda): 常にN個のインスタンスをウォーム状態に保ちます。プロビジョニングされた容量のGB秒あたり$0.0000041667かかります。256MBの関数の場合、ウォームインスタンスあたり月額約$3.20です。レイテンシーに敏感なエンドポイントには価値があります。
最小インスタンス(Cloud Run、Firebase Functions v2): 同じ概念ですが、名前が異なります。Cloud Runはアイドルインスタンスに対して割引料金を請求します。
バンドルサイズの削減: これが最も影響が大きく、労力に対する効果が高いです。ツリーシェイキング、開発依存関係の除外、軽量なSDKクライアントの使用により、初期化時間が劇的に短縮されます。
// Bad: AWS SDK全体をインポートする(バンドルに40MB以上追加)
import AWS from 'aws-sdk';
// Good: S3クライアントのみをインポートする(約3MB追加)
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
遅延初期化: モジュールスコープでデータベース接続を確立したり、重いリソースをロードしたりしないでください。最初の呼び出しで初期化し、ウォームな呼び出し間で再利用します。
import { Pool } from 'pg';
let pool: Pool | null = null;
function getPool(): Pool {
if (!pool) {
pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 1, // Lambdaインスタンスごとの単一接続
});
}
return pool;
}
export async function handler(event: APIGatewayEvent) {
const db = getPool();
const result = await db.query('SELECT * FROM users WHERE id = $1', [event.pathParameters?.id]);
return { statusCode: 200, body: JSON.stringify(result.rows[0]) };
}
異なるスケールでのコスト比較
コストは、トラフィックの規模に応じて経済性が逆転するため、議論が面白くなる点です。
低トラフィック(1日あたり1,000〜50,000リクエスト)
この規模では、サーバーレスはほとんどの場合、安価です。多くのスタートアップやサイドプロジェクトがここに該当します。
1日あたり30,000リクエスト、平均実行時間200ms、メモリ256MBのLambdaコスト:
- リクエスト料金:90万リクエスト/月 x $0.20/百万 = $0.18
- コンピューティング料金:90万 x 0.2秒 x 0.25GB x $0.0000166667 = $0.75
- 合計:約$1/月
同等の従来のサーバー(AWSのt4g.small):
- オンデマンド:$12.26/月
- 1年間のSavings Plan利用時:約$8/月
低トラフィックでは、95%アイドル状態の常時稼働サーバーに料金を支払っていることになります。
中トラフィック(1日あたり100,000〜1,000,000リクエスト)
これは、比較が拮抗するクロスオーバーゾーンです。
1日あたり500,000リクエスト、平均実行時間200ms、メモリ256MBのLambdaコスト:
- リクエスト料金:1500万/月 x $0.20/百万 = $3.00
- コンピューティング料金:1500万 x 0.2秒 x 0.25GB x $0.0000166667 = $12.50
- 合計:約$15.50/月
t4g.medium(2 vCPU、4GB RAM):
- オンデマンド:$24.53/月
- Savings Plan利用時:約$16/月
コストは同程度ですが、従来のサーバーはこのトラフィックをかなりの余裕を持って処理します。トラフィックが安定している場合は、サーバーの方がわずかに安価です。トラフィックがスパイク状(平日集中型、イベント駆動型バースト)の場合、Lambdaの呼び出しごとの課金により、静かな期間には料金が発生しません。
高トラフィック(1日あたり5,000,000以上のリクエスト)
この規模では、サーバーレスは持続的なワークロードに対してほとんどの場合、より高価になります。
1日あたり5,000,000リクエスト、平均実行時間200ms、メモリ512MBのLambdaコスト:
- リクエスト料金:1億5000万/月 x $0.20/百万 = $30
- コンピューティング料金:1億5000万 x 0.2秒 x 0.5GB x $0.0000166667 = $250
- 合計:約$280/月
c6g.large(2 vCPU、4GB RAM)とオートスケーリンググループ:
- Savings Planを利用した2インスタンス:約$60/月
- ロードバランサー込み:約$16/月
- 合計:約$76/月
この規模では、従来のアプローチの方が3.7倍安価であり、トラフィックが増加するにつれてその差は広がります。
考慮すべき隠れたコスト
Lambdaの課金は全体像ではありません。API Gateway(100万リクエストあたり$1〜$3.50)、CloudWatch Logs(取り込みGBあたり$0.50)、およびX-Rayトレースを使用する場合はそれらも追加されます。従来型の場合、ロードバランサーのコスト、監視、インスタンスとデプロイの管理にかかる運用時間も追加されます。
開発者体験の違い
サーバーレスDX
利点:
- 管理するインフラストラクチャがない(パッチ適用、スケーリング設定なし)
- 関数ごとのデプロイにより、影響範囲が小さい
- エミュレーターによるローカル開発(Firebase Emulator Suite、SAM CLI、Serverless Framework offline)
- プラットフォームのロギングとトレースによる組み込みの可観測性
課題:
- ローカル開発が本番環境の動作と完全に一致することはない
- 分散された関数間の呼び出しのデバッグは、モノリスをステップ実行するよりも難しい
- デプロイサイズ制限(Lambda:解凍後250MB)により、依存関係の選択が制約される
- IAMと権限の設定は冗長で間違いやすい
- コールドスタートにより、デプロイされた関数に対してテストする際の開発イテレーションループが遅くなる
従来のバックエンドDX
利点:
- ローカル開発環境が本番環境である(同じプロセス、同じ状態)
- デバッグは簡単 — デバッガーをアタッチし、ブレークポイントを設定し、ステップ実行する
- デプロイサイズ制限がない
- ミドルウェアパターン、リクエストライフサイクルフック、グローバルエラーハンドリングが自然
- WebSocketサポート、長時間実行プロセス、バックグラウンドジョブが追加サービスなしで機能する
課題:
- インフラストラクチャのライフサイクル(更新、スケーリング、ヘルスチェック)を自分で管理する必要がある
- デプロイにはオーケストレーションが必要(ローリングアップデート、ヘルスチェックの猶予期間)
- スケーリングには設定が必要(オートスケーリンググループ、コンテナオーケストレーション)
- 監視とロギングには明示的なセットアップが必要
フレームワークのギャップは縮まっている
SST (Serverless Stack) や Architect のようなフレームワークは、サーバーレスのDXを劇的に改善しました。特にSSTは「Live Lambda」開発モードを提供し、ローカルで実行中のコードがLambdaの呼び出しをリアルタイムで処理するため、デプロイ-テストサイクルを完全に排除します。
従来型では、Railway、Fly.io、Renderのようなプラットフォームがgit pushによるデプロイを簡素化しました。従来のバックエンドを運用する際の運用負担は大幅に減少しています。
サーバーレスが優位に立つ場合
イベント駆動型ワークロード
コードがイベント(ファイルアップロード、データベース変更、キューメッセージ、スケジュールされたタスク)に応答して実行される場合、サーバーレスは自然な選択肢です。ポーリングループやリスナープロセスを維持する必要はありません。プラットフォームが必要なときに正確にコードをトリガーします。
// Firebase Functions: Firestoreドキュメント作成時にトリガーされる
import { onDocumentCreated } from 'firebase-functions/v2/firestore';
export const onOrderCreated = onDocumentCreated('orders/{orderId}', async (event) => {
const order = event.data?.data();
if (!order) return;
// 確認メールを送信
await sendEmail(order.customerEmail, {
template: 'order-confirmation',
data: { orderId: event.params.orderId, items: order.items },
});
// 在庫を更新
await updateInventory(order.items);
// レストランに通知
await sendPushNotification(order.restaurantId, {
title: 'New Order',
body: `Order #${event.params.orderId} received`,
});
});
散発的または予測不可能なトラフィック
月曜日の朝に50リクエスト、週の残りの期間は2リクエストしか受け取らない社内ツール。サードパーティAPIからのイベントを処理するWebhookレシーバー。1日1回30秒間実行されるスケジュールレポートジェネレーター。これらのワークロードはサーバーレスではほぼゼロコストであり、常時稼働のインフラストラクチャでは無駄な費用がかかります。
低〜中トラフィックのAPIエンドポイント
1日数千リクエストを処理するスタートアップのMVP APIの場合、サーバーレスは最小限の運用オーバーヘッドとほぼゼロコストで機能的なバックエンドを提供します。ビジネスロジックに完全に集中できます。
並列データ処理
10,000枚の画像を処理したり、500本のビデオをトランスコードしたり、大規模なデータセットで分析を実行したりする必要がありますか?数千の同時Lambda呼び出しにファンアウトし、並列で処理し、使用したコンピューティング時間に対してのみ料金を支払います。従来のサーバーで同じ並列処理を実現するには、そのピーク容量をプロビジョニング(および支払い)する必要があります。
従来のバックエンドが優位に立つ場合
永続的な接続
WebSocket接続、Server-Sent Events、gRPCストリーム、ロングポーリングはすべて、クライアントとサーバー間の永続的な接続を必要とします。Lambda関数には15分の実行制限があり、長期間の接続には設計されていません。
API Gateway WebSocket APIやAWS IoT Coreでこれを回避することはできますが、その複雑さとコストは、Cloud Runやコンテナプラットフォーム上のシンプルなWebSocketサーバーを超えることがよくあります。
複雑なリクエストパイプライン
リクエストが認証、レート制限、入力検証、ビジネスロジック、データベース操作、キャッシュ更新、イベント発行、応答フォーマットを通過する場合、Express/Fastify/Koaのミドルウェアパイプラインパターンは、クラウド関数のフラットハンドラーパターンよりも人間工学的に優れています。
// 従来型:クリーンなミドルウェアパイプライン
app.use(cors());
app.use(helmet());
app.use(rateLimiter);
app.use(authenticate);
app.use('/api/v1', apiRouter);
app.use(errorHandler);
これをLambdaで再現するには、Middyのようなフレームワーク(重さと複雑さを追加する)を使用するか、関数間でセットアップコードを重複させる必要があります。
ステートフルな操作
アプリケーションがインメモリ状態(キャッシュ、接続プール、レートリミッターカウンター、セッションデータ)を維持する場合、従来のサーバーはこれを自然に処理します。Lambda関数はウォームな呼び出し間で状態を再利用できますが、それに依存することはできません。永続化する必要がある状態は、外部サービス(Redis、DynamoDB)を必要とし、これはレイテンシーとコストを追加します。
厳密なレイテンシー要件
リアルタイム入札、ゲームサーバー、金融取引など、ミリ秒単位のすべてが重要なAPIの場合、コールドスタートによって導入される予測不可能なレイテンシーは、プロビジョニングされた同時実行を使用しても許容できません。接続プールとウォームキャッシュを備えた適切にチューニングされたサーバープロセスは、サーバーレスでは保証できない一貫した10ms未満の応答時間を提供します。
ハイブリッドアプローチ
実際には、私が構築するほとんどのプロダクションシステムはハイブリッドになります。コアAPIは従来のバックエンド(Cloud Runまたはコンテナプラットフォーム)で実行され、補助的なワークロードはクラウド関数で実行されます。
実用的なハイブリッドアーキテクチャ
Client
│
├─→ API Gateway / Load Balancer
│ └─→ Cloud Run (main API)
│ ├─→ PostgreSQL (Cloud SQL)
│ ├─→ Redis (Memorystore)
│ └─→ Pub/Sub