📝 TL:DR
- 「今日この問い合わせ多くない? バグってんじゃない?」をシステム検知してみた
- 本番運用で耐えうる精度までいかなかった
- チューニングが難しい...
🤔 1件なら環境問題、10件なら障害?
サポート業務をやったことのある方に質問です。
「ログインできません」が1件だけ来たとき、それって障害だと思いますか?「パスワード間違ってるんじゃね?」と私なら思ってしまいます。でも同じ内容が3分で10件来たら「これ、障害じゃん」って焦ると思います。
つまり緊急度の情報はメッセージの中ではなく "分布" の側にもあります。これは1件ずつAIに読ませても、淡々と原因を考えるだけで「これやばいよ!」という判断はできません。サポート業務の方々の「今日この問い合わせ多くない? バグってんじゃない?」を、システムで検知してしまおうというわけです。
🔔 「CloudWatch アラームでよくない?」
最初に突っ込まれそうなので書いておきます。
CloudWatch速いし安いし確実です。ただし「何を見るか?」を先に決める必要があります。知っている問題の再発を秒で検知するのは得意ですが初めて起きることには無力です。
| CloudWatch アラーム | セマンティッククラスタリング | |
|---|---|---|
| 何を見るか | 事前に決める | 決めない |
| 得意 | 既知の再発 | 未知の初発 |
| 速さ | 秒 | 数分 |
| 見ているもの | システムの状態 | 顧客の声 |
システムは全部グリーンなのに顧客だけが困っているパターン。特定ブラウザでだけ表示が壊れている、外部の決済事業者側で止まっている、文言を変えたら誤解が広がった。どれもエラーログには出ませんが問い合わせには出ます。既知は CloudWatch に任せて秒で捕まえる。未知はこちらが数分で気づく。運用としてはこういう形が理想な気がします。
こちらで見つかった新しいクラスタをCloudWatchのメトリクスフィルタに落とし込む。未知を既知に変えていく流れですね。
🏗️ 火種検知くん
構成
- 問い合わせを埋め込みベクトルにしてDynamoDBに貯める
- 一定期間(window)ごとにクラスタリングする
- 平常時より明らかにホットになってるクラスタを検知する
- 書き出しをトリガーに LLM でタイトルと要約をつけて通知する
判定にLLMを使わないので判断が決定的で速度も速いのがメリットです。通知が出るときだけLLMで要約してメールを送ります。毎回発生するわけではないのでコスト的にも軽いです。
🔤 「キーワード集計でよくない?」
こちらも突っ込まれそうなので補足します。突っ込みはごもっともです。今回の問い合わせはユーザが自然言語で送信してくるというケースを想定しているため「打刻できない」「タイムカードが押せない」「出勤ボタンが反応しない」は単語だと別の塊に割れてしまうため意味の近さ(セマンティック)で束ねることにしました。
🗄️ DynamoDB のテーブル構成
📥 問い合わせテーブル(inquiry-burst-inquiries)
| 属性 | 型 | 内容 |
|---|---|---|
| InquiryId | S | パーティションキー |
| TenantId | S | どの会社から来たか |
| ReceivedAt | S | 受信日時(ISO 8601) |
| Epoch | N | 受信日時の UNIX 秒 |
| Day | S | 日付(JST)。GSI のパーティションキー |
| Channel | S | email / chat / phone_memo。判定には使っていない |
| Text | S | 問い合わせ本文 |
| Vector | B | 埋め込みベクトル。float32 を1024個並べた4096バイト |
| ExpiresAt | N | TTL。この時刻を過ぎると自動で消える |
ベクトルデータは格納しますがコサイン類似度計算として使うため、
今回はベクトル検索は行いません。
🚨 検知結果テーブル(inquiry-burst-hot-clusters)
| 属性 | 型 | 内容 |
|---|---|---|
| ClusterId | S | パーティションキー。同じ話題の続きなら同じ ID を使い回す |
| DetectedAt | S | ソートキー。検知した時刻 |
| WindowStart / WindowEnd | S | 判定に使った窓の両端 |
| Size | N | 生の件数 |
| Units | N | テナント名寄せ後の単位数 |
| TenantCount | N | 何社にまたがっているか |
| MaxShare | N | 1社が占める最大の割合 |
| Baseline | N | 平常時のレート |
| PValue | N | 偶然この件数が集まる確率 |
| Centroid | B | 塊の重心ベクトル |
| Members | L | 問い合わせの写し(ID・テナント・時刻・本文) |
| Status | S | detected → notifying → notified と進む |
| Title / Summary | S | 通知のときに LLM が書き戻す |
| ReportKey | S | ヒートマップ画像の S3 キー |
このテーブルには DynamoDB Streams を有効にしてあります。ここへの書き込みが、そのまま通知 Lambda(メール通知)の起動になります。
🧮 クラスタリング手法
📐 コサイン類似度
問い合わせの文章はベクトルに変換します。内容が似ているものは同じ方向を向くのでその向きがどれだけ揃っているかだけを見れば仲間かどうかが分かります。
※今回はコサイン距離0.6を閾値としています
🎲 ポアソン分布
普段めったに来ない話題が短時間に立て続けに来る確率です。「いつもと違う何かが起きているぞ!」というのを判断します。
※今回は過去14日分データを平常時データ母数としています
🕳️ ハマったところ
🏢 ① テナントの分布も見るようにした
最初は件数だけで判定していました。「ログインできません」が特定のテナントから大量に問い合わせが来ましたが、その会社の環境問題かもしれません。本当にやばいのはテナントを跨いで同じ機能の問い合わせが集中するパターンです。サポートセンターの方なら青ざめるやつではないでしょうか?
そこで見るようにしたのがテナントの分布です。クラスタリングの前に同じテナントの似た問い合わせを1件に畳むとクラスタの大きさが「件数」ではなく「何社で起きているか」に変わります。
- 1社から10件 → その会社の環境問題っぽい
- 5社から各2件 → プラットフォーム側の問題っぽい
📊 ② 件数ではなく平常時との乖離で判断するようにした
「ログインできません」のようなよく使用する機能の問い合わせって頻繁に来ますよね。かといって件数閾値を上げると今度はめったに来ない種類の問い合わせを取りこぼします。数件しか来ていなくてもいつもゼロの内容なら異常の可能性があります。
そこで件数の絶対値をやめました。判断基準にしたのは平常時からどれだけ乖離しているかです。同じ5件でも毎日5件来ているものは鳴らさずいつも来ないものは鳴る。
📧 ③ 同じ障害で何度もメールが飛んだ
検知が動きだしたら、今度は同じ障害で何度もメールが飛びました。5分おきに判定しているので障害が続いている間はずっと鳴り続けます。
そこで、一度鳴らしたクラスタを専用テーブルに書き出すことにしました。同じものは直近120分のあいだ鳴らしません。「同じもの」かどうかはクラスタの重心どうしの距離で見ます。
✉️ メール通知
通知は SNS のトピックにメールアドレスを購読させて、メールで飛ばしています。
打刻機能の障害:アプリから退勤打刻ができない状態が継続
スマートフォンアプリの打刻ボタンが反応しない、または画面遷移しない障害が発生。5社5件の問い合わせが2時間に集中し、朝から継続している模様。iOS端末での障害報告が確認されており、アプリ側の不具合の可能性。
────────────────────────────────────────────────
■ ヒートマップ(縦=テナント / 横=時刻 / 色=件数)
複数社の行に同じ時刻で色が付いていればプラットフォーム側の障害、
1社の行だけ濃ければその会社固有の問題を疑う。
https://inquiry-burst-reports-123456789012.s3.ap-northeast-1.amazonaws.com/2026/08/18/e7455b8e-....png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=(署名付きURL・以下省略)
※ 署名付きURL。有効期限は12時間。
※ Lambda の一時認証情報で署名しているため、これより早く切れることがある。
※ 期限が切れたら実体を直接取ること: aws s3 cp s3://inquiry-burst-reports-123456789012/2026/08/18/e7455b8e-....png .
(画像は30日で自動削除される)
■ 規模
問い合わせ : 5件(テナント名寄せ後 5単位)
影響テナント : 5社
最大集中度 : 1社が全体の 20%
平常比 : 平常レート 0.30 件/窓 に対して 5単位 = 約16.8倍
■ 時間帯(JST)
最初の問い合わせ : 2026-08-18 22:28
対象の窓 : 2026-08-18 21:45 〜 2026-08-18 23:45
検知時刻 : 2026-08-18 23:45
■ 代表的な問い合わせ(5件中)
1) [T001] 2026-08-18 22:28
退勤の打刻をしようとしましたが、ボタンを押しても反応がありません。朝も同じ状態でした
2) [T004] 2026-08-18 22:50
今朝から続いてるやつ、まだ直ってないですよね?退勤も打刻できないので帰るに帰れないんですけど
3) [T003] 2026-08-18 23:16
本日の朝より、当社の複数の社員がスマートフォンのアプリから打刻できない状態が続いております。夕方の退勤の時間帯になり、再び同様の連絡が増えてまいりました。復旧の見込みと、記録が残っていない分の取り扱いについてご教示ください。
■ なぜ鳴ったのか(判定の根拠)
平常レート(Baseline) : 0.30 件/窓(過去の実績だけから推定。未来は見ていない)
上側確率(PValue) : 1.52e-05 ← この窓に偶然これだけ集まる確率
→ 名寄せ後 5単位 が 5社にまたがっており、平常レートから見て偶然では説明しにくい水準まで増えたため通知した。
■ レコード
ClusterId : e7455b8e-3527-4a17-b160-bcb623c4b35d
DetectedAt : 2026-08-18T23:45:00+09:00
🧪 検証結果
平常時の問い合わせを19種類、そこへ仕込んだ障害を5種類、テナント集中型の罠を2種類まぜた331件をAWS上で流した結果です。打刻の修正申請や給与ソフト連携、アプリの使い方など、障害と語彙が重なるものもわざと混ぜてあります。
| 何を見たか | 結果 |
|---|---|
| 仕込んだ障害5件のうち、何件見つけたか | 3/5件 |
| 1社だけの集中を弾けたか | 弾けた |
| 月末の繁忙を障害と間違えなかったか | 間違えた |
思ったより精度が悪く結構ショックでした
取りこぼした2件について
レアな事象が3件発生していたが「3件くらいなら偶然重なることであるでしょ」ということで平常と判断された
8件発生したが日常的に問い合わせが来るベクトルに近く平常と判断された
閾値を下げれば通知されますが、日常的な問い合わせで通知されるリスクが高まります。
これは合成データでの成績です。自分で作った問い合わせを検知しているので出来すぎている、もしくは意地悪データすぎる可能性は否定できません
💰 コスト:5分おきに回して月5ドルちょっと
1日500件、1件200トークン、判定は5分おきで試算するとこうなりました。
※ 東京リージョン単価
| 項目 | 月あたり |
|---|---|
| 埋め込み(Cohere Embed v4・約300万トークン) | $0.36 |
| DynamoDB 読み込み(平常レートの推定) | $4.74 |
| DynamoDB 書き込み+GSI | $0.10 |
| ストレージ | $0.02 |
| Lambda(5分おき=8,640回) | 無料枠内 |
| 合計 | 約 $5.2 |
9割を占めているのが DynamoDB の読み込みです。判定のたびに平常レートを推定するため過去14日分の7,000件を丸ごと読み直しているからですね。平常レートを日次で事前計算しておけばここは下げられそうです。
😩 ダメだったところ
🧩 ① 似た言葉を使っていれば、別の問題でも同じ塊になる
原因が別でも言葉さえ似ていれば同じグループに入ってしまう。
検証データでも「アプリから打刻できない」という障害の塊に、「打刻忘れのお知らせを、社内で使っているチャットツールに飛ばせるようにしてほしい」という機能の要望が混ざっていました。これは障害ではなく困ってすらいないんですよね。あったら嬉しいな〜という前向きな要望がいま現場が止まっている障害と同じレベルでカウントされます。
📅 ② 繁忙期を障害と間違える
この仕組みは「いつもと違う」を異常だと判断します。セールや繁忙期、勤怠管理なら月末の締めと給与計算。問い合わせが普段の何倍にもなる時期は、異常と判断してしまいます。
🙅 ③ 最初の1件は救えない
構造上問い合わせ一件目で異常と判断することがほぼありません。同じグループの問い合わせが蓄積するまでアラートが出ないためどれだけクリティカルな内容であっても、複数問い合わせが来て平常時と乖離が出るまでアラートがなりません。タイトルの”いち早く”と矛盾するところで大変心苦しい限り...
🍜 締め〆
検知の精度はそこそこでノイズが混ざりやすいという傾向があり、まだまだ運用に耐えられる精度ではありませんでした
。閾値チューニングで向上する可能性はありますが、合成データの結果に合わせにいくチューニングをしても...という部分もありチューニングは途中で打ち切りました。
そもそも問い合わせ内容自体がある程度ベクトルの偏った内容であるためそのベクトルをクラスタリングしていくというアプローチは相性が悪いのかもしれません。ただ今後のemmbeddingModel性能やキーワードベースのクラスタリングとのハイブリット判定で実運用に耐えうるアーキテクチャになる可能性も秘めてるかな?とも感じました。
ベクトル×クラスタリングで未知の障害をいち早く検知するという発想自体は悪くないかなと思いましたが実際やってみると思うような結果にはならないものですね。またそれも面白いところです。
今回の課題はサポート業務をやったことのない私が"妄想"の課題に対するアプローチを検証したものになります。そのためサポート業務現場のリアルな課題とはマッチしていない可能性がありますのでご了承ください



