最近、学校向けに小規模な英語学習支援システムをほぼ1人で作っています。生徒が Speaking (発話) の練習を行い、AI が自動で採点するシステムです。
開発中、「複数人で同時に採点を行うと 500 エラーが発生する」という問題に直面しました。
本記事では、問題の調査から解決までの過程を共有します。似たようなシステムを作っている方がいれば、ぜひアドバイスやご意見をいただけると嬉しいです。
要件
- ブラウザで動作する Web アプリ(PC / タブレット / スマホ対応)
- 生徒が録音した音声ファイルを AI がリアルタイム採点
- クラス単位(約50人)で同時に利用する想定
- なるべく低コストで運用したい
採点の流れ
音声ファイルを受け取ってから採点結果を返すまでの流れ:
| 順序 | 処理 | 技術 |
|---|---|---|
| 1 | 音声変換 | ffmpeg(WAV 形式に変換) |
| 2 | 無音検出 | VAD(音声区間を検出) |
| 3 | 文字起こし | Whisper |
| 4 | AI 評価 | ChatGPT(発音・文法・総合評価) |
VAD(Voice Activity Detection)とは
VAD は音声区間検出の技術です。「事前に無音解析を行いたい」という要件があり導入しました。VAD ライブラリは WAV 形式の音声を必要とするため、事前に ffmpeg で変換しています。
(もし VAD が不要であれば、そもそも ffmpeg 不要 → CPU Throttling は発生しなかったと思います)
システム構成
| カテゴリ | 技術 |
|---|---|
| ホスティング | Firebase Hosting |
| ストレージ | Firebase Storage |
| データベース | Firestore |
| バックエンド | Cloud Run |
アーキテクチャ
Before(同期処理)

シンプルな同期処理方式。リクエストを受けて、採点処理を行い、結果を返す。
After(非同期 + Cloud Run Job)

非同期処理方式。採点処理を Cloud Run Job に分離し、結果は Firestore 経由で通知。
問題の発端
導入した学校から「複数人で試験の採点を行うと 500 エラーが発生する」という報告を受けました。
1人で採点する分には問題なく動作するのに、複数人が同時に採点を実行すると失敗するケースがある。典型的な負荷問題かと思いましたが、想定外の原因が次々と見つかりました。
原因1: Firebase Hosting の 60 秒タイムアウト制限
発見の経緯
採点処理自体は正常に完了しているケースでも、フロントエンドには 502 エラーが返っていることがわかりました。
処理時間を計測すると、60 秒を超えた時点でエラーが発生していました。
原因
Cloud Run 側のタイムアウトは 300 秒 (default) なので正直問題ないと思っていましたが、Firebase Hosting に 60 秒のタイムアウト制限があり。これが起因となっていました。この値は変更できないようです。

Speaking 採点は音声ファイルの処理(ffmpeg)、文字起こし(Whisper)、評価(ChatGPT)と複数の処理を直列で行うため、60 秒を超えることがありました。
解決策
REST の同期応答を待つ方式から、Firestore の onSnapshot による非同期監視に変更しました。

これで 60 秒制限を回避できました。しかし、ここで新たな問題が発生しました。
原因2: Cloud Run の CPU Throttling
発見の経緯
Firestore 監視方式に変更後、採点処理が極端に遅くなりました。同じ処理なのに、遅いときは 100 秒以上かかる。これは明らかに異常でした。
原因
Cloud Run のデフォルト設定では、「CPU はリクエスト処理中のみ割り当てられる」という仕様があります。つまり、HTTP レスポンスを返却した時点で CPU の割り当てが停止 します。

原因1 の対応で「先にレスポンスを返して、処理はバックグラウンドで」という方式に変更したことが、この問題を引き起こしていました。
解決策
Cloud Tasks を使って処理を別の Cloud Run Service に移動しました。

Cloud Tasks 経由で呼び出された処理は「新しい HTTP リクエスト」として扱われるため、CPU 制限を受けません。これで処理速度は大幅に改善しました。
しかし、大量同時実行時にはまだ不安定さが残っていました。
原因3: ffmpeg の競合
発見の経緯
50 人同時実行のベンチマークを行うと、処理時間にばらつきが大きく、一部で 100 秒を超えるケースがありました。
原因
Cloud Run Service は、複数のリクエストを 1 つのコンテナ内で並列処理します。ffmpeg は CPU とメモリを大量に消費するため、同一コンテナ内で複数の ffmpeg が同時実行されると競合が発生していました。

解決策
Cloud Tasks + Cloud Run Service から Cloud Run Job に移行しました。Cloud Run Job は 1 タスク = 1 コンテナで実行されるため、リソース競合が発生しません。

これでさらなる高速化と安定性向上を実現しました。
検討したが採用しなかった対策
Cloud Run Service の「最小インスタンス数を 1 に設定」する方法も検討しました。これによりコールドスタートを回避し、常にウォーム状態を維持できます。
しかし、この方法は常時インスタンスが起動している = 常時課金が発生するため、「なるべく低コストで運用したい」という要件に合わず採用しませんでした。
Cloud Run Job 方式であれば、処理がないときは課金が発生しないため、コスト面でも要件を満たせました。
まとめ
今回の問題は、1つの原因ではなく 3つの異なる原因が複合的に絡んでおり、1つの問題を解決すると別の問題が発生するという連鎖的な状況でした。なんだかんだ解決してよかったです。
| 原因 | 詳細 | 解決策 |
|---|---|---|
| Firebase Hosting 60秒制限 | 変更不可のタイムアウト | Firestore 非同期監視 |
| Cloud Run CPU Throttling | レスポンス後の CPU 制限 | Cloud Tasks で処理分離 |
| ffmpeg 競合 | 1コンテナ内での並列実行 | Cloud Run Job に移行 |
ひとこと
検証に利用したベンチマークツールなどは Claude Code との協業で短時間で作成しました。1人開発で技術相談できる相手がいない中、AI と設計から実装まで進められたのは助かりました。
似たようなサービスを作る方にとって、なにかしら参考になれば幸いです。
システム構成(詳細)
| カテゴリ | 技術 |
|---|---|
| 言語 | TypeScript |
| フレームワーク | Nuxt 4, Hono, TailwindCSS |
| ホスティング | Firebase Hosting (CDN) |
| 認証 | Firebase Authentication |
| ストレージ | Firebase Storage |
| データベース | Firestore |
| コンテナ | Cloud Run (service/job) |
| 音声処理 | ffmpeg, VAD |
| AI | OpenAI Whisper, ChatGPT |