レポート

Mastodon/Pawooの運用&開発技術 - pixiv Night #04 レポート

この記事を読むのに必要な時間:およそ 7 分

「実運用してみてわかった,大規模Mastodonインスタンスを運用するコツ」

道井さん@harukasan@pawoo.netは,inside.pixiv.blogに書いた実際に運用してみてわかった,大規模Mastodonインスタンスを運用するコツについて話しました。

画像

最初に,PawooのソースコードはGitHubにあるので参照してほしいと言及しました。

マストドン・Pawooの構成

マストドンはPostgreSQL,Redis,node.jsなどを使った普通のモダンなRuby on Railsアプリケーション構成で,nginxがまとめて通信します。

画像

PawooはAWSに載っています。リリースまで時間がないけど一番優先されるのは安定性という,かけ離れた要求がされたと,道井さんは振り返っていました。

画像

マストドンのようなサービスを自社のオンプレで動かすノウハウは持っているけれども,さすがにオンプレのサーバーで安定したものを作るには時間がかかると言います。1か月程度の期間限定サイトを作るときにはAWSを使うことがあるので,今回はそのノウハウを使って進めたそうです。RDS/ElasticaseのマルチAZ構成,メディアはS3に保存,EC2を並べてALBでロードバランスする形でで作ったとのことです(図のCloud Frontは使っていないとのこと⁠⁠。

基本的にはEC2インスタンスを並べています。すべてのEC2インスタンスは同じ構成で,AMIでシュッと増やしていると説明しました。ただし,オートスケーリングが全く考えられていないため,そのうちオートスケーリングするか, スポットフリートに載せることを検討しているそうです。

なお,マストドンはDockerを使っていますが,Pawooでは全部はがして各サービスをsystemdで管理していると補足していました。Dockerのまま使っても良いのですが,Dockerを使うことで,そのために検討・対応する時間が増えることを回避したかったそうです(DockerのままだとECS使えて便利なのですが,ECSだとホットデプロイを考えたり,リソースをうまいことをやったりとか,そのあたりを考えないといけないとのこと⁠⁠。

エラーの改善

実はここまでの作業は,道井さんは携わっておらず,新人さんらが行ったことだったと言います。その日のうちにPawooがリリースされましたが,すぐにエラーが出始めて,翌朝にnginxのエラーログから見始めたそうです。nginxのエラーは,設定を変更して直したとのこと(バランシング自体はEC2のnginxではなくALBを使っているそうです。『nginx実践入門』を読めば分かると薦めていました⁠⁠。

また,マストドンはPostgreSQLを使っています。pixivではほぼMySQLを使っているため,知見があまりないそうです。PostgreSQLの場合は,コネクションを張るごとにforkします。そのためマストドンでは,コネクションを張り過ぎるとCPUをとても喰うことにつながります。そこで,Railsのコネクションプールの数を上手い具合に設定して,良い感じになるよう調整したとのこと。あとはEC2インスタンスをスケールアップして対応しているそうです(可用性とバックアップはRDSにお任せとのこと⁠⁠。つまりお金で解決しているため,知見を求めていると話していました。なお,コネクションプールについては,PgBouncerを使うことも検討しているとのことですが,怖くてまだ使ってないと述べていました。

メインロジックはSidekiq

マストドンのメインロジックは,ジョブ中のしくみを記述しているSidekiqのほうにあると言います。マストドンではSidekiqをメッセージパッシングに使っています。つまりstreamingなどをジョブとしてSidekiqに登録しています。

例えば,1トゥートされると,フォロワーの数だけSidekiqに変身します。これは相当やばいと言及していました。フォロワーの数が多いと,その分かさむからです。

普段からダッシュボードを見ると,2秒で1,000とか2,000とか処理しているのが分かりました。次の図のように,たまに50,000という数値が出たりしているそうです。道井さんは「普段は見ることができないSidekiqの様子を見れるのがマストドンだ」と説明しました。

画像

マストドンのキューの種類はpull, push, mail, defaultです。本当はキューを分けたほうが良いのですが,そういうissueを立てたら,これでもせいいっぱいだ,と言われたので,これからもこの数だろうと話していました。

一番の問題は,Sidekiqが詰まることです。デフォルトだと,すべてのキューが1つのSidekiqプロセスで処理することになっています。このうちのpullとpushがリモートインスタンスに対してメッセージの配信を行いますが,例えばリモートインスタンスのpullとpushのキューが詰まると,pullとpushが詰まり始めることになります。そうするとSidekiqのキューが増えていって,結果タイムラインが遅延してしまいます。

つまり,Pawoo自体を改善しても遅延してしまい,サービスが続けられないことになります。そこで,Sidekiqのプロセスを分ける必要があると言います。また,Sidekiqは1プロセスで1コアまでしか使ってくれません。そのため,プロセスをたくさん立ててカバーする必要があります。

よって,実際には1インスタンスだけで,1,000スレッド以上のSidekiqが動く代物になっていると言います。こうすると,Sidekiqのプロセス画面がだいぶ見づらいし,とても重いのが課題になっていると述べていました。

画像

課題としてはさらに,リモートインスタンスが詰まるのが解決しないことです。例えば大規模なmstdn.jpが詰まると,ほかのインスタンスにも配信されなくなります。そこで,がんばってプロセスを立てるのですが,プロセスを立てるとその分だけリモートインスタンスに配信することになり,DoSのようになってしまうと説明しました。つまり,お金の力で増やしまくっても良くないという問題です。つまり,遅いインスタンスや大規模インスタンスの処理をどうにかしなければいけません。道井さんは「そもそもトゥートあたりのジョブ数がN+1になっているのがやばい」と述べていました。

最後に,道井さんは「マストドンのメインはRailsではなくSidekiq。そのジョブをうまく捌けると良い。将来的にはsidekiqプロセスをスポットインスタンスで並べるのが良いと思っているが,その前に最適化されると良い」と話しました。また,⁠僕らは立てて2週間経ってないので,そもそも最適化というとこまでいきつけてない。とりあえず今は捌くことを考えて作業しているが,そもそもうまく行ってないので,ここは変わるはずだ」とも述べていました。

「なんかnodeのCPU使用率が100%で張り付いててマジヤバかったのでなんとかした話」

片倉さん@geta6@pawoo.netの発表は,Pawooリリース日の23時頃,マシンは大丈夫だけどnodeのプロセスのみ使用率100%で張りついてるという話を受け,それを改善した話でした。

画像

マストドンでは,WebSocketを使ったstreaming関連のAPI実装が1ファイルでできていて,つまりポエムで書かれていると言います。PostgreSQLのコネクションプール,Redisコネクション,HTTPインターフェース,WebSocketインターフェースが全部1つのファイルになっているそうです。

また,npm scriptを見ると,この実装がbable-nodeコマンドを使ってES2015コードを直接起動する筋力運用になっていると説明しました。特にpm2などのマネージャーは挟んでいなくて,普通にnodeを起動しています。

node.jsはシングルスレッドであるため,コア数の違いでパフォーマンスは変わりません。元のコードは素直な実装で,1サーバーあたり1プロセス,つまり1コア分しか利用できない状態でしか立てられないものでした。つまりCPU負荷が偏った状態になります。このため,マストドンを動かしているサーバーとしては大丈夫なのに,nodeのプロセスだけが100%で張り付いて,マストドンのページは普通に見えるのに,なぜかストリーミングが流れないとか,タイムラインがとても遅れる状態になっていたと説明しました。

この解決には,nodeのプロセス数を増やしスケジューリングして,サーバー内のコア間で負荷を分散する方法が一般的です。定石としてはpm2などのプロセスマネージャーを使って,いい感じにnodeのプロセス数を増やします。そうすると一個一個プロセスを殺させることで,サーバーが耐えられるようになるそうです。

そこで,片倉さんはpm2のconfig fileを書いて動かしてもらったのですが,systemdとpm2の相性が悪いのか,片倉さんの書き方が悪いのか,動かなかったそうです。しかしリリース後で,ユーザーが困っているため,早く解決しないといけません。ここに至り,もっとも手っ取り早くnodeをマルチコアに対応させる,clusterモジュールを使って解決しました。

clusterモジュールは,nodeに標準でバンドルされていて,クラスタリング機能を持った,だいたいのプロセスマネージャーの基礎になっています。導入は簡単で,次のようなコードで書けます。(コア数 - 1)しているのは,forkしたプロセスが爆発した時にサーバーが吹っ飛ばないようにするためだと説明しました。

画像

結果,マルチコアを全部使いきれるようになりました。なお,fork元がmaster,fork先がworkerとプロセス的には呼んでいますが,workerへの仕事の割り振りはOSが勝手にしてくれます(設定すれば,ラウンドロビンも選択できるとのこと⁠⁠。

ここまでの話は,バージョン1.2.2で本家に取り込まれています。片倉さんは「ぜひ試してみてください」と話していました(⁠⁠Very interesting!⁠と言われて承認欲求がえられるとのこと⁠⁠。

クラスタの話は以上ですが,次の2つの改善にも触れていました。

  • タイムライン一番下までスクロールした時に無限にXHRリクエストが走るのを抑制した。たぶん実装忘れで,ReduxのAction/Reducerにnext-urlが存在しないときの処理を入れた。
  • トゥートとするときのテキストエリアのサイズを自動的に変更するようにした。難しい印象を持たれるが2行でできる。

画像

休憩

ここで休憩になりました。参加者には,ピザとアルコールが振舞われました。

画像

著者プロフィール

高橋和道

gihyo.jp編集部 所属。最近では電子書籍の制作にも関わる。

URL:https://twitter.com/k_taka