目次
サーバーレス(AWS Lambda・Cloud Functions・Vercel Functions など)を使っていると、「たまに最初のリクエストだけ遅い」という経験をする。これが「コールドスタート」問題だ。LLM APIを呼ぶアプリやAIサービスでは、コールドスタートによるレイテンシが特に問題になりやすい。
サーバーレスの仕組みとコールドスタートの発生
サーバーレスでは、リクエストが来たときだけコンテナを起動し、処理後は停止する(スケールゼロ)。これがコスト効率の源泉だが、初回起動に時間がかかる。
コールドスタート(初回または再起動後):
リクエスト到着
↓ 200ms〜数秒
コンテナ起動
↓
ランタイム(Node.js/Python)の初期化
↓
アプリケーションコードのロード
↓
ハンドラー実行
↓
レスポンス返却
ウォームスタート(起動済みコンテナ再利用):
リクエスト到着
↓ 数ms
ハンドラー実行
↓
レスポンス返却
コールドスタートが発生するタイミング
- 関数のデプロイ直後
- 一定時間(通常15〜30分)リクエストがなかった後
- 同時リクエスト数が増えてスケールアウトが必要なとき
- メモリや設定の変更後
コールドスタートの時間測定
// AWS Lambda でのコールドスタート検出
let isFirstInvocation = true;
export const handler = async (event) => {
const startTime = Date.now();
const coldStart = isFirstInvocation;
isFirstInvocation = false;
// 処理...
const result = await processEvent(event);
const duration = Date.now() - startTime;
console.log(JSON.stringify({
coldStart,
duration,
memoryUsed: process.memoryUsage().heapUsed
}));
return result;
};
# Python Lambda での計測
import time
import json
_cold_start = True
_init_start = time.time()
def lambda_handler(event, context):
global _cold_start
handler_start = time.time()
is_cold = _cold_start
_cold_start = False
# 処理...
result = process_event(event)
print(json.dumps({
"cold_start": is_cold,
"init_duration_ms": (handler_start - _init_start) * 1000 if is_cold else 0,
"handler_duration_ms": (time.time() - handler_start) * 1000
}))
return result
言語・ランタイム別のコールドスタート時間
| ランタイム | コールドスタート時間(目安) | 理由 |
|---|---|---|
| Python | 100〜500ms | 比較的軽量 |
| Node.js | 100〜500ms | V8 JIT が速い |
| Go | 50〜200ms | バイナリが軽量 |
| Rust (WASM) | 10〜100ms | 非常に軽量 |
| Java | 1〜10秒 | JVMの初期化が重い |
| .NET | 500ms〜3秒 | CLRの初期化 |
AI/ML ライブラリを含む場合: NumPy・TensorFlow・PyTorch などをインポートすると、これだけで数百ms〜数秒追加される。
コールドスタート対策
1. パッケージサイズの削減
インポートするモジュールが増えるほどコールドスタートが遅くなる。
# ❌ 避ける: 使わないものまでインポート
import pandas as pd
import numpy as np
import sklearn
import tensorflow as tf
# ✅ 推奨: 必要なものだけインポート
from sklearn.preprocessing import StandardScaler
# または遅延インポート
def handler(event, context):
import numpy as np # ハンドラー内で必要なときにインポート
// ❌ 全モジュールを読み込む
const { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');
// ✅ 使うものだけ
const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
// ✅ ESM + Tree-shaking(バンドラーで未使用コードを削除)
import { GetObjectCommand } from '@aws-sdk/client-s3';
2. 初期化コードをハンドラー外に移動
コンテナ起動時に一度だけ実行されるコードはハンドラー外に書く。
import boto3
import json
# ❌ ハンドラー内で毎回初期化
def handler(event, context):
client = boto3.client('s3') # 毎リクエスト初期化
# ...
# ✅ ハンドラー外(コンテナ起動時に一度だけ実行)
client = boto3.client('s3')
config = json.loads(open('config.json').read())
def handler(event, context):
# client と config は再利用される
response = client.get_object(Bucket='my-bucket', Key='data.json')
# ...
// ✅ Node.js: モジュールレベルで初期化
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
// ハンドラー外でクライアントを初期化(ウォーム時は再利用)
const dynamodb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
export const handler = async (event) => {
// dynamodb を再利用
};
3. プロビジョニング済みコンカレンシー(AWS Lambda)
コンテナを常に起動済み状態に保つ。コールドスタートをゼロにできる代わりにコストがかかる。
# serverless.yml
functions:
api:
handler: src/handler.main
provisionedConcurrency: 5 # 常に5インスタンス待機
4. ウォームアップリクエスト(スケジュール定期実行)
定期的にダミーリクエストを送ってコンテナをウォームな状態に保つ。
# AWS EventBridge で5分ごとにウォームアップ
Resources:
WarmupRule:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "rate(5 minutes)"
Targets:
- Arn: !GetAtt MyFunction.Arn
Input: '{"warmup": true}'
def handler(event, context):
# ウォームアップリクエストは即座に返す
if event.get('warmup'):
return {'statusCode': 200, 'body': 'warm'}
# 通常の処理...
5. 軽量ランタイムへの移行
JavaからGoやRustへの移行は、コールドスタートを大幅に改善できる。
# AWS Lambda on Rust (cargo-lambda)
cargo lambda build --release
cargo lambda deploy my-function
6. Edge Functions——地理的に分散配置
Cloudflare Workers・Vercel Edge Functions は、V8 isolate を使って従来のコンテナより大幅に速い(1ms未満のコールドスタートも可能)。
// Cloudflare Workers(エッジで実行)
export default {
async fetch(request, env) {
// コールドスタートほぼなし
return new Response('Hello from the edge!');
}
};
LLMアプリでのコールドスタート対策
LLMアプリではHTTPクライアントの初期化やAPIキーの読み込みも考慮が必要だ。
import os
from anthropic import Anthropic
# ハンドラー外で初期化(再利用)
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
def handler(event, context):
if event.get('warmup'):
return {'statusCode': 200}
message = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": event['prompt']}]
)
return {
'statusCode': 200,
'body': message.content[0].text
}
まとめ
コールドスタートはサーバーレスアーキテクチャのトレードオフ(コスト効率 vs レイテンシ)だ。
- 原因: コンテナの初期化・ランタイム起動・アプリコードのロードに時間がかかる
- 対策1: パッケージサイズを最小化(不要なインポートを削除)
- 対策2: 初期化コードをハンドラー外に移動して再利用
- 対策3: プロビジョニング済みコンカレンシーで常時ウォーム(コスト増)
- 対策4: スケジュール実行でウォームアップ(低コスト)
- 対策5: Go・Rustなど軽量ランタイムへの移行
- 対策6: Edge Functions(V8 isolate)で超低レイテンシを実現
LLMアプリでは「LLM推論の数秒」に比べれば数百msのコールドスタートは無視できる場合も多い。まず計測し、ユーザー体験に実際に影響しているか確認してから対策を打つのが合理的だ。
免責事項 — 掲載情報は執筆時点のものです。料金・機能は変更される場合があります。最新情報は各公式サイトをご確認ください。