🤖

Go + NATSで構築する、負荷分散型Discord読み上げBOTの設計と開発

に公開

はじめに

Discordを使用していて読み上げBOTのレスポンスが遅すぎたり、設定が面倒だったりしたことはありませんか?

今まで既存の読み上げBOTを利用してきていて以下のような不満がありました。

  • 人気過ぎて読み上げのレスポンスが悪い
  • 重いBOTを避けるためにBOTを入れ替えないといけない
  • 自分の管理するサーバー内で複数同時に使用しようとすると設定が…

色々と面倒くさくなって自分で作ってみたので少し記録に残したいと思います。

何を解決するか

読み上げBOTを作成するにあたって以下の内容を解決しようと決めました。

  • 複雑な設定が不要で複数チャンネルで同時に利用できる
  • どのBOTが重いか考えなくて良いようにする
  • 負荷が上がった時の対策ができるように考慮する
  • とにかくチャット欄にコマンドを打ちたくない
  • 運用コストを捻出するのに不安定な寄付に頼らずきちんとした収益を元に運営する

使用した技術スタック

技術スタックとしては以下の組み合わせを採用しました。
※単純に最近はGO言語ばかり触っているので・・・という理由で採用しています。

データベース

  • PostgreSQL
  • Prisma Migrate (スキーマ管理)
  • sqlc (クエリ管理/ORM)

メッセージプロバイダ

  • NATS

Discord BOT

  • GO言語
  • github.com/disgoorg/disgo

TTSWorker

  • GO言語

WebAPI

  • GO言語
  • github.com/labstack/echo/v4

ダッシュボード

  • TypeScript
  • Vue.js
  • TailwindCSS
  • PrimeVue

Webページ

  • Astro
  • TailwindCSS

アーキテクチャ

まず、「複雑な設定が不要で複数チャンネルで同時に利用できる」と「どのBOTが重いか考えなくて良いようにする」「負荷が上がった時に対処出来るようにする」の3つを解決するために、ユーザーがボイスチャットへ入室したことを監視するYOMIと読上げの処理を行うANDONと役割分担を行い、その時一番負荷の低いANDONがボイスチャットに参加する方式を採用しました。
これにより均等に各ANDONに負荷が分散し、より多くの方にストレスなく使用して頂けると考えています。

VOICEVOXについてもANDONと切り離すことで特定のBOTに負荷が集中しないようにしてみました。
将来的に負荷が上がった場合、ANDON/VOICEVOXを追加すれば柔軟に対応できると考えています。
また、各サービスはProtocol BuffersでエンコードしたデータをNATS経由でやり取りすることで協調動作を行っています。

読み上げBOTの呼び出し

前述のとおり、「複雑な設定が不要で複数チャンネルで同時に利用できる」「どのBOTが重いか考えなくて良いようにする」と考えていたので、読み上げBOTを何も考えずに呼び出せば最適なBOTがやってくる状態にしたかったため、YOMIANDONという2つの役割に分担しています。
これにより、何も考えずに/yomi joinするだけで現在入室しているボイスチャットにANDONがやってきます。
現在、同時に呼び出せるのは3体までに制限しているので、構成図上では4体いますが、この4体の中から3体までならどこで呼んでも設定なしで同時に参加させることが可能です。
※ただしサーバーに4体のANDONを招待して頂く必要があります
ロールの設定と格闘する必要も、どのインスタンスを呼び出すか悩む必要もありません。

呼び出すBOTと負荷分散

呼び出すBOTを決める際、YOMIから問合せメッセージをブロードキャストします、各ANDONは負荷状態を返信し、YOMIは一番負荷の低いANDONに入室依頼を行います。
ANDONは入室依頼を受け取ったらボイスチャンネルに入室し音声の監視を始めます。

Text-to-Speech

ANDONは自分ではTTSの処理を行わず、メッセージプロバイダ経由でTTSWorkerへ変換依頼を送ります。TTSWorkerVOICEVOXでTTS処理を行い、取得した音声データをメッセージプロバイダ経由でANDONに送りかえすことで、ANDONはメッセージと音声の送受信を行うだけで良くなります。

NATS(メッセージプロバイダ)

今回、メッセージプロバイダとしてNATSを採用しました、理由は以下のとおりです。

  • Go用のライブラリが存在する
  • 軽量であること
  • Protocol Buffersのデータが流せる
  • JetStreamによりat least once配信にも対応できる
  • 使い慣れている
    はい、使い慣れているのが一番ですね :p

ダッシュボード

個人的にチャット欄でスラッシュコマンドを打つのが苦手なので、出来るだけチャット欄を使わなくて良いようにダッシュボードを準備しました。
その為、YOMIには最低限のコマンドしか準備していません。
YOMIへコマンドを打ち込んで設定するのではなくWeb上のダッシュボードから各種設定を行うことができます。
設定された内容はデータベースに保存され、即座に各サービスに反映されます。

BOT一覧/招待画面

BOT一覧/招待画面

自動入室の設定画面

自動入室の設定画面

収益モデルについて

寄付による運用も考えましたが、収入が不安定で各利用者への負担が不透明になりやすい為、「運用コストを捻出するのに不安定な寄付に頼らずきちんとした収益を元に運営したい」と考えて小規模用の無料プランとしっかり使う為の有料プランを設定しています。
有料プランを準備するにあたって、Stripeによる決算方法や価格の設定方法などとても勉強になりました。

開発の苦労と得られた知見

作成するうえで色々と詰まることがあったのですがAIに聞きながらなんとか作成することができました。
作成中にDiscord側の挙動が変わったり、利用していたDiscord用のパッケージが最新のボイスチャットに対応していない事で入れ替えたりする事態にはなりましたがなんとか完成しました。

詰まりポイント1

Discord側のボイスチャット仕様が移行期だったようで、最初動いていたソースが途中から急に動かなくなりました(初期段階では「なんか不安定だな?」程度に思っていたのですが途中から完全につながらなくなりました)

これについては、最初に利用していたgithub.com/bwmarrin/discordgoにパッチを当てて進めていたのですが、どうしてもVC接続の動作を安定させることが出来なかったため、悩んだ挙句にgithub.com/disgoorg/disgoへの乗り換えを決意しました。
大きい修正にはなりましたが移行してよかったと考えています。

詰まりポイント2

これは今回のプロジェクトに限った話ではありませんが、途中、楽を使用としてAIに大部分を任せてコーディングを進めてしまった結果、「修正できました」→「だめ」→「ココでした修正出来ました」→「ダメ」→「やっぱりここでした修正出来ました」とお手本のようなダメな流れになってしまいました。
これは、人間に頼む際にも言える事ですが、目指している結果と目的をきちんと仕様として伝えきれていないことが原因だったと考えています。(AIの使い方の話なのでここでは掘り下げません)

良かった点

今回、有料プランを作成するにあたりStripeを使用しました。
初めて使用したためドキュメントを読んでもイマイチ理解できず、AIでサンプルコードを作成してもらってドキュメントと突き合わせるという作業をへてようやく実装ができました。
直接コードを突っ込んで聞くより、サンプルコードやドキュメントの理解の補助に使う方が、スキルアップとなり長期的には時間の短縮になるように感じました。

運用してみて

運用を開始して3カ月ほど経ちますが、現状では自分のサーバーだけで利用していることもあり快適です。(当たり前)
色々と意気込んで監視ソフトも導入して負荷の監視も行っているのですが、当たり前のことですが使う人がいなければ負荷も上がりません。

今後について

現状、ほぼ自分用ということもあり自宅内のネットワークで動作させていますが、クラウド環境へ移行してさらに安定動作するようにしたいと考えています。

最後に

BOT運用のノウハウがない為、アドバイス等を頂ければ大変ありがたいです。

YOMI Projectについて

というわけで宣伝になってしまうので興味の無い方はここで閉じてください。
今回作成したDiscrod用読上げBOTは「YOMI Project」して公開しています。
BOTの追加については、以下のリンクよりログインしたダッシュボードより行えますので、興味が有られる方はよろしくお願いいたします。

YOMI Project
YOMI Project Banner

Discussion