モチベーション
Discord用の自作TTS-Botの音声変換エンジンにVOICEVOXを使っています。
VOICEVOXが合成する音声の品質は素晴らしいのですが、APIサーバーとして多くのリクエスト処理するのには向いているとは言えません。
そこで、次の二つの対策を考えました。
- VOICEVOXのAPIサーバーを複数用意して負荷分散する
- リクエストの結果をキャッシュしておき、同じリクエストの場合はキャッシュから結果を返す
※長文にはほぼ効果はないですが、定型文の連打とかには効果が見込めるかも?
最初はBot側で実装しようかなって考えていたのですが、「これってリバースプロキシとかロードバランサの仕事じゃない?」と思い直したのでNGINXでやってみることにします
TTS-Bot <---> NGINX <-----> VOICE Server 1(192.168.0.101:50021)
↕ |--> VOICE Server 2(192.168.0.102:50021)
(Cache) |--> VOICE Server 3(192.168.0.103:50021)
NGINXによる負荷分散とヘルスチェックの話
振分については特に解説は必要ないかと思いますが、ヘルスチェックの挙動については下記参考になります!
https://www.konosumi.net/entry/2021/09/12/234543
https://neinvalli.hatenablog.com/entry/2017/10/31/002839
https://recruit.gmo.jp/engineer/jisedai/blog/loadbalancing-nginx/
マイTTS-BotでつかうVOICEVOXのAPI
/audio_query
/synthesis
どっちもPOSTメゾッドです
そもそもPOSTってキャッシュしてもいいの?
キャッシュしてもよいそうです。
ただ「ほとんどの実装では対応してないけどね」という注意書きがつきますが・・・。
APIサーバー側がステートレスで同一のリクエストには同一の結果を返すのを保証するなら問題なさそうです。
※ということで、VOICEVOX側のユーザー辞書機能は封印します。
何をもって「同一」のリクエストとするか
- ホスト名(ポートまで含む)
- URLパラメータ(URLの末尾の?以降の部分)
- リクエストBody
今回は、上記一致なら同一リクエストとみなしましょう。
つまり、この3項目をキャッシュを引き当てるキーに使います。
一旦出来上がったnginx.conf
RHEL9でインストールできるパッケージのconfファイルがベースです
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# コネクション確立とか送信方向は早めにタイムアウトさせて次のノードにトライさせる
proxy_connect_timeout 1s;
proxy_send_timeout 1s;
proxy_read_timeout 60s;
# バッファーのサイズが小さくてキャッシュに失敗していたので大きめに設定
proxy_buffer_size 64k;
proxy_buffers 100 64k;
proxy_busy_buffers_size 128k;
#これを設定しないとリクエストBodyのサイズによっては
#$request_bodyが空になってキャッシュの引き当てがうまくいかない
client_body_buffer_size 1m;
# POSTのレスポンスコード200の結果のみ、24時間10GBまで保持する。
# でも再利用されなかったキャッシュは1時間で捨てる。
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=vv-cache:10m inactive=1h max_size=10g;
proxy_cache vv-cache;
proxy_cache_valid 200 24h;
proxy_cache_methods POST;
# キャッシュを引き当てるためのキーの設定
# $http_host ・・・ホスト名(ポート含む)
# $request_uri ・・・URIパラメータ
# $request_body ・・・リクエストBody
proxy_cache_key $http_host$request_uri$request_body;
# 負荷分散先
upstream voicevox {
server 192.168.0.101:50021;
server 192.168.0.102:50021;
server 192.168.0.103:50021;
}
server {
listen 50021;
location / {
proxy_pass http://voicevox;
}
}
}
同じ単語を連打した場合、キャッシュからレスポンスしてくれるようになった。
効果測定
※方法を検討中
- ダミー文章の準備
→面倒なのでキャッシュを無効化して同じクエリをPOSTする方向で(おい) - 負荷試験用のプログラム作成
→jmeterで試行錯誤中 - レスポンスの遅延が出始める単位時間あたりのリクエスト数を測定すればいい?
→対処不可能なリクエスト数が来た場合、NGINXでキューイング&ドロップさせる処理が必要だと思い始めた
参考