はんドンクラブ 運営ブログ

Mastodonインスタンス「handon.club」の運営ブログです。コラムなど,Mastodonに関する一般的な記事も投稿予定です。

Mastodonを構成する技術要素(技術者向け)

こんばんは.今後,handon.clubのサーバ構成について詳細な説明記事を書く予定です.しかしそのためには,まずはMastodonの動作原理について解説しておいた方がよいかなと思います.この記事では,Mastodonを構成する技術要素について,特にスケーリングの観点を中心に説明します.

今日の記事は主に,ソフトウェア技術者向け(ある程度のバックグラウンドがある方向け)に書いてみます.ただ,それだけではおもしろくないので,後日一般向けの記事にもチャレンジしてみるつもりです.読んでみて,ちんぷんかんぷんだった方は次回をお待ち頂けると嬉しいです!

全体の概要

以下に,Mastodonの動作原理の概要図を示します.ここで,図中では,正確性は犠牲にしており,わかりやすさのため単純化していることはご留意下さい.

f:id:highemerly:20181217233147p:plain
Mastodonの動作原理概要

まず,Mastodonはwebアプリケーションですので,全てのリクエストは一度webサーバ(公式ではnginxが推奨されています)で受け,その後適切な振り分けを行います.ここで図中では,上からユーザ(または他のインスタンス)からアクセスが行われるイメージで記載しています.

nginxからの振り分け先は3つで,Webページ(テキスト等)表示用にはrailsとNode.jsが,そして画像表示用にはS3またはSwiftなどのオブジェクトストレージが利用されています.また,Mastodonサーバ内部では,長期でデータを保持するDBとしてpostgresqlが,短期でデータを保持するキャッシュとしてredisが用意されています.更に,非同期処理はsidekiqで実現されています.

要素名 ソフトウェア名 概要
web server nginx HTTP/HTTPSリクエスト処理
web rails Web処理(通常処理)
streaming Node.js Web処理(Userstreamのみ)
media swift 画像やカスタム絵文字の格納
db postgresql アカウント情報やトゥートの格納
cache redis キューキャッシュ
job queue sidekiq ジョブハンドラー

dockerについて

ここで注意点です.あなたがもしこれからMastodonサーバを建てようと思って調査を始めると,すぐに「docker環境」「非docker環境」という2つの環境があることに気づくと思います.dockerは,インストール済みのイメージをダウンロードしコンテナとして展開することで,自らの環境構築作業の手間を削減できる便利な技術です.Mastodongithubを参照すると分かりますが,Mastodon用のdockerコンテナとして,図中に記載している「web」「streaming」「db」「redis」「sidekiq」の5つのコンテナイメージおよびその設定ファイル(docker-compose.yml)が用意されています(nginxは自らホスト上に建てる必要があります).当たり前なのですが,本記事で紹介する基本的な動作原理においては,「docker環境」であろうと「非docker環境」であろうと大きな差分はありません.

以下,それぞれ詳細を説明したいと思います.

Webサーバ(nginx)

www.slideshare.net

みんな大好きnginxです.Apacheに比べとても軽量で,スケールすることが知られています.もちろんWebサーバならばなんでもよいのですが,MastodonGithubリポジトリではnginxが推奨されており,サンプルの.confファイルも提供されています.

具体的な役割ですが,設定例を見ると分かりやすいです.そこで,イメージを以下に示します(ただし,この設定は説明用のため,本質ではないところは省略しています.決してコピペして使わないで下さい).

# 1. HTTPをHTTPSへリダイレクト
server {
  listen 80;
  listen [::]:80;
  server_name example.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name example.com;

 # 2-1. HTTPSの設定
  ssl_protocols TLSv1.2;
  ssl_ciphers EECDH+AESGCM:EECDH+AES;
  ssl_ecdh_curve prime256v1;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;
  ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

  # 2-2. HSTSの設定
  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    try_files $uri @proxy;
  }

  # 3. 画像サーバへリダイレクトする際にCDN向けheaderを追加する設定
  location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    try_files $uri @proxy;
  }

  # 4-1.「web」(ブラウザからのリクエスト,REST API)へのリバースプロキシ
  location @proxy {
    proxy_pass http://127.0.0.1:3000;
  }

  # 4-2.「streaming」(Userstream)へのリバースプロキシ
  location /api/v1/streaming {
    proxy_pass http://127.0.0.1:4000;
  }

}

主な役割は以下の通りです.

項番 役割
1 HTTPをHTTPSへリダイレクトする
2 HTTPS,HSTSなどセキュリティ周りの設定を行う
3 特定のコンテンツに対してのみ,キャッシュを要求する
4 リバースプロキシ

1は分かりやすいと思いますので説明は割愛します.2, 3, 4について,以後で説明します.

Letsencript(HTTPS

Let's Encrypt の使い方 - Let's Encrypt 総合ポータル

個人のWebサイトでHTTPS対応をする場合,数年前まではそれなりに高額な費用が発生していました.しかし今となっては,「letsencrypt」を使えば,無料でかつ簡単に証明書を取得することができます.

Mastodonでは,HTTPSを使うことが強く推奨されていますし,構築マニュアルでもこのletencryptが紹介されています.HTTPS対応をしない場合,Google Chromeで閲覧すると警告画面が出てしまうだけでなく,Google等の検索エンジン最適化(SEO)でも不利だと言われています.letsencryptを使って,是非HTTPS化には対応しておきたいところです.

letencryptは,提供されるいくつかの方法のうち,いずれか一つでサーバを認証すれば,HTTPS対応に必要な証明書を発行してくれます.nginxのconfig中の項番「2」に該当する「 /etc/letsencrypt/live/example.com/fullchain.pem 」等が,実際にletsencryptから発行された証明書を表しています.

余談ですが,letencryptは,通常の証明書に比べその有効期限が非常に短い(3ヶ月)です.そのため通常は,cronなどのジョブスケジューラを使って,3ヶ月に1回以上の頻度で自動の証明書更新を設定する必要があります.

CDNとキャッシュ

www.slideshare.net

CDN(Content Delivery Network)は,世界中に分散設置されたキャッシュサーバの塊です.例えばAppleが,iPhoneのOSイメージを配布するケースを考えましょう.アメリカのデータセンタにだけサーバを立ててしまうと,日本やヨーロッパからの大量のアクセスがあった場合,都度アメリカからデータを配信することになってしまいます.1万回ダウンロードの要求があれば,全く同じOSイメージが1万回も海を渡るのです.これでは非効率です.そこでCDNは,よくアクセスされるコンテンツを自動的にキャッシュします.そして,日本からのアクセスでは日本のキャッシュサーバから,ヨーロッパからのアクセスではヨーロッパのキャッシュサーバからコンテンツを配信します.

このCDNによって,ユーザがダウンロード時間の短縮というメリットを受けることができるだけではなく,配信サーバの管理者側もサーバ負荷を削減することができるのです.夢のような話ですね.これもLetencriptと同じで,昔は有料のサービス(Akamaiなど)しかなかったのですが,近年は「CloudFlare」に代表される無料CDNサービスが登場しました.なぜ無料なのか不思議でならないのですが,一定の制限はありつつも,普通に使えます.

Mastodonの場合,CDNを使うことは必須ではありません.しかしながら,特に投稿画像やカスタム絵文字などについては,CDNを通すことでユーザのレスポンスを大幅に改善することができます.更に,CDN事業者によっては,DDoS攻撃を緩和してくれるサービスを提供している場合もあります.つまり,CDNを通すことでセキュリティを高めることも可能なのです.そのため,一定規模以上のインスタンスでは,CDNを活用しているケースが多いようです.

さて,nginxの話に戻ります.項番「3」は,CDNを強く意識した設定です.これは,CDNサーバに対して,「emoji(カスタム絵文字)」や「media_attachments(投稿画像)」については最大限キャッシュして下さいね,という指示を送るための設定です.nginxではこういった設定までも簡単に行えるのです.

リバースプロキシ

プロキシサーバとリバースプロキシサーバの違い | ITSakura

残りの項番「4」は,リバースプロキシの設定です.リバースプロキシは様々な目的で用いられますが,これまでに説明したようなnginxを通すことによるメリットを教授するため,MastodonのWebサーバに直接接続するのではなくnginxを "噛ます" ための設定だとご理解下さい.

リバースプロキシは説明し始めると長いのでこの辺で終わりにします.

フロントエンド(rails,Node.js)

Mastodonの主要部分はRubyで書かれています.実際ソースコードを眺めると,かなりの部分がRuby on Railsで書かれていることが分かるかと思います.この部分は詳細を説明し始めると長くなりますので割愛します.ただ,基本部分は前述の通りrailsで実現されているものの,UserStreamだけはNode.jsで実現されていることがポイントかと思います.

Node.js

www.slideshare.net

Node.jsはストリーミングAPIを提供することのできるサーバサイドJavascriptです.私はインフラ屋さんなのでWeb系の技術には大変疎いのですが,それでも知っている超有名なサーバサイドJavascriptです.「ノンブロッキングI/O」を実現していて,シングルスレッドで実行するにもかかわらず応答が帰ってくるまでの間の排他を必要としないため,非常に高速かつスケーラブルであることが知られています.つまり,多量のユーザから同時にUserstreamの受信要求があったとしても,ある程度スケーリングするようになっています.

DB(postgres,swift)

postgres

employment.en-japan.com pgtune.leopard.in.ua

基本的なデータベースとしてはSQLであるpostgresqlが採用されています.つまり,皆さんの過去のトゥートは,全てこのpostgresの中に入っていることになります.例えば,ユーザがタイムラインを表示してくれというアクセスをした場合、そのユーザはrails(web)やNode.js(streaming)を通して,postgresのDB内を参照しています.

なお,postgresは大変取り扱いやすいSQLではありますが,Mastodonにおいて最もスケーリングが難しい部分と言われています(まあ,DBがネックになるのはMastodonだけではないのですが・・・).特にpostgresは,RAMをバカ食いします.RAMのチューニングがとても重要です.

チューニングについては,pgtuneというサイトが便利です.なんと自動で,マシン環境に応じた設定ファイルを生成してくれます.サンプルとして,以下に,pgtuneを使って作成した,postgresql.conf設定例を示します.

max_connections = 50
shared_buffers = 1GB
effective_cache_size = 3GB
maintenance_work_mem = 256MB
checkpoint_completion_target = 0.7
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 4
effective_io_concurrency = 2
work_mem = 20971kB
min_wal_size = 1GB
max_wal_size = 2GB
max_worker_processes = 2
max_parallel_workers_per_gather = 1
max_parallel_workers = 2

一番上に記載している「max_connections」は,とても重要な設定です.同時接続コネクションをいくつまで許容するかを決定します.そのうえで,他の部分で各コネクション毎のメモリの使い方を指定することで,postgres全体のチューニングを行いますとなります.postgresでは,同時にたくさんのコネクションを接続するためには多数のRAMを必要とするため,RAMが少ないマシンではコネクションを制限してRAM消費を減らす,などの工夫が必要となります.もし,マシンの搭載RAMではまかないきれないようなコネクション数を許容する設定にしてしまうと,常にswapが発生する状態となり,パフォーマンスに大きく影響します.pgtuneでは,この辺りの設定を自動で行ってくれるのです.

ただ実際は,よほど大規模なインスタンスではない限り,pgbouncerと呼ばれるコネクションプールツールを使って,RAM不足の問題を簡単に解消することも可能です.コネクションプールは,postgresqlがスケーリングしない理由の一つである「同時にたくさんのコネクションを接続するためには多量のRAMを必要とする」という問題を解決してくれるツールです.

swift/S3

画像の格納にはオブジェクトストレージが使われています.当初はS3にしか対応していませんでしたが,いつからかswiftにも対応しました.本家のS3はお高いので,代わりにS3互換ストレージか,OpenStack swiftを使っている人が多い印象です.

前述の話と合わせると,大半の画像データはCDNがキャッシュしているので、「CDNがS3/Swiftに画像を取りに行く」とも言えますね.

格納されている画像は,「アイコン画像」「トゥートに添付された画像」「カスタム絵文字の画像」の3種類です.ここで,全ての種類の画像は,他のインスタンスから受信した画像も含まれる点に注意してください.言い換えると,自インスタンスの画像は,他インスタンスのDBにもキャッシュされていることになります.

なお,このような他インスタンスのキャッシュを持ち続けると,ストレージ容量が肥大化してしまいます.そこでMastodonでは,ある程度時間のたった古いトゥートに添付された画像については,簡単に削除できるツールが提供されています.もちろん,自インスタンスの画像は消さずに残すことが可能です.一般には,このツールをcron等の定期実行ツールで動かすことが多いです.

余談ですが,この仕様は,過去Pawoo関連で大きな問題を生みました.詳細は割愛しますが,「ポルノ画像が全世界のMastodonサーバーにキャッシュされ,インスタンス管理者が皆逮捕されてしまうのではないか?」との懸念から,Pawooをインスタンス単位でブロックする流れが生まれたのです.現在はインスタンス単位で画像をキャッシュする/しないを選択することができるため,ことなきを得ています(got ことなき).

なお,「docker環境」で構築した場合含め,初期設定では,画像は全てローカルに保存されています.つまり,オブジェクトストレージを一切使わない設定になってしまいます.以前のJP鯖の管理人であるnullkal氏も発言していましたが,このローカル保存は本当にスケーリングしませんし,また後からの環境変更も困難です.スケーリングのためには,AWS S3とまでは言いませんが,S3互換ストレージ,またはswiftを使うべきです.

バックエンド(redis,sidekiq)

f:id:highemerly:20181221002108p:plain
sidekiq

Mastodonの全ての動作は同期処理で行われているわけではありません.主に非同期処理を担うのがsidekiqです.sidekiqは,Railsアプリケーションでよく使われるジョブキューとなります.またsidekiqは,待っているジョブをキャッシュしておく場所として,redisを必要とします.そのため,Mastodonもredisを持っています.

Mastodonにおける非同期処理について説明します.例えば,Mastodonインスタンス間のトゥート配信では,activitypubと呼ばれるプロトコルが使われています.例えばAさんが発言した場合,そのAさんが所属しているインスタンスは,Aさんをフォローしている人がいる他のインスタンス全てに対して,トゥートを配信しなくてはなりません.これは非常に重たい処理ですし,仮に落ちているインスタンスがあればリトライをしなくてはなりません.このような処理を同期処理で実施していては大事になってしまいます.そこで,Mastodonではこういった非同期処理をsidekiqに担わせているのです.

その他の非同期処理としては,トゥート本文中のURLのインラインプレビューを更新する作業,メール送信機能などが上げられます.これら非同期処理はいくつかの「キュー」に分類されていますので,インスタンス管理者は各キューにどれだけサーバリソースを割り当てるか設定することもできます.ただし,初期設定,特にdocker環境では,全ての「キュー」が対等に扱われるようになっています.

キュー

さて,「キュー」には,default,mail,pull,pushの4つがあります.pullとpushが他インスタンスとの連携(activitypub)に関するキューで,defaultとmailが自インスタンスに完結する処理に関するキューです.

キュー名 内容
default インスタンス内部でのトゥート処理
mail 新規登録,フォロー通知などユーザへのメール配信
pull インスタンスからのトゥート受信
push インスタンスへのトゥート送信

ちなみに大規模インスタンスの一つであるPawooでは,スケーリングのためにわざわざこのキューを独自で拡張しているそうで,スケーリングのためにはキューを正しく使いこなすことが重要であることがお分かり頂けるかと思います.

スケーリングのために注意すべきは,sidekiqプロセス自体は,CPUのシングルスレッドしか使えないという点です.しかし,例えば4キューを別々のsidekiqプロセスで処理させることで,簡単にマルチスレッド化が可能です.つまり,使っているCPUが4スレッド以上あれば,うまく別スレッドに割り当てて互いの影響を最小限にすることが可能なのです.

よく,「sidekiqが詰まる」と言っているMastodonインスタンス管理者を見かける,という方もいらっしゃるのではないでしょうか.これは,処理が間に合わず,キューが増え続ける状態を指しています.このように,sidekiqは,特に小中規模インスタンスでは最もスケーリングが難しい部分です.ただし前述のとおり,プロセス数を増やすことで実行スレッドを簡単に増やせること,またDBのような排他処理も不要なため実行サーバを大量に増やすことで並列処理が容易なことから,サーバリソースさえつぎ込めばいくらでもスケールします.つまり,「金で殴る」ことが簡単な部分なのです.

オープンソースとライセンス

少し余談です.

当然,Mastodonを構成する全てのコンポーネントオープンソースとして公開されています.ただし,Mastodonソースコード自身は,AGPLライセンスで公開されていることに注意が必要です.

AGPLライセンスは,GPLと大変よく似たライセンスです.GPLの条項の中には,「利用者からソースコードを求められた場合,必ず公開しなくてはならない」という旨の規定があります.このように,オープンソースをベースに別人が追加開発したソフトウェアであっても,それがオープンソースとなることが保証される点が特徴です.オープンソース界の発展に繋がる,たいへん"強い"ライセンスではありますが,一部企業等からは自ら開発したソフトウェアの収益化が難しくなるため,避けられることもあるライセンスです.

AGPLとGPLの違いは,この「利用者」の定義です.端的に言うと,AGPLでは,利用者の定義が「Mastodonソースコードを改変してWebに公開した場合,そのインスタンスに登録したりトゥートを閲覧する人」にまで広げられています.GPLよりも"強い"と言うことも出来ると思います.

Pawooやfriend.nicoなど,Mastodon本家のコードに追加開発を加えたインスタンスについても,そのソースコードが公開されていることは有名だとは思います.この理由はこのライセンス条項によるものだと考えられます.

終わりに

今日はMastodonを構成する技術要素について話をしました.私の運営する「handon.club」でどのような構成になっているか,という話まではできませんでしたので,今後の記事にご期待頂けると嬉しいです.また,非技術者向けにも分かりやすい解説記事についても書きたいと思っていますので,そちらを楽しみにしている方はしばしお待ち頂ければ幸いです.

もし当記事に明らかな誤り等有ればコメント等でご指摘頂けると大変嬉しいです・・・.

参考にさせて頂いた文献

私がMastodonの技術要素の理解だけでなく,インスタンス構築に当たっても参考にさせて頂いた記事のご紹介です.もちろんこれだけではありませんが,3つに絞ってみました.

qiita.com

inside.pixiv.blog

http://amzn.asia/d/49xq410amzn.asia

追記

2018/12/20 一部説明が不足していた部分を追記しました.まだまだ追記予定です.