Mastodonの構成︎

道井俊介氏(以下、道井):harukasanといいます。今日は、Pawooを実際に運用してみてわかった、大規模Mastodonインスタンスのコツということで、insideのブログの内容とほぼ同じことをしゃべります。

まず、自己紹介します。僕はpixivでリードエンジニアをやっている、道井といいます。ふだんは「harukasan」というidで各所にいます。今は「ImageFlux」というサービスのプロダクトマネージャーをやっています。その前はインフラチームというところで、基盤やったり、コードやいろいろ書いたりしています。

下に並べているような『Nginx実践入門』や、あと『WEB+DB』の連載などをやっていました。

ふだん僕はImageFluxをやっていて、こちらが本職なので、みなさんImageFluxのことにご興味があれば、僕のほうに今日はぜひお声がけください。

(会場笑)

話は戻って、Pawooの話をしますが、このPawooというのは、先ほどから出ているMastodonです。こちらのコードはpixivのMastodonというところにあがっているので、ここでみなさん話ができるということですね。

AWSでシュッと作ってAMIでシュッと増やした

Mastodonは、普通のモダンなRailsアプリケーションでできています。構成は、Railsの下にPostgreSQLがいて、Node.jsでRedisをやって、それがストリーミングするような感じです。

この上の部分が、RailsがRESTっぽい普通のAPIを持っていて、Node.jsがストリーミングのAPIを持っている。これらをNginxがうまいことまとめてクライアントと通信している、というのがMastodonの構成です。

そして、これをがんばって10時間でリリースしたものがPawoo。

(会場笑)

Pawooの構成は基本的にはこんな感じです。

みなさんご存じのAWSの上に乗っています。AWSでシュッと作ったのですが、「時間がないけど、一番優先されるのは安定性だから」というかけ離れたことを要求されるので。

(会場笑)

この2つなんかおかしいなと思いながらやっているのですが。

こうしたものを自社でインフラも……我々は基本的にオンプレのサーバを数百台運用していて、そちらのほうが実は運用ノウハウがあるのですが、さすがにオンプレのサーバで安定したものを作ってと言われた場合は、「じゃあ見積もりも入れて3ヶ月ぐらいかな?」といった話になるんですね。それを10時間でどうにかするという感じなので、ここはAWSでシュッと作ります。

RDSの構成でマルチAZを作ってあげて、メディアは全部S3にあげる。EC2並べて、ALBでロードバランスすればいいよねという感じで作りました。

ピクシブではAWSのサイトを実はけっこうやっています。というのは、けっこう期間限定の1週間だけとか、1ヶ月だけという仕事もありまして、そうした場合はオンプレの基盤を使うのではなく、AWSでパッと立ててパッと閉じるといったことをやっています。

基本的には、インスタンスをガーッと並べて作っています。すべてのインスタンスに同じものを突っ込んで、AMIでシュッと増やすと。

(会場笑)

シュッと増やしてはいるのですが、ぜんぜんオートスケーリング考えられていないので、そのうちオートスケーリングするか、Spot Fleetに乗っけるか、どっちかなというふうに考えています。

MastodonはけっこうDockerを使っているのですが、Dockerから全部はがして、Systemdで管理しています。

別にDockerのまま使ってもいいのですが、Dockerのまま使うと考えなきゃいけないことが多くなるので、数時間の間にいろいろ考えるよりは、あえてSystemdにしたほうが早いよねといった感じです。

DockerでやるのであればECSが使えて便利なのですが、ECSだとホットデプロイを考えたり、リソースでうまくやったりなど、無駄が多いので、そのあたりをうまいこと考えなければいけないので。

実はここまでの作業は、僕はぜんぜん携わっておらず、うちの新卒と4月から入ったばかりの人が適当にやってくれました。

(会場笑)

その日のうちにリリース、すぐにエラー︎発生

そんな感じで、その日のうちにリリースされたんですけど、すぐにエラーが出始めました。次の日の朝ぐらいかな。土曜日の朝ぐらいにようやく僕がチャットを見始めるわけです。そこで「エラーけっこう出てるな」と思い、とりあえずNginxからログを読み始めました。

Nginxはエラーがけっこう入っていたので、ここの2つ以外設定変更すれば直るかなと思って、適当に設計を変更し直しました。バランシング自体はEC2のNginxではなく、ALBを使ってやっています。

そのあたりのことは、この『Nginx実践入門』という本に。

(会場笑)

こちらの本を買っていただければわかるということです。

次にPostgreSQL。MastodonはPostgreSQLを使っているのですが、弊社のサービスはほとんどMySQLを使っています。ですから、実はほとんど知見がありません。

一番の特徴としては、MySQLは、コネクションを張ってもスレッドしか増えないんですけど、PostgreSQLはコネクションを張るごとにforkすると。なので、コネクション数を貼りまくると、めちゃくちゃCPUを食うことにつながります。ここはRailsのコネクションプールの数をうまい具合に設定してあげると、いい感じになります。

あとはインスタンスをがんがんスケールアップして、こうした対応をする。可用性とバックアップのところはRDSにおまかせなので、RDSがうまいことやってくれると。

コネクションプールに関しては、Railsの機能を使う以外に、PgBouncerというものがあって、PgBouncerを入れてコネクションプーリングしたほうがいいかなと思いつつも、ちょっと怖くてまだ使っていないという感じです。

グラフはこんな感じになっていて、途中からもうやばいぐらい上がってきて、ガーンとスケールアップしてどうにかなった。つまり、金で解決という感じです。

(会場笑)

ここは知見を求む。

Sidekiqが詰まってタイムラインが遅延することが課題︎

またMastodonに戻りますが、Mastodonは普通のRuby on Railsアプリケーションだと説明しましたが、ここの前のHTTPやストリーミングは、ほとんどユーザーインターフェースで、メインのロジックはどこにあるかというと、ここにあります。

つまりSidekiqがメインロジックのような感じです。

Railsをご存じない方に説明しておくと、Sidekiqというのはジョブキューの仕組みです。ジョブをあとでenqueueしておいて、それを遅延処理するのがSidekiqの一般的な使い方ですね。

MastodonはこのSidekiqをなにに使っているのかというと、メッセージパッシングに使います。つまりトゥートの配信やストリーミングなど、そういったものをジョブとしてSidekiqに追加する。つまり、1トゥートされると、それがフォロワーの数だけジョブとしてSidekiqにenqueueされます。相当やばいですね(笑)。

(会場笑)

ということで、フォロワーの数が多いと、それだけやばいことになります。

ダッシュボードを見るとこんな感じです。ちょっと見えにくいですが、完了の数がありえない数になったり、2秒で1,000とか、2,000とかを処理するわけですね。

たまにこれがこういうグラフになって。これ、ここを見ると50Kとか書いてあって(笑)。

(会場笑)

ちょっとふだんはありえないようなSidekiqの画面を見ることになるのがMastodonです。

Mastodonにはいくつかキューの種類があります。pull・push・mail・その他全部という感じです。

これは本当はたぶんキューをもっと分けたほうがいいんですけど、そういうイシューを立てたら、「これでも精一杯だよ」みたいなことを突っ込まれて脅迫されたので、これからもこの数です。

一番ここで困っているのが、Sidekiqが詰まる問題です。

デフォルトだと、今ある4つのキューが1個のSidekiqプロセスで動くように設定されています。

このうちのpullとpushというのは、リモートインスタンスに対してメッセージの配信を行うというものなんですけど、pullとpushのキューが詰まるというのは、僕らのサーバが詰まったんじゃなくて、リモートインスタンスのサーバが詰まると、pullとpushのキューが詰まり始める。すると、Sidekiqのキューがどんどん増えていって、defaultが増え始めて、タイムラインが最終的に遅延する。

ってことで、僕らがどれだけがんばっても、サービスが遅延して、このままでは終わってしまうということが起こります。これが一番の問題です。なので、ここは、Sidekiqのプロセスを分けることでカバーします。

もう1つは、Sidekiqが1プロセスでどうがんばっても100パーセントまでしかCPUを使ってくれませんから、ここもプロセスをたくさん立ててカバーする必要があります。

実際にはこういった感じで、サービスファイルのキューのところをdefaultだけにするというようなことをやっています。これは一般的なものですね。

これが、今1インスタンスにこのぐらい立っています。Sidekiq-default-1.service、default-2.service、default-3.serviceといった感じに並んでいて、それぞれ40スレッドずつ持っている。

今はデフォルトだけで1,000スレッド以上のSidekiqが起こるという、「なに言ってんだ」みたいな数になります。

(会場笑)

こうするとなにが起こるかというと、Sidekiqのプロセス画面がだいぶ見づらい。

(会場笑)

かなり下のほうまでスクロールしないと、今実行中のジョブがわからないということが起こります。この画面がめちゃくちゃ重くてつらいというのが今の課題です。

このSidekiqが詰まる問題にはまだ課題があって。結局pullとpushのキューを分けたとしても、リモートインスタンスが詰まる問題はなにも解決しない。とくに大規模な.jp(mstdn.jp)さんとかが詰まると、.jpさんに配信するためにほかのインスタンスにも配信されなくなってしまうという問題がある。

そうなるとほかのインスタンスさんも困るので、がんばってプロセスを立てるんですけど、プロセスを立てれば立てただけ、またリモートインスタンスに配信するので、DoSみたいなものです。

(会場笑)

なので、金の力で増やしまくってもやばいということがある。

ここの遅いインスタンスや大規模インスタンスの処理をどうにかしないといけないというのがあるんですけど、そもそもトゥートあたりのジョブ数がN+1になってるのがもうやばいよねという感じですね。

もうまとめに入っちゃうんですが、解決していないけど、まとめです。

(会場笑)

AWSを使ってシュッと立てることで、運用を開始しました。MastodonのメインはRailsではなくSidekiqです。このSidekiqのジョブをうまくさばければよい。

将来的には、このSidekiqプロセスをスポットインスタンスでバババっと並べて、バババッと小さくするようなことをやりたいんですが、その前に、ここを最適化してくれるとうれしいというのが、僕らの希望です。

以上です。ありがとうございました。

(会場拍手)

トゥートの上限で死んでしまう

司会者:ありがとうございました。思ったよりも時間が早かったので、質問は2つぐらい受け付けられそうですが、どなたか質問のある方がいらっしゃいましたら、よろしければ挙手をお願いします。

質問者1:さくらインターネットの鷲北と申します。

(会場笑)

質問者1:1インスタンスのアカウントの上限はどれぐらいの人数になるか予想されていますか?

道井:アカウントの上限……アカウントの上限はあまり考えていません。それよりも、まあたぶんトゥートの上限で死ぬんで。

質問者1:なるほど。

道井:アカウント数はそんなに問題じゃないかなと思いますね。

質問者1:よくわかりました。ありがとうございます。

道井:あれですね。なんか某マストドンアズアサービスが、アカウント数すごい数を、「この値段でやれんの」というようなやつがありましたね。この間。

質問者1:ああ。まあ、それは置いておいて。

(会場笑)

道井:そうですね、アカウント数で縛るということがそもそも難しくて、どちらかというと、やっぱり最大のトゥートをどれだけ処理できるかというところが一番難しいので、そこにどうにかしないと、このままではMastodonは終わるという感じですね。アカウントをたくさん作られるよりも先に、たぶんトゥートを投げられまくって終わるので。

質問者1:ありがとうございます。

司会者:もう1人いけますけども。

質問者2:このままいくと、ユーザー数増えて無限にお金がかかってくると思うんですけど、そのあたりについてはどのように考えているんですか?

道井:今はほとんど最適化が入っていなくて、けっこうバニラな書き方をされているので、そこはやりようがあります。それこそ、Sidekiqジョブがこれだけ無限に増えるという状況はおかしいので、そもそもそこを直せばいいという話なんですね。

今のところ、まだ僕ら立てて……1週間経ったんだっけ? 2週間目?

司会者:2週間……経ってない?

道井:立ててから2週間経っていないぐらいなので、そもそもまだ最適化というところまで行き着いていません。とりあえず、今さばくにはどうすればいいかと考えると、Sidekiqプロセスをガンガン並べて、ガンガンさばけばいいということなんですけど、そもそもそのシステムがうまくいっていないので、おそらくそこの部分は、そのうちなくなると思います。

司会者:それでは、harukasanでした。ありがとうございました。

(会場拍手)