Twilio ConversationRelay の WebSocket バックエンドを API Gateway + Lambda で構築したら、初回通話で応答しない問題が発生した

Twilio ConversationRelay の WebSocket バックエンドを API Gateway + Lambda で構築したら、初回通話で応答しない問題が発生した

Twilio ConversationRelay の WebSocket バックエンドを API Gateway + Lambda で構築しました。ECS 構成と比べてコストは約 85% 削減できましたが、初回通話時にユーザーの発話に対して AI が応答しない問題に遭遇しました。CloudWatch Logs の調査でコールドスタートに原因があると考え、EventBridge によるウォームアップで解消しました。
2026.02.19

はじめに

Twilio ConversationRelay を使って AI 音声応答システムを構築する際、WebSocket バックエンドのインフラ選定は検討事項のひとつです。

当初は ECS/Fargate + ALB の構成で運用していましたが、PoC 用途としてはコストが高いです。そこで API Gateway WebSocket API + Lambda の構成に移行したところ、試算では月額コストを約 85% 削減でき、運用もシンプルになりました。

しかし、この構成に移行した直後から、初回通話でユーザーの発話に AI が応答しないという問題が発生しました。 本記事ではこの問題の調査過程と対策を共有します。

構成

移行後の構成は以下の通りです。

Lambda は $connect$default$disconnect の 3 つのルートを 1 つの関数で処理します。セッション状態は Lambda 内に保持しないステートレス設計です。

ECS 構成との比較をまとめると、次のような形です。

項目 ECS/Fargate + ALB API Gateway + Lambda
月額コスト (1,000 コール想定) 約 $130-150 約 $16-20
応答時間 (prompt → 回答) 約 3.5 秒 約 2.5-4.3 秒
運用対象 VPC, ALB, ECS 等 約 15 リソース API Gateway, Lambda 等 約 5 リソース
コールドスタート なし 約 600ms

Lambda 構成はコスト効率と運用の簡素さに優れますが、コールドスタートという固有の特性があります。

発生した問題: 初回通話で AI が応答しない

Lambda 構成への移行後、以下の症状が発生しました。

  • しばらく通話がなかった後の初回通話で、welcomeGreeting (着信時の挨拶音声) は再生されるが、ユーザーが発話しても AI が応答しない
  • 2 回目以降の通話では正常に動作する

ECS 構成では発生しなかった問題であり、Lambda 構成への移行に起因するものと考えられました。

原因調査: CloudWatch Logs の分析

正常系と異常系のログ比較

CloudWatch Logs で正常に動作した通話と問題が発生した通話のログを比較しました。

正常な通話 (2 回目以降):

Lambda invoked { routeKey: "$connect", connectionId: "XXXXX=" }
Lambda invoked { routeKey: "$default", connectionId: "XXXXX=" }
Received message { type: "setup" }
Session setup { sessionId: "XXXXX-...", callSid: "CAXXXXX..." }
Lambda invoked { routeKey: "$default", connectionId: "XXXXX=" }
Received message { type: "prompt" }
Processing prompt { utteranceLength: 24 }
Intent detected { intent: "NORMAL" }
RAG search completed { hitCount: 3, topScore: 0.72 }
Answer sent { answerLength: 185, totalDurationMs: 3812 }

問題のある通話 (初回):

INIT_START Runtime Version: nodejs:22.v45
Lambda invoked { routeKey: "$connect", connectionId: "YYYYY=" }
Lambda invoked { routeKey: "$default", connectionId: "YYYYY=" }
Received message { type: "setup" }
Session setup { sessionId: "YYYYY-...", callSid: "CAYYYYY..." }
(... prompt メッセージが届かないまま約 34 秒経過 ...)
Lambda invoked { routeKey: "$disconnect", connectionId: "YYYYY=" }

正常な通話では setup の後に ConversationRelay からユーザーの発話内容を含む prompt メッセージが届きますが、問題のある通話では届いていません。

INIT_START との関係

INIT_START は Lambda コールドスタートが発生した際に記録されるログです。全通話のログを INIT_START の有無と prompt メッセージの有無で分類した結果、ここが関係していそうでした。

通話 INIT_START prompt 受信 結果
通話 A あり (Init Duration: 650ms) なし 異常
通話 B なし あり 正常
通話 C あり (Init Duration: 720ms) なし 異常
通話 D なし あり 正常
通話 E あり (Init Duration: 603ms) なし 異常
通話 F なし あり 正常

調査した約 25 件の通話のうち、コールドスタートが発生した通話ではほぼすべてで prompt が届きませんでした。

コールドスタートが発生すると、$connect ハンドラの応答に Init Duration (約 650ms) 分の遅延が加わります。ウォームスタート時は $connect が約 10-20ms で完了するのに対し、コールドスタート時は約 650-720ms かかります。この遅延が ConversationRelay 側の STT (Speech-to-Text, 音声認識) の初期化に影響し、prompt メッセージが送信されなくなっている可能性が高いと考えました。

対策: EventBridge による Lambda ウォームアップ

コールドスタートを回避するため、EventBridge のスケジュールルールで Lambda を 5 分ごとに呼び出し、実行環境をウォーム状態に保つようにしました。

Lambda ハンドラーでのウォームアップ検知

EventBridge からの呼び出しを検知し、即座にレスポンスを返します。

export async function handler(
    event: APIGatewayProxyWebsocketEventV2 | Record<string, unknown>
): Promise<APIGatewayProxyResultV2> {
    // EventBridge からのウォームアップ呼び出しを検知
    if ('source' in event && event.source === 'aws.events') {
        console.log('Warmup invocation');
        return { statusCode: 200, body: 'Warm' };
    }

    // 以降は通常の WebSocket メッセージ処理
    const wsEvent = event as APIGatewayProxyWebsocketEventV2;
    // ...
}

Terraform の EventBridge リソース定義

# 5 分ごとに Lambda を呼び出してウォーム状態を維持
resource "aws_cloudwatch_event_rule" "lambda_warmup" {
  name                = "${var.project_name}-warmup-${var.environment}"
  description         = "Keep Lambda warm to avoid cold start issues with ConversationRelay STT"
  schedule_expression = "rate(5 minutes)"
}

resource "aws_cloudwatch_event_target" "lambda_warmup" {
  rule = aws_cloudwatch_event_rule.lambda_warmup.name
  arn  = aws_lambda_function.ws_handler.arn
}

resource "aws_lambda_permission" "warmup" {
  statement_id  = "AllowCloudWatchWarmup"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.ws_handler.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.lambda_warmup.arn
}

動作確認

対策の適用後、以下のように動作を確認できました。

  1. EventBridge によるウォームアップ呼び出しのログ:
INIT_START Runtime Version: nodejs:22.v45  Init Duration: 654.12 ms
Warmup invocation
REPORT RequestId: XXXXX  Duration: 2.85 ms  Billed Duration: 657 ms  Init Duration: 654.12 ms
  1. その直後の通話ログ (Init Duration なし = ウォームスタート):
Lambda invoked { routeKey: "$connect", connectionId: "XXXXX=" }
Lambda invoked { routeKey: "$default", connectionId: "XXXXX=" }
Received message { type: "setup" }
Session setup { sessionId: "XXXXX-..." }
Lambda invoked { routeKey: "$default", connectionId: "XXXXX=" }
Received message { type: "prompt" }
Processing prompt { utteranceLength: 24 }
Answer sent { answerLength: 185, totalDurationMs: 3812 }

ウォームアップによりコールドスタートが回避され、prompt が正常に受信されるようになりました。

まとめ

Twilio ConversationRelay の WebSocket バックエンドを API Gateway + Lambda で構築した際に発生した、初回通話でユーザーの発話に AI が応答しない問題について調査と対策を行いました。CloudWatch Logs から、Lambda コールドスタートが原因である可能性が高いことがわかりました。EventBridge による 5 分間隔のウォームアップを導入したところ、コールドスタートが回避され問題は解消しました。

API Gateway + Lambda 構成は ECS 構成と比較してコストと運用の面で大きな利点がありますが、ConversationRelay のように WebSocket 接続の確立速度に敏感なサービスと組み合わせる際は、コールドスタートを考慮する必要があります。

この記事をシェアする

FacebookHatena blogX

関連記事