Skip to main content
舞台裏
舞台裏EngineeringDecisions

4,000 の Discord サーバーを見守る:Grafana と PostHog が教えてくれたこと

トラフィックの半分を静かに生み出していたループ、1日300万件の無意味なリクエスト、そして1分で終わった攻撃。私たちの監視が見ているもの、そして意図的に見ないもの。

公開日 著者: AlphaCloud7 分で読めます

AlphaCloud は4,000を少し超える Discord サーバーで動いており、それらは4つのシャードに分散しています。4つのプロセスがそれぞれサーバーの一部を受け持ち、すべてが同じ API と通信しています。

この規模になると、問題が何かを壊すことで姿を現すことはめったにありません。はるかに多いのは、すべてが動いているのに、本来よりも高くつき、遅く、うるさくなっているケースです。そのため、私たちは2026年9月に監視の仕組みを作り直しました。この記事では、それがどんなものか、そして最初の数週間で何を見せてくれたのかを紹介します。

2つのツール、2つの問い

Grafana は「Bot は何をしているのか」という問いに答えます。Bot と API はメトリクス(リクエスト、応答時間、エラー率、Discord からの応答、AI の使用量)を Prometheus に送り、ログは Loki に送られます。各サーバーノードには独自のスタックがあり、Grafana はインターネットからではなく、プライベートネットワーク経由でしかアクセスできません。

PostHog は「私たちの Web サイトで人々は何を体験しているのか」という問いに答えます。ページビュー、ブラウザのエラー、読み込み時間、そして同意があれば記録されたセッションです。PostHog は、Cookie バナーで同意した後にだけ起動します。

この分け方は意図的です。Grafana は機械を見て、PostHog はインターフェースを見ます。両方を1つのツールに押し込むと、サーバーログにユーザーデータが入るか、プロダクト分析にサーバー負荷が入るかのどちらかになります。

見つかったもの

トラフィックの半分を生み出していたループ

終了時刻のある投票は、バックグラウンドのタスクが毎分、期限切れのものがないかを確認して締め切ります。このタスクは、それをすべてのサーバーについて個別に行っていました。1シャードあたりおよそ1,000サーバー、シャードが4つで、毎秒約70リクエストになり、そのほぼすべてが「期限切れのものはない」という答えでした。

これに気づいたのは、Grafana で Bot から API へのリクエストをパスごとに分けて見たときでした。この1つのエンドポイントが、全トラフィックの半分以上を占めていたのです。すべては正常に動いていました。ただ、誰も求めていない負荷を背負っていただけでした。

現在は、各シャードが1分に1回「そもそも期限切れの投票があるのはどのサーバーか」と問い合わせ、該当するサーバーについてだけ詳細を取得します。毎分約4,200件だったリクエストが、約4件になりました。

300万回の「未設定」

同じパターンが別の場所にもありました。いくつかのモジュール(サポート記録、不在届、チームリスト、Bot 統計、ニュースレター)は、ループの中ですべてのサーバーの設定を定期的に読み込んでいました。ほとんどのサーバーでは、そのモジュールはそもそも設定されていないので、答えは「見つかりません」です。それが1日に約300万回ありました。

修正は地味なものです。「未設定」という結果を30分間覚えておくようにしました。これが安全なのは、これらのモジュールがどれも、Web パネルで保存された瞬間に Bot へ更新を送るからです。つまり、モジュールを設定しても30分待たされることはありません。

Discord の障害は私たちのバグではない

チーム用のエラーチャンネルは、失敗したアクションをすべて「Runtime Error」として、固有の参照番号付きで投稿していました。Discord 自体がサーバーエラーを返した瞬間も含めてです。それは私たちの側のバグでも、私たちが直せるものでもないのに、本物の不具合とまったく同じ見た目でチャンネルに並んでいました。

現在は警告として記録され、メンバーにはバグ報告へのリンクではなく、数秒後にもう一度試すよう案内しています。メッセージの半分がエラーではないエラーチャンネルは、いずれ読まれなくなります。そうなると、本物を見逃してしまいます。

1分で終わった攻撃

9月末のある午後、私たちのトップページに1分間で50万件を少し超えるリクエストが、多くの国から同時に届きました。そのほとんどはサーバーに届く前に止められ、1分後にはトラフィックは通常に戻っていました。

私たちがそれに気づいたのは、何かが落ちたからではありません。その数字がグラフのほかの部分と合わなかったからです。それこそが監視の本当の仕事です。すべてが燃えているときに警報を鳴らすことではなく、どこが通常と合わないかを示すことです。

私たちの監視が意図的に見ないもの

ここでのルールは、ダッシュボードと同じくらい重要です:

  • ログに訪問者の IP アドレスを残しません。 プライバシーポリシーでそう約束しているので、2か所で削除しています。アクセスログを書き込むときと、保存する前にもう一度です。
  • メトリクスにサーバー、ユーザー、メッセージの ID を含めません。 どのメトリクスも、大まかな属性(モジュール、ステータスクラス、シャード)でしか分けていません。これでデータが守られ、メトリクスのデータベースも小さく保てます。
  • セッションの記録に映るのは私たちのインターフェースで、あなたのコンテンツではありません。 読めるまま残るのは、私たちの翻訳ファイルから一字一句そのまま来たテキストだけです。サーバー名、チャンネル名、ユーザー名、入力内容、プレビューはマスクされます。許可されたテキストの一覧を読み込めない場合は、すべてがマスクされます。
  • Web サイト上のものはすべて同意の後だけです。 そのため、そこでの数字は下限値でもあります。実際のページ閲覧数については、ネットワークプロバイダーが出す匿名の合計値を見ています。

詳しくはプライバシーポリシーをご覧ください。

そこから得たこと

高くつくものが壊れているとは限らない。 上の発見はどれも、エラーを起こしていませんでした。生み出していたのは負荷、コスト、ノイズで、計測しなければどれも見えません。

サーバーごとに問い合わせる方式はスケールしない。 大きな2つの発見は、同じパターンでした。すべてのサーバーをループし、1つずつ個別に問い合わせる方式です。答えはほぼ必ず、同じ質問をまとめて1回だけ聞くことです。

アラートはまれでなければアラートではない。 だからこそ、チームに通知を送るのは Grafana だけで、それも誰かが対応する必要があるものに限っています。PostHog からの新しいエラーの種類は、通知なしでチャンネルに届きます。

ご自身でも Discord サーバーを運営していて、ここで触れたモジュールを見てみたい場合は、投票、サポート記録、不在届が Web パネルにあり、ドキュメントで説明しています。

その他の記事