#品質保証#ソークテスト#SRE#AI

AIが作り、AIが配信し、AIが落ちる — 誰も見ていないシステムの品質保証


AI アバターが YouTube と Twitch に無人で配信し続けるシステムを作りました。番組を登録しておけば、時間になると配信枠が作られ、GPU が起動し、視聴者のコメントに音声で応答し、尺が来たら締めて終わります。当日、人間は何もしません。

作っている間、ずっと引っかかっていたのが「これ、何をテストすれば品質を保証したことになるんだ」でした。出力は毎回違う。正しさは主観。そして一番厄介なことに、失敗しても止まらない。誰も見ていないので、誰も気づかない。

この記事は、その状況で「保証する対象を組み替えた」記録と、その一部として避けて通れなかった「長時間でしか出ないバグ」への向き合い方をまとめたものです。前半は QA の設計思想、後半はソークテストの実務です。開発者向けに、つまずき → 原因 → 解決の順で書きます。

従来のテストが前提にしていた3つのこと

書いていて気づいたのは、これまでのテストが暗黙に置いていた前提が、3 つとも成り立たないということでした。

前提 無人 AI 配信では
入力が決まっている 視聴者が何を書くか分からない
出力が判定できる LLM の出力は毎回違う。正解の文字列がない
失敗すると止まる **失敗しても止まらない。**無言のまま配信が続く

3 つ目が一番厄介でした。普通のシステムは、壊れると例外が飛び、リクエストが失敗し、誰かが気づきます。**配信は壊れても流れ続けます。**映像は出ている。プロセスも生きている。ヘルスチェックも緑。ただアバターが一言もしゃべらない。

これは実際に起きた障害です。対話サーバだけが再起動したとき、途中の層が切断を下流に伝えていなかったため、**視聴者から見ると「AI が黙り込んだ配信」**になっていました。どの監視項目にも出ません。この「生きているのに機能していない」状態は、この記事を通して何度も出てきます。無人運用における最悪の状態だからです。

保証する対象を組み替える

そこで、保証の対象を出力から振る舞いへ移しました。「アバターが良いことを言うか」は保証できません。保証できるのは、システムが期待どおりに振る舞うかです。

具体的には 4 つに絞りました。

1. 止まるべきときに止まる

自動化されたシステムで怖いのは、失敗ではなく成功の継続です。誰も見ていないので、動き続けている限り誰も気づきません。そして GPU の課金は続きます。

止まる条件を明示的に実装しました。

  • 番組の尺が来たら、締めの挨拶をして終了する
  • 一定時間ライブにならなければ、失敗として片付けて Pod を破棄する
  • レンダラーが短時間に復帰を繰り返したら、自分でプロセスを終了する

3 つ目は「自分から死ぬ」実装です。直感に反しますが、異常を上位に伝える最も確実な手段でした。

2. 壊れたら気づける

「生きているのに機能していない」状態を、最悪の状態として定義しました。

そのうえで、各層に「上流が死んだらどう振る舞うか」を明示しました。既定の振る舞いは、たいてい「何もしない」です。何もしないと、切断が途中の層で吸収されて消えます。冒頭の「黙り込んだ配信」は、まさにこれが原因でした。

  • 上流の切断を検知したら、自分の下流の接続も切る
  • 切られた側は再読み込みし、再接続する
  • 再接続が失敗しても、上流が戻るまでリトライする

**死を伝播させる。**可用性のために生き延びようとする層が、全体の可用性を下げていました。

3. 壊れたら戻る(そして戻ることを実測する)

復帰処理は、書いただけでは動きません。実際に壊して測りました。

壊し方 復帰
配信中に GPU の Pod を手で削除 32 秒で検知、別ホストで再開
対話サーバをローリング再起動 41 秒で完全復帰(状態の復元込み)

障害注入は、それぞれ数分の作業です。**やらずに書いた復帰コードは、たいてい一箇所で止まります。**我々も、実際に壊してみて初めて「再接続のリトライがないと、再起動中に当たったとき止まる」ことに気づきました。前節の「リトライを入れる」は、机上ではなくこの実測から出た対策です。

4. 出してはいけないものを出さない

出力そのものは判定できませんが、出力の境界は判定できます。

  • チャットの入口で NG ワードと連投を落とす(汚れたものは入口で止める
  • AI 生成コンテンツであることをプラットフォームに申告する
  • 検証中は限定公開に固定し、公開に切り替えるのは人間の判断にする

「何を言うか」ではなく「何を入れるか、どう出すか」を制御する、という発想の切り替えです。

それでも人間が見る場所を残す

すべてを機械で判定できるわけではありません。実際、性能指標が全部正常なまま映像だけが壊れる不具合がありました。キャラクターの顔が明滅しているのに、fps もエラーも GPU 使用率も正常。

見つけたのは人間の目です。以降、描画や音声に関わる変更は、実物を見聞きしてから確定する運用にしました。

自動化できる検査を増やすほど、人間が見る場所を意識的に残す必要がある、というのが逆説的な学びでした。全部緑のダッシュボードは、全部正常であることを意味しません。

長時間でしか出ないものがある

ここまでの検査を短時間で全部通しても、まだ足りませんでした。2 時間回すと壊れるものがあるからです。10 分のテストは全部通っていたのに、本番相当の長さで回すと別の顔が出てきます。

実際に長時間稼働で見つかった不具合を並べたら、きれいに 4 つに分かれました。**どれも短時間のテストでは原理的に検出できません。**回す時間そのものが検査項目だった、ということです。

類型 なぜ短時間で出ないか
累積 タイマーの遅れが積もってリップシンクが漂流 1 回の誤差が小さい。時間に比例して増える
増加 履歴・メモリ・状態が際限なく増える 上限に当たるまで時間がかかる
周回 同じ話題を繰り返し始める バッファが一周するまで起きない
再起動 更新されたトークンが再起動で失われる プロセスが生きている間は症状が出ない

以下、4 類型を一つずつ、原因と検出のコツで見ていきます。

類型1: 累積 — 1回の誤差 × 回数

配信映像のフレームを一定間隔で書き出す処理を setInterval で書いていました。タイマーの発火は遅れることがあり、遅れた分は取り戻されません。

1 回あたり 1ms の遅れでも、30fps なら 1 分で 1.8 秒ぶんのフレームが欠けます。音声は実時間で流れているので、映像だけが遅れていく。開始時は合っているのに、時間が経つほどズレるという症状でした。

検出のコツ: 「時間が経つほど悪化するか」を明示的に確認する。開始直後と 1 時間後で同じ測定をして比較します。「調整しても再発する」と感じたら、たいてい累積です。

類型2: 増加 — 上限があるかを実測する

配信の状態(会話履歴、視聴者ごとの記憶など)を保持していました。設計上は直近のぶんだけ持つつもりでしたが、設計の意図と実装が一致しているかは別問題です。

2 時間の稼働で確認した数字がこれです。

項目 結果
状態スナップショットのサイズ 6.2KB で頭打ち
プロセスのメモリ 増加なし

「頭打ち」を確認できたことが重要でした。増え続けるなら、いずれ必ず落ちます。上限があるという設計を、実測で裏付ける。

検出のコツ: サイズやメモリを定期的に記録して、グラフが寝るか、伸び続けるかを見る。10 分では両者の区別がつきません。

類型3: 周回 — バッファが一周する

コメントが来ないときに自分で話題を出す機能を入れていました。直近の会話履歴を見て、同じ話題を避けるようになっています。

問題は、履歴の保持量がそのまま「話題が一周する周期」になることです。履歴から消えた話題は、また新鮮な話題として選ばれます。視聴者から見ると「さっきも同じ話をしていた」になります。

これは 10 分のテストでは絶対に出ません。履歴が一周していないので。

検出のコツ: 「N 件保持」「N 分保持」という設計値があったら、その N を超える長さで回す。リングバッファ、キャッシュ、重複排除、レート制限。同じ構図はいろいろな場所にあります。

類型4: 再起動 — プロセスが生きている間は正常

外部サービスのトークンを更新するとき、新しいリフレッシュトークンが返ってくることがあります。それをプロセスのメモリに持っているだけだと、再起動した瞬間に古い値に戻ります。

古い値がまだ有効なら動きますが、無効化されていれば認証が通らなくなります。プロセスが動き続けている間は完全に正常なので、ソークテストですら見つかりません。連続稼働と再起動耐性は、別のテストなのです。

検出のコツ: ソークテストの途中でわざと再起動する

終わり際と、後片付け

長時間テストでしか確認できないものが、4 類型のほかにもう 2 つあります。

終わり際の処理。番組の締めの挨拶、終了処理、リソースの解放。これらは最後の 1 分にしか動きません。途中で止めたテストでは一度も実行されていないコードです。「止まるべきときに止まる」を実装しても、その締め処理を最後まで走らせて確かめなければ、動く保証はありません。

後片付け。我々は毎回、終了後に GPU の Pod が本当に 0 台になっているかを確認しています。止まらないと課金が続くので、ここは目視の確認項目にしました。「終わったはず」を信じないことにしています。

ソークで見つかった最悪のもの

2 時間のソークで見つかった中でいちばん重要だったのは、冒頭で触れた対話サーバだけが死んだとき、配信が無音のまま続くという挙動でした。

  • 映像は出ている
  • プロセスは生きている
  • どのヘルスチェックも緑
  • アバターだけが一切しゃべらない

これは「壊れ方が観測されない」という点で、4 類型よりさらに危険です。長時間回していなければ、本番で初めて遭遇していました。「壊れたら気づける」という設計目標そのものが、この一件を長時間回して観測できたからこそ具体化できた、とも言えます。

ソークテストの観測項目

我々が実際に見ているものを挙げておきます。長時間テストの持ち物リストとして使えます。

  • メモリ / 状態サイズの推移(寝るか、伸びるか)
  • 時刻のズレ(開始時と終了間際で同じ測定をする)
  • 出力の実尺 vs 実時間(脱落していないか)
  • 同じ挙動の繰り返し(話題、応答パターン)
  • 途中での再起動と、そこからの復帰時間
  • 終了処理が実際に走ること
  • 後片付け(残っているリソースがゼロか)

まとめ

QA の設計として:

  • 無人で動く AI システムでは、テストの前提(入力が既知・出力が判定可能・失敗すると止まる)が3 つとも崩れる
  • 保証の対象を出力の正しさから、システムの振る舞いへ移す
  • 具体的には 4 つ: 止まるべきときに止まる / 壊れたら気づける / 壊れたら戻る / 出してはいけないものを出さない
  • 「生きているのに機能していない」を最悪の状態として定義する
  • 復帰は実際に壊して計測する(Pod 削除で 32 秒、対話サーバ再起動で 41 秒復帰)。書いただけの復帰コードは動かない
  • 自動検査を増やすほど、人間が見る場所を意識的に残す

ソークテストの実務として:

  • 長時間でしか出ないバグは 累積・増加・周回・再起動 の 4 類型に整理できる
  • 累積は「時間が経つほど悪化するか」で見つける
  • 増加は「上限があること」を実測で裏付ける(状態は 6.2KB で頭打ちを確認)
  • 周回は設計値(N 件保持)を超える長さで回して初めて出る
  • 再起動はソーク中にわざと再起動しないと出ない
  • 終わり際の処理と後片付けは、最後まで回したテストでしか実行されない
  • 「10 分動いたから大丈夫」は、4 つの類型のどれ 1 つも検査していないという意味

「AI の出力の品質をどう保証するか」に正面から答えるのは難しいですが、**「AI が変なことを言っても、システムとしては壊れない」**を保証するのは可能でした。無人運用では、後者のほうがずっと重要でした。そして回す時間を伸ばすだけで見つかるバグが、これだけあります。

← 記事一覧へ