AWS LambdaとAPI Gatewayの組み合わせは、サーバーレスアーキテクチャにおけるAPI構築の定番パターンです。インフラ管理が不要で、リクエスト数に応じた従量課金という特性から、スタートアップから大企業まで幅広く採用されています。
しかし、実際に本番運用してみると「コールドスタートが遅い」「デバッグが難しい」「コスト管理が予想以上に大変」といった課題に直面します。筆者は過去3年間で複数のサーバーレスAPIを本番運用してきた経験をもとに、設計段階で押さえるべきポイントと運用ノウハウを解説します。
Lambda関数の粒度は、プロジェクト規模とチーム構成に応じて選択します。代表的なパターンを紹介します。
1つのLambda関数で全てのAPIエンドポイントを処理するパターンです。小規模なAPIやプロトタイプに向いています。
// handler.ts - 単一関数で全ルートを処理
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'
export const handler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
const { httpMethod, path } = event
// ルーティング
if (path === '/users' && httpMethod === 'GET') {
return await listUsers(event)
}
if (path.match(/^\/users\/[^/]+$/) && httpMethod === 'GET') {
return await getUser(event)
}
if (path === '/users' && httpMethod === 'POST') {
return await createUser(event)
}
return {
statusCode: 404,
body: JSON.stringify({ message: 'Not Found' }),
}
}
async function listUsers(event: APIGatewayProxyEvent) {
// DynamoDB等からユーザー一覧を取得
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ users: [] }),
}
}
エンドポイントごと、またはリソースごとにLambda関数を分割するパターンです。中〜大規模なAPIに推奨されます。関数ごとにメモリやタイムアウトを最適化でき、デプロイの影響範囲も限定できます。
インフラはコードで管理すべきです。AWS SAM(Serverless Application Model)を使った構成例を示します。
# template.yaml - AWS SAMテンプレート
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Runtime: nodejs20.x
Timeout: 30
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref UsersTable
STAGE: !Ref Stage
Parameters:
Stage:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- prod
Resources:
ApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: !Ref Stage
Cors:
AllowOrigin: "'*'"
AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'"
AllowHeaders: "'Content-Type,Authorization'"
ListUsersFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handlers/users.list
Events:
Api:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users
Method: GET
CreateUserFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handlers/users.create
Events:
Api:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users
Method: POST
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub '${Stage}-users'
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
コールドスタートはサーバーレスAPIの最大の課題です。初回リクエストや一定時間アクセスがなかった後のリクエストで、Lambda実行環境の初期化に時間がかかります。
# Provisioned Concurrencyの設定(SAMテンプレート)
ListUsersFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handlers/users.list
AutoPublishAlias: live
ProvisionedConcurrencyConfig:
ProvisionedConcurrentExecutions: 5
筆者の経験では、P99レイテンシが1秒以下を求められるAPIにはProvisioned Concurrencyを設定し、それ以外は依存関係の最適化で対応するのがコストパフォーマンスに優れたアプローチです。
サーバーレス環境では、構造化ログとCloudWatchの活用が運用の要です。
// 構造化ログの実装例
const logger = {
info: (message: string, context: Record<string, unknown> = {}) => {
console.log(JSON.stringify({
level: 'INFO',
message,
timestamp: new Date().toISOString(),
requestId: context.requestId,
...context,
}))
},
error: (message: string, error: Error, context: Record<string, unknown> = {}) => {
console.error(JSON.stringify({
level: 'ERROR',
message,
errorName: error.name,
errorMessage: error.message,
stackTrace: error.stack,
timestamp: new Date().toISOString(),
...context,
}))
},
}
CloudWatch Logs Insightsを使えば、構造化ログに対して強力なクエリを実行できます。エラー率の監視やレイテンシの分析が容易になり、障害対応の速度が格段に上がります。
Lambda + API Gatewayの組み合わせは強力ですが、全てのユースケースに最適とは限りません。長時間実行が必要な処理、WebSocket接続が中心のアプリケーション、予測可能な高トラフィックを処理するAPIでは、ECSやEC2の方がコスト効率が良い場合もあります。サーバーレスの特性を正しく理解し、プロジェクトの要件に合致するか慎重に判断してから採用しましょう。