🐘

Mastodon メンテナンスメモ(移植版)

2022/01/11に公開約32,900字

Qiitaにて公開されていた記事ですが、都合によりZennへ移行しました。

備忘録として残しておきます

Mastodon専用ユーザにて、Mastodonのインストールディレクトリにて操作することを前提にしています。
(非Dockerならliveディレクトリ)

参考資料

他の記事、ウェブサイトへのリンク集

GitHubリポジトリ

tootsuite / mastodon

公式ドキュメント

Mastodon documentation

インストール手順

Installing from source - Mastodon documentation
公式ドキュメント。Ubuntuにインストールするならこの手順に従う。

Mastodon構築手順・非Docker版
非公式・手前味噌ドキュメント。RHEL系なら参考になるかも。

アップデート手順

Upgrading to a new release - Mastodon documentation
公式ドキュメント。非Docker。

Mastodonアップデート手順(基本)・非Docker版
非公式・手前味噌ドキュメント。内容としてはおおむね重複している。

バックアップ手順

Backing up your server - Mastodon documentation
公式ドキュメント。

サーバ移行手順

Migrating to a new machine - Mastodon documentation
公式ドキュメント。非Docker。

Mastodonサーバ移行手順(非Docker)
非公式・手前味噌ドキュメント。内容としてはおおむね重複している。

スパム対策

Mastodonにおけるスパム対策
非公式・手前味噌ドキュメント。内容が肥大化したため、別記事に分離しました。

サービス保守

tootctl

v2.5.0より、tootctlという保守用コマンドラインツールが追加された。
Rakeタスクでの引数指定方法等への不満から誕生したものである。

既存Rakeタスクは、初期セットアップに使う一部のものを除き、tootctlに置き換えられた。

tootctl周りについては、肥大化してしまったため勝手ながら別投稿に移動した。

勝手 Mastodon tootctl リファレンス

なお、公式ドキュメントもある。
Using the admin CLI - Mastodon documentation

tootctl の実体

https://github.com/tootsuite/mastodon/tree/master/lib/mastodon

ここに入っているファイルたち。

Rakeタスク

主に初期セットアップに使用するコマンドラインツール。
v2.5.0以前は管理タスクも含まれていた。

https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/List-of-Rake-tasks.md

あるいは

# すべてのRakeタスクを表示
$ RAILS_ENV=production bundle exec rails -T

# Namespace "mastodon" のタスクのみ表示
$ RAILS_ENV=production bundle exec rails -T mastodon

で、定義されているタスクが一覧(+簡単な説明文)表示される。
v2.6.1で確認したところでは、殆どの管理タスクがtootctlに移行されている。

details::: タスク一覧の出力例

mastodon_tasks(v2.6.1)
$ RAILS_ENV=production bundle exec rails -T mastodon
rails mastodon:setup                       # Configure the instance for production use
rails mastodon:stats                       # Report code statistics (KLOCs, etc)
rails mastodon:webpush:generate_vapid_key  # Generate VAPID key

:::

Rakeタスクの実体

https://github.com/tootsuite/mastodon/tree/master/lib/tasks

tootctlやrakeタスクをcronで動かしたいとき

cronから直接実行すると、環境変数が読み込まれず動作しない模様

たとえば、毎日0時0分にmedia removeを実行する場合は下記のようにする

crontab
0 0 * * * /bin/bash -l -c 'cd ~/live && RAILS_ENV=production bundle exec bin/tootctl media remove >> ~/log/media_remove.log 2>&1'

crontabでの時間指定の仕方については、ここでは省かせて頂く(検索するとたくさん出てくる)

これによりtootctl等のコマンドを通常通り実行することができる。

※Docker環境での動かし方について情報求む

irbを使った操作(tootctlやrakeタスクを介さない操作)

irb(Interactive Ruby Shell)を用いれば、Mastodonの環境でRubyのコードを直接実行できる。
いろいろできるということは、それなりに危険を伴うのでオススメはできない。

Mastodon用irbコードメモ

irbの起動のしかた

非Docker

$ pwd
/home/mastodon/live
$ RAILS_ENV=production bundle exec rails console
irb(main):001:0>

Docker

$ docker-compose run --rm web rails console
irb(main):001:0>

※Docker環境がないため未検証

トラブルシューティング

不具合かな?と思ったときの切り分け方法など

まず落ち着いて!

  • Lv 1: 異常に重たいけど機能している
    何かしらのバックグラウンドタスクでリソースを圧迫している可能性がある。
    コンソール接続できる場合は、topコマンド等で何がリソースを消費しているか調べる。
    ※一般的に、アクセス集中やフォローリストインポート、アーカイブ生成などで一時的に重くなることがある。

  • Lv 2: 象バンバン(HTTP 503)が出た
    Mastodon関連サービスでエラーが発生している、もしくは記憶領域を使い果たした可能性がある。
    コンソール接続できる場合は、ディスクの空き容量はあるか、Mastodon関連サービスが起動しているか、あるいは後述する「Mastodonサービスのログを見る」で何が原因か突き止める。

  • Lv 3: そもそもつながらない。ブラウザにエラーが出る。
    証明書関連のエラーが出た(信頼できないサーバです、など)場合は、証明書の有効期限をチェックする。
    接続タイムアウトの場合は、ネットワークは正常か・サーバが起動しているか・Nginxサービスが起動しているか・ファイアウォール設定は正しいか・DNS設定が正しいか、などを順番にチェックする。

  • 番外編: アップデート後に不具合が出た
    ログを確認するとともに、アップデート手順に抜けがないかチェックする。
    リリースによっては、追加の作業が必要な場合もあるため。リリースノートを再度チェックする。
    ハマりがちなところ:

    • データベースマイグレーション実行忘れ … システムが動作不良を起こす。mastodon-web のログに「column not found」などのエラーが出る。
    • アセットコンパイル忘れ … 画面の動きがおかしくなったり、画像がリンク切れになったりする。必要に応じてclobberをしてからprecompileを実施する。
    • 一時ディレクトリアクセス権の問題 … 画像のアップロードができなくなり、画面上に500エラーが出る。
      nginxのエラーログに「access denied」といったエラーが出る。エラーが出ているディレクトリの所有者を nginx.confで指定したユーザーにする
    • サブリソース整合性の問題 … 画面が真っ白になるなど、正常に描画されなくなる。ブラウザのコンソールにFailed to find a valid digest in the 'integrity' attribute for resource ~ などといった警告が出る。配信経路上でリソース(JavaScript等)の改竄が検出されたことによる読み込み阻止が働いている。たとえばCloudflare等のCDNで「JavaScript等の最適化」といったオプションが有効になっている場合はオフにすることで改善される。

ログの見かた

非Docker環境です。
Docker環境での情報求む。

Mastodonサービス

$ sudo journalctl -r -u mastodon-web
$ sudo journalctl -r -u mastodon-sidekiq
$ sudo journalctl -r -u mastodon-streaming

-r で新しいものを先に表示
-u で表示するモジュールを指定
-f も付ければ、継続監視が可能(tailみたいに)

PostgreSQL

$ sudo journalctl -r -u postgresql

Nginx

$ sudo tail /var/log/nginx/access.log
$ sudo tail /var/log/nginx/error.log

継続して監視したい時は、 tail -f が便利

よく使うやつのメモ:

連合からの投稿の受け口(inbox)を除外して継続監視
$ sudo tail -f /var/log/nginx/access.log | grep -v " /inbox "
Mastodonからのアクセスを除外して継続監視
$ sudo tail -f /var/log/nginx/access.log | grep -v " (Mastodon/"
特定のHTTPステータスコードを返しているものを探す
$ sudo cat /var/log/nginx/access.log | grep " 404 "
Botっぽいものを探す
$ sudo tail -f /var/log/nginx/access.log | grep -E "[Bb]ot"

ディスクの空き容量を確保したいとき

連合経由でとめどなく情報が流れてきて蓄積していくので、何の対策もとっていないと、恐ろしい速度でディスク領域を食いつぶしていきます…

メディアファイル類のキャッシュ削除

メディアファイル(添付された画像・音声・動画)、プレビューカードのサムネイルなどのキャッシュファイル。
空き容量が逼迫している場合、真っ先に着手すべきはこれらのクリーンアップを行こと。

メディアファイルキャッシュ

外部のメディアファイルの削除には、tootctlコマンドを用いる。
参考→ 勝手 Mastodon tootctl リファレンス - 外部サーバの古いメディアファイルを消す(remove)

外部のメディアファイルは再取得可能で、最悪発信元サーバを見に行けば問題ない。
また、自サーバのメディアファイルについては対象としない。

孤立メディアファイル

どこからもリンクされていない、孤立したメディアファイルを削除する。
参考→ 勝手 Mastodon tootctl リファレンス - どこからも参照されていないメディアを削除する (remove-orphans)

これは、ファイルシステムを走査してどこからも参照されていないメディアファイルを見つけ出し、削除するもの。
※オブジェクトストレージ等、ディスク操作に課金されている環境の場合、十分に検討した上で使用しすること。

プレビューカード

参考→ 勝手 Mastodon tootctl リファレンス - 古いプレビューカードを削除する(remove)

プレビューカードを消したところでリンクの動作に問題が起こるわけではない。
また、一定期間後に再度リンクが貼られた場合、再取得しに行くため、恒久的に生成されなくなるわけではない。

投稿キャッシュ

誰からもフォローされていないユーザの古い投稿を削除する。

参考→ 勝手 Mastodon tootctl リファレンス - 過去の投稿を削除する(remove)

参考資料のリンク先にも記載があるが、これを実行しただけではディスク使用領域を解放することができない。
VACUUM FULLもしくはpg_repackを実行すること。

存在しないユーザの削除

すでに存在しない外部ユーザを削除する。

参考→ 勝手 Mastodon tootctl リファレンス - 存在しないリモートユーザを削除する(cull)

アイコンやヘッダー画像のクリーンアップの意味合いが強いが、あまり効果的ではないように思う。

メディアファイルをオブジェクトストレージに逃がす

ある程度のコスト増を飲めるのであれば、メディアファイルをオブジェクトストレージに逃がすことで、ローカルの空き容量を大きく確保することができる。

参考1 → Mastodon docs - Configuring your environment - File storage
参考2 → MastodonのメディアファイルをOpenStack Swift互換オブジェクトストレージに移行する

オブジェクトストレージの課金システムについては様々なバリエーションがあるので、注意深く選定されたし。

Ruby Gem を再インストールしたいとき

稀に壊れたりすることがあるようです

$ bundle pristine

thanks @theoria !

あるいは

$ bundle exec gem uninstall -aIx
$ bundle install

引数 -aIx で、「全てを確認無しでアンインストール」
その後、通常通りインストールする

bundler 経由で操作しないと、グローバル環境のGemに作用する?らしいので注意。

アセットをゼロから作り直したいとき

$ RAILS_ENV=production bundle exec rails assets:clobber
$ RAILS_ENV=production bundle exec rails assets:precompile

clobber で webpackの出力を全消しできる。
長いことclobberをしていないと、古いアセットが残りっぱなしになることがある(らしい)ので、たまにやった方がいいかもしれない。

パフォーマンスチューニング

(他にやれそうなことがあったら教えてください)

スワップファイルの作成

低スペックのサーバ、特にメモリが2GB未満のサーバで動作させていると
OOM Killer(Out-Of-Memory Killer)によってbundlerなどの重要なプロセスが強制終了されてしまうことがある。

ローカルディスクの空き容量に余裕がある環境であれば、スワップファイルを用意することでスワップメモリとして容量を拡張することができる。

# 2GBの /swapfile を作成する 
$ sudo dd if=/dev/zero of=/swapfile bs=1M count=2048

# ファイルをスワップ領域としてマークし、マウントする
$ sudo mkswap /swapfile

# スワップを一時的に有効にする
$ sudo swapon

# スワップファイルを永続化する
$ sudo vim /etc/fstab
/etc/fstabに追記するやつ
/swapfile                                 swap                    swap    defaults        0 0

ただし、スワップファイルはメインメモリに比べると 圧倒的に遅い ため、できればメモリを2GB以上に拡張することをお勧めする。

PostgreSQL

サーバスペックに応じたパラメータを設定する

たとえば、pgTune( http://pgtune.leopard.in.ua/ )を参考に、サーバのスペックに合ったパラメータを設定する。

定期的にvacuumを行う

標準機能のautovacuumを有効にしたり、cron等で定期的にvacuumdbコマンドを実行する。
(環境によってはautovacuumが無効になっているので要確認。postgresql.confに設定項目がある。)

それにより、データベースファイルの肥大化を防いだり、多少の速度アップが望めるかもしれない。

cronでvacuumdbを行う場合のcrontabの例
0 4 * * * /usr/bin/vacuumdb -z -U dbuser -d dbname >> ~/log/vacuum.log 2>&1

上記例では、毎日4時に権限のある全テーブルに対し、バキュームおよびオプティマイザ用統計情報の計算を行う。
テーブルロックがかからないとはいえ、それなりに負荷がかかることが想定されるので、アクティブな時間を外すとよい。

pg_repack する

通常のvacuum を定期的に行っても、テーブルサイズの肥大化を完全に抑えることはできない。
vacuum full を実行すれば余計な領域を解放させることが可能だが、処理中はテーブルロックがかかってしまうため、実行中はサービス提供ができない。
それに対する解決策がpg_repackである。

これは、最小限のロックでvacuum full 相当の操作をすることができるツール。
また、必要に応じてデータの並び替え(cluster相当)も行うことが可能。

pg_repack 公式ドキュメント

pg_repackのインストール

事前に必要なモジュールをインストールし、pg_repackのビルドとインストールを行う。

一般ユーザ作業
$ sudo yum install rpm-build postgresql-devel postgresql-static

$ git clone https://github.com/reorg/pg_repack.git
$ cd pg_repack
$ make
$ sudo make install

データベース拡張としてpg_repackを登録する

postgresqlスーパユーザ作業
$ sudo su - postgres
$ psql -c "CREATE EXTENSION pg_repack" -d mastodon
CREATE EXTENSION
$ 

pg_repackの効果測定

現在のテーブル容量を見る

postgresqlスーパユーザ作業
$ psql -d mastodon

mastodon# SELECT pg_size_pretty(pg_total_relation_size('statuses'));
 pg_size_pretty 
----------------
 3041 MB
(1)
mastodon# \q

pg_repackを実行する

postgresqlスーパユーザ作業
$ pg_repack -d mastodon -t statuses

実行後のテーブル容量を見る

postgresqlスーパユーザ作業
$ psql -d mastodon

mastodon# SELECT pg_size_pretty(pg_total_relation_size('statuses'));
 pg_size_pretty 
----------------
 26 MB
(1)
mastodon# \q

マジか

mastodonデータベース全体にpg_repackを実行する

postgresqlスーパユーザ作業
$ pg_repack mastodon
INFO: repacking table "public.account_aliases"
INFO: repacking table "public.account_conversations"

.
. (中略)
.

INFO: repacking table "public.web_push_subscriptions"
INFO: repacking table "public.web_settings"
$ 

vacuum full相当の操作を行うものなので、タプルの追加削除が頻繁に行われるテーブルに対して行った方が効果が高い。(逆に、追加削除がほとんど行われていないテーブルに対して実行してもさして効果はない)
定期的に行うよりも、デッドタプルが増えてきたら実行する、くらいの感じがちょうどよいかもしれない。

PostgreSQLのvacuumについて調べたメモ - vacuumすべきか否かの判断

接続にSocketを使う

Unix Socketを使うことで、localhost接続よりも高速化が見込めるかもしれない。

/var/lib/pgsql/data/postgresql.conf
listen_addresses = '' # TCP/IPでの待ち受けが必要ない場合は空白とする

unix_socket_directories = '/var/run/postgresql' # ソケットファイルの場所を指定する

デフォルトでは /tmp をソケットディレクトリとして使用する。必要に応じて場所を変更する。
設定を変更したら、有効にするためPostgreSQLサービスを再起動する。

続いて、Mastodon側の設定も変更する。

/home/mastodon/live/.env.production
DB_HOST=/var/run/postgresql

設定を変更したら、Mastodonサービス3点セットを再起動する。

pgBouncer を使う

PgBouncer とは

PgBouncer は、軽量なコネクションプーラーです。
通常、クライアントからデータベースに対して大量の接続が発生すると、接続を確立する為の時間のコストや、接続を管理する為のコンピュータリソースが多く必要となります。
このようなケースにおいて PgBouncer を使用すると、これらのコストを劇的に減少させる事ができます。
http://postgres.sios.com/modules/newbb/viewtopic.php?forum=1&topic_id=149&post_id=150

コネクションの開閉にはそこそこパワーを使うので、プールしておいて有効に使おうよ、という仕組み。

Mastodon公式ドキュメントにも、pgBouncerに関する記事がある。

Mastodon documentation - Scaling up your server - Transaction pooling with pgBouncer

Redis

Socketを使う

PostgreSQLと同様、Unix Socketを使うことで、localhost接続よりも高速化が見込めるかもしれない。

/etc/redis.conf
port 0 # TCP/IPでの待ち受けが必要ない場合はゼロとする

unixsocket /var/run/redis/redis.sock
unixsocketperm 777

デフォルトではソケットを使用しない設定になっているので、既存の設定値を消すかコメントアウトした上、ソケットの設定を追加する。

設定を変更したら、Redisサービスを再起動する。

続いて、Mastodon側の設定も変更する。

/home/mastodon/live/.env.production
REDIS_URL=unix:///var/run/redis/redis.sock
#REDIS_HOST=127.0.0.1
#REDIS_PORT=6379
#REDIS_PASSWORD=

ソケットはREDIS_HOSTでなくREDIS_URLに対して設定する点に注意!

設定を変更したら、Mastodonサービス3点セットを再起動する。

Mastodon

Tuning Mastodon (リンク切れ)

jemalloc を使ってメモリ最適化

jemallocとは?

jemalloc は、標準ライブラリで定義されているmalloc, free 等のメモリアロケーション APIの実装である。
http://zonomasa.hatenablog.com/entry/jemalloc_about

詳しい機能については引用元のWebページを参考にして頂きたいが、わたしのざっくりとした理解では:

  • メモリのフラグメンテーションを防止し、有効に使われるようにする
  • マルチコア・スレッド環境においてパフォーマンス向上
  • メモリ周りの不具合(リーク、オーバーフロー等)の検査ツール付

また、このパッケージについて公式ドキュメントでも紹介されている。
Tuning Mastodon - Using jemalloc

Debian系(Ubuntu等)においては、上記公式ドキュメント通りに設定すればよいが、Fedoraにおいてはパッケージ名等が違うので注意が必要。

  • パッケージ名: jemalloc (jemalloc-5.0.1-5.fc28.x86_64 等)
  • インストールパス: /usr/lib64/libjemalloc.so.2

jemallocの導入

yum-install
$ sudo yum install jemalloc jemalloc-devel
$ ls /usr/lib64/libjemalloc.so.2 
/usr/lib64/libjemalloc.so.2

Mastodonに対して設定する前に、Rubyをjemallocに対応させておく必要がある。
jemalloc対応版かどうかを確認し、対応していない場合はRubyの再インストールが必要となる。

確認・アンインストール・インストール手順についての参考資料: rbenvでインストールしたRubyのバージョンを上げる

Mastodonサービス側の設定:jemallocの有効化

mastodon-web.service および mastodon-sidekiq.service に対し、
jemallocを読み込ませるため Environment="LD_PRELOAD=/usr/lib64/libjemalloc.so.2" を追記する。
(下記はwebの例だが、sidekiqも同じように追記する。)

mastodon-web.service
[Unit]
Description=mastodon-web
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
Environment="PORT=3000"
Environment="LD_PRELOAD=/usr/lib64/libjemalloc.so.2"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec puma -C config/puma.rb
ExecReload=/bin/kill -SIGUSR1 $MAINPID
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target

編集後、更新されたファイルを再読み込みし、webとsidekiqを再起動する。

$ sudo systemctl daemon-reload
$ sudo systemctl restart mastodon-{web,sidekiq}

あまり知られていないであろう設定項目

おもに .env.productionにて設定する項目たちです

セキュア(Authorized Fetch)モード

インライン署名されたアクティビティを拒否し、他サーバが公開・非収載の投稿を取得しようとするときに認証を必須とする。
それにより、ブロックしているサーバが投稿を取得していくことを防ぐことができる。(完全に防ぐことを保証するものではない)

ただし、計算コストが上がり、古いバージョンと互換性がなくなる(たとえばMastodon v3.0以前)、連合リレーに参加できなくなる、など制限が発生する。

設定方法

Step 1: .env.production ファイルに、Authorized Fetchモードを有効にする一文を追記する。

.env.production
AUTHORIZED_FETCH=true

Step 2: Mastodonサービスを再起動する。

連合制限モード

どのバージョンからなのかは不明だが、ホワイトリストモードが追加された。
これは、 特定のサーバと対してのみ やりとりをできるようにするモードである。
v3.2.0より、「連合制限モード(limited federation mode)」と名前が改められた。

連合制限モードの特徴

  • 許可リストに登録しているサーバ相手なら、普段通りやりとりができる。
  • 途中で連合許可モードに切り替えた場合、許可リストに登録していないサーバを一括パージできるコマンドがある(tootctl domain purge --limited-federation-mode) → 参考資料
  • 非ログイン状態では、ほとんどのパブリックページへのアクセスができなくなる。
  • /about/about/more/pubiuc/@username(ユーザの個別ページ) 等へのアクセスは、ログイン画面にリダイレクトされる。
  • API経由でも同様。/api/v1/insances/api/v1/accounts/:id 等へのアクセスにはログインが必要となる。
  • 許可リストにないサーバからでもアカウント名を決め打ちで検索すれば、アカウントの存在を知ることはできる。
  • 許可リストにないサーバからの接触(フォロー、リプライ等)は弾かれると思われる。(未検証)

設定方法

Step 1: .env.production ファイルに、連合許可モードを有効にする一文を追記する。

.env.production
# v3.1.5まで
WHITELIST_MODE=true

# v3.2.0rcから
LIMITED_FEDERATION_MODE=true

Step 2: Mastodonサービスを再起動する。

Step 3: 管理者でログインし、設定画面の [管理] → [既知のサーバー] ページを開き、[連合を許可]より許可リストへ追加を行う。

Step 4: 許可リストにないサーバーを切り離す

$ sudo su - mastodon
$ RAILS_ENV=production bundle exec bin/tootctl domains purge --limited-federation-mode

その他小ネタなど

  • ホスティングサービス等を使用せず、独自にサーバを立て、収益を上げるような仕組み(広告、アフィリエイト、寄付など)を設け、自分以外の第三者にも使用できる状態にした場合、各地域の総合通信局に「電気通信事業届出」が必要となります。(未届で行った場合、一応罰則もあるようです)
  • 「サイト設定」内に連絡先を記載。メールアドレス、管理者欄を埋める。他のサーバのアカウントなどがあれば併記しておく。サーバに何かあったときに、誰かが知らせてくれるかも。

assets:precompileしたらbabel系モジュールが見つかりませんと言われてエラーになる

v3.4.5 → v3.4.6にアップデート後、assets:precompileしたところ発症。
大量にスタックトレースが出力され何事かと慌てた。

Cannot find module '@babel/helper-environment-visitor'

同様の問題がbabelのissueに上がっている。
古いキャッシュが悪さしているかも?ということで、既存のnode-moduleをリネーム退避させて再度yarn installしたところ直った。

$ cd ~/live
$ mv node-modules node-modules_bk
$ yarn install
$ RAILS_ENV=production bundle exec rails assets:precompile

「お知らせ」の時差(JST to UTC)

タイムラインの上に出る「お知らせ」を作成する際、日付時刻の範囲や掲載開始日時を設定できるが
これはJST(日本標準時)でなくUTC(協定世界時)で設定しなければならない。

UTC・JST間の時差は +9 時間である。

つまり、日本標準時で昼の12時を指定したい場合は、協定世界時の早朝3時にセットする必要がある。

時差を計算するサイトも多数あるので、それらを使うと楽。

keisan by CASIO

時差計算

アーカイブファイルの読み方

Mastodonの「 設定 > インポート・エクスポート > アーカイブのリクエスト 」を用いると、自分が投稿した文章や画像をまとめてダウンロードできるようになります。
しかし、内容はActivityPub形式の大きなJSONファイルのため、そのまま読める形にはなっていません。

手前味噌ですが、ActivityPub形式のoutbox.jsonを読める簡易リーダーを試作しました。

https://gist.github.com/Neustrashimy/51442fe6932c86d2555e18bada0d6c93

全文検索に対応していないサーバでも、この方法であればブラウザのテキスト検索機能を使用して検索することができます。

謎のファイルが /tmp を食いつぶす問題

/tmp/magick-* (ImageMagickの一時ファイル)

Mastodon側で画像の縮小・メタ情報削除を行う処理を行うようになっているため、そのような一時ファイルが生成されます。
また、何らかの原因により、一時ファイルが削除されず残ってしまうことがあるようです。

/tmp は様々なアプリケーションが一時ファイル置き場として使うので、使い切ってしまうとサーバごと機能不全に陥る可能性があります。

応急処置としては、手動で一時ファイルを削除することで解消できます。

$ rm /tmp/magick-*

再発する場合は、ImageMagickが使用するディスク領域を制限することで解消を図ります。

ImageMagickのリソース制限値表示(Before)
$ identify -list resource
Resource limits:
  Width: 214.7MP
  Height: 214.7MP
  List length: unlimited
  Area: 2.0403GP
  Memory: 972.867MiB
  Map: 1.90013GiB
  Disk: unlimited
  File: 768
  Thread: 2
  Throttle: 0
  Time: unlimited

Disk: unlimited になっているため、限界まで使用しようとします。これを制限します。

$ sudo vim /etc/ImageMagick-6/policy.xml
policy.xml
<policymap>
  <policy domain="resource" name="disk" value="256MB"/>
</policymap>

<policymap> 以下にコメントアウトされている設定値が羅列してありますので、該当部分をコメントアウトして設定し、保存します

ImageMagickのリソース制限値表示(After)
$ identify -list resource
Resource limits:
  Width: 214.7MP
  Height: 214.7MP
  List length: unlimited
  Area: 2.0403GP
  Memory: 972.867MiB
  Map: 1.90013GiB
  Disk: 256000000B
  File: 768
  Thread: 2
  Throttle: 0
  Time: unlimited

Disk値が制限されました。

とりあえず、これで様子を見てみます。

Paperclipの一時ファイル(v3.1.4)

$ ll /tmp
total 138992
-rw------- 1 mastodon mastodon   388526 May 21 13:21 8d777f385d3dfec8815d20f7496026dc20200521-41299-100pphy
-rw------- 1 mastodon mastodon   201603 May 21 13:21 8d777f385d3dfec8815d20f7496026dc20200521-41299-102g7ck
-rw------- 1 mastodon mastodon   106475 May 21 13:21 8d777f385d3dfec8815d20f7496026dc20200521-41299-1052yfm
-rw------- 1 mastodon mastodon    41792 May 21 13:21 8d777f385d3dfec8815d20f7496026dc20200521-41299-10g2myt
-rw------- 1 mastodon mastodon    54901 May 21 13:21 8d777f385d3dfec8815d20f7496026dc20200521-41299-10indac
-rw------- 1 mastodon mastodon    26021 May 21 13:21 8d777f385d3dfec8815d20f7496026dc20200521-41299-10rv5ny
-rw------- 1 mastodon mastodon   416685 May 21 13:21 8d777f385d3dfec8815d20f7496026dc20200521-41299-10ytz90
-rw------- 1 mastodon mastodon    23994 May 21 13:21 8d777f385d3dfec8815d20f7496026dc20200521-41299-116kyt4
-rw------- 1 mastodon mastodon     9726 May 21 13:21 8d777f385d3dfec8815d20f7496026dc20200521-41299-11d09gv
  • 先頭の文字列(8d777f385d3dfec8815d20f7496026dc20200521)はSidekiqのJID
  • 数字(41299)は、mastodon-sidekiqのプロセスID
  • 末尾の文字列(11d09gv等)はSidekiqのTID

であり、Paperclip(ファイル等を操作するライブラリ)によって生成された一時ファイルだろうと思われる。

Sidekiqの管理画面上で該当するジョブが走っていなければ、おそらく消し忘れなので、手動で消しても差し支えないように思う。

PostgreSQL照合順序によるインデックスの不整合

2018年頃、PostgreSQLデータベースの照合順序として使われているglibcのロケールデータが更新された。
その結果、本来一意になるはずのインデックスの重複判定がうまくされないことによって、データが重複してしまう可能性がある。

glibc 2.2.8 以前でインデックスを作り、それ以降のバージョンでインデックスの再作成をしていない場合、影響を受ける可能性がある。

参考:glibcモジュールのバージョン確認
# rpmコマンドで
$ rpm -q glibc
glibc-2.31-4.fc32.x86_64

# またはdnf、yumなどのパッケージマネージャで
$ dnf list installed | grep glibc
glibc.x86_64                                2.31-4.fc32                      @updates               
glibc-common.x86_64                         2.31-4.fc32                      @updates               
glibc-devel.x86_64                          2.31-4.fc32                      @updates               
glibc-headers.x86_64                        2.31-4.fc32                      @updates               
glibc-langpack-en.x86_64                    2.31-4.fc32                      @updates      

自分の環境が影響を受けているのかチェック

Mastodonデータベースの照合順序をチェックし、影響を受ける可能性があるか見る。
ディストリビューションによって影響を受ける照合順序が異なるようなので、各自 PostgreSQL公式サイトの情報 を参照し、判断を行うこと。

参考:データベースの照合順序を調べる。Postgresユーザ作業
$ psql -c "SELECT datcollate FROM pg_database WHERE datname=current_database()" -d mastodon
 datcollate  
-------------
 en_US.UTF-8
(1 row)

以下の情報は、PostgreSQL公式Wikiの情報を和訳したものです。(20201230版)
最新ではなく、誤訳の可能性があるので、できれば ソース を読んでください。

Debian

Version 8 (jessie) と 9 (stretch)は古いロケールデータを使っています。それらのリリースでは互換性のない変更は確認されていません。Version 8 から 9 へのアップグレードは安全です。
Version 10 (buster) は新しいロケールデータを使っています。そのため、アップグレード時に注意が必要です。

以下の情報も参照してください:

https://lists.debian.org/debian-glibc/2019/03/msg00030.html

Ubuntu

Ubuntuは Version 18.04 (bionic) まで古いロケールデータを使っています。
新しい glibc 2.28 ロケールデータは (LTS版でない) Version 18.10 (cosmic) で登場しました。
bionicもしくはそれ以前から、cosmicより新しいバージョンへのアップグレードを行う際は、緩和手段が必要です。

RHEL/CentOS

バージョン 6 と 7 は古いロケールデータを使っています。バージョン 6 から 7 へのアップグレードは安全ですが、 de_DE.UTF-8 ロケールが使用されていた場合は別です。(その他のロケール、およびde_DE.UTF-8を除くde_*.UTF-8ロケール、たとえば de_AT などは安全です。)
Version 8 は新しいロケールデータを使っています。そのため、アップグレードする際は注意が必要です。

実際にインデックスが壊れているのかチェック

Mastodon公式サイトでは、PostgreSQLのamcheck拡張によるチェック方法が載っている。
amcheck拡張はpostgresql-contribパッケージに含まれているので、事前にそれをインストールしておく。

amcheck拡張について、詳しくは下記ページを参照されたし。
PostgreSQL 10.5文書 - 付録F 追加で提供されるモジュール - F.2. amcheck

一般ユーザ作業
$ sudo dnf install postgresql-contrib
amcheck拡張を登録し、psqlを起動する。Postgresユーザ作業
$ psql -c "CREATE EXTENSION IF NOT EXISTS amcheck" -d mastodon
CREATE EXTENSION

$ psql -d mastodon
psql (12.4)
Type "help" for help.

下記SQLは、どのリレーションが影響を受けたのか分かりやすくするためc.relnameを表示するようにしているが、それ以外はMastodon公式情報と全く同じ。

Postgresユーザ作業(psql)
SELECT c.relname, bt_index_check(c.oid)
FROM pg_index i
JOIN pg_class c ON i.indexrelid = c.oid
WHERE c.relname IN ('index_account_domain_blocks_on_account_id_and_domain',
  'index_account_proofs_on_account_and_provider_and_username',
  'index_accounts_on_username_and_domain_lower', 'index_accounts_on_uri',
  'index_accounts_on_url', 'index_conversations_on_uri',
  'index_custom_emoji_categories_on_name',
  'index_custom_emojis_on_shortcode_and_domain',
  'index_devices_on_access_token_id', 'index_domain_allows_on_domain',
  'index_domain_blocks_on_domain', 'index_email_domain_blocks_on_domain',
  'index_invites_on_code', 'index_markers_on_user_id_and_timeline',
  'index_media_attachments_on_shortcode', 'index_preview_cards_on_url',
  'index_statuses_on_uri', 'index_tags_on_name_lower',
  'index_tombstones_on_uri', 'index_unavailable_domains_on_domain',
  'index_users_on_email', 'index_webauthn_credentials_on_external_id'
);
                           relname                          | bt_index_check 
-----------------------------------------------------------+----------------
 index_accounts_on_username_and_domain_lower               | 
 index_users_on_email                                      | 
 index_account_domain_blocks_on_account_id_and_domain      | 
 index_account_proofs_on_account_and_provider_and_username | 
 index_devices_on_access_token_id                          | 
 index_invites_on_code                                     | 
 index_conversations_on_uri                                | 
 index_custom_emojis_on_shortcode_and_domain               | 
 index_accounts_on_uri                                     | 
 index_accounts_on_url                                     | 
 index_custom_emoji_categories_on_name                     | 
 index_domain_allows_on_domain                             | 
 index_domain_blocks_on_domain                             | 
 index_unavailable_domains_on_domain                       | 
 index_markers_on_user_id_and_timeline                     | 
 index_email_domain_blocks_on_domain                       | 
 index_media_attachments_on_shortcode                      | 
 index_preview_cards_on_url                                | 
 index_tombstones_on_uri                                   | 
 index_statuses_on_uri                                     | 
 index_tags_on_name_lower                                  | 
 index_webauthn_credentials_on_external_id                 | 
(22 rows)

bt_index_checkの列が空白であれば影響を受けている可能性は小さい。
逆に何かしらメッセージが出た場合は影響を受けている可能性がある。

メッセージが出なかった場合、さらに深く(親子関係も含めた)チェックを行うことが推奨されている。
このチェックはShareLockを要求するため、実行中はデータベースへの書き込みができなくなるので注意。

Postgresユーザ作業(psql)
SELECT c.relname, bt_index_parent_check(c.oid)
FROM pg_index i
JOIN pg_class c ON i.indexrelid = c.oid
WHERE c.relname IN ('index_account_domain_blocks_on_account_id_and_domain',
  'index_account_proofs_on_account_and_provider_and_username',
  'index_accounts_on_username_and_domain_lower', 'index_accounts_on_uri',
  'index_accounts_on_url', 'index_conversations_on_uri',
  'index_custom_emoji_categories_on_name',
  'index_custom_emojis_on_shortcode_and_domain',
  'index_devices_on_access_token_id', 'index_domain_allows_on_domain',
  'index_domain_blocks_on_domain', 'index_email_domain_blocks_on_domain',
  'index_invites_on_code', 'index_markers_on_user_id_and_timeline',
  'index_media_attachments_on_shortcode', 'index_preview_cards_on_url',
  'index_statuses_on_uri', 'index_tags_on_name_lower',
  'index_tombstones_on_uri', 'index_unavailable_domains_on_domain',
  'index_users_on_email', 'index_webauthn_credentials_on_external_id'
);
                           relname                          | bt_index_check 
-----------------------------------------------------------+----------------
 index_accounts_on_username_and_domain_lower               | 
 index_users_on_email                                      | 
 index_account_domain_blocks_on_account_id_and_domain      | 
 index_account_proofs_on_account_and_provider_and_username | 
 index_devices_on_access_token_id                          | 
 index_invites_on_code                                     | 
 index_conversations_on_uri                                | 
 index_custom_emojis_on_shortcode_and_domain               | 
 index_accounts_on_uri                                     | 
 index_accounts_on_url                                     | 
 index_custom_emoji_categories_on_name                     | 
 index_domain_allows_on_domain                             | 
 index_domain_blocks_on_domain                             | 
 index_unavailable_domains_on_domain                       | 
 index_markers_on_user_id_and_timeline                     | 
 index_email_domain_blocks_on_domain                       | 
 index_media_attachments_on_shortcode                      | 
 index_preview_cards_on_url                                | 
 index_tombstones_on_uri                                   | 
 index_statuses_on_uri                                     | 
 index_tags_on_name_lower                                  | 
 index_webauthn_credentials_on_external_id                 | 
(22 rows)

bt_index_parent_checkの列が空白であれば問題ないため、db:migrate時に出るメッセージを無視することができる。

影響を受けてしまっているようですが、どうすればいいですか?

修正するためのタスクが用意されているので安心してください!

RAILS_ENV=production tootctl maintenance fix-duplicates

このコマンドを実行することにより、各テーブルのインデックスを一時解除し、重複データの確認と削除を行ったのち、再度インデックスを張り直すという一連の操作を行うことができる。

手前味噌ですが以下の記事にも少し情報があるので参考にされたい。
勝手 Mastodon tootctl リファレンス - メンテナンスタスク系\重複データをマージ・削除する

この問題を回避するには?

libcがアップデートされた際、ただちにインデックスの再作成を行うこと。

Mastodonサーバの閉じ方

tootctl self-distructというコマンドができています。

参考 → 勝手 tootctl リファレンス - サーバを連合から切り離す(self-destruct)

これを実行し、sidekiqキューを流しきった後、サーバを閉じるとよいかと思います。

無条件に「410 Gone」を返すことについて

過去記事(削除済み)にて「無条件に410 Goneを返すといいのではないか」という提言をしましたが、それだけではあまり意味がありませんでした。
繋がっていたサーバ側でtootctl accounts cullを実行した際、410 Goneが返ってこればそのアカウントは削除されたものとされ、繋がっていたサーバからも消されるはずです。
結果的に「そのサーバに属しているユーザがゼロ名」になることによって、投稿等の配信が行われなくなるのでは、という推測に基づいたものでした。
閉じたサーバが410 Goneを返していたとしても、繋がっているサーバがtootctl account cullもしくはtootctl domains purgeをしない限り残り続けてしまい、
いつかサーバそのものが消え、名前が引けなくなったりタイムアウトを起こしたりした際に、またSidekiqの再試行キューに上がってきてしまいます。

Sidekiqの状態をCLIから確認したい

redis-cliコマンドを用いて、Sidekiqが用いているRedisデータベースを直接覗くことで確認ができる。

# TCP/IP接続の場合
$ redis-cli -h 127.0.0.1 -p 6379
redis 127.0.0.1:6379>


# UNIX Socket経由の場合
$ redis-cli -s /var/run/redis/redis.sock
redis /var/run/redis/redis.sock> 

実行待ちキューの数を知りたい

queues

redis > SMEMBERS queues 
1) "pull"
2) "scheduler"
3) "default"
4) "push"
5) "mailers"

redis > LLEN queues:pull
(integer) 0

redis > LLEN queues:scheduler
(integer) 0

redis > LLEN queues:default
(integer) 0

redis > LLEN queues:push
(integer) 0

redis > LLEN queues:mailers
(integer) 0

予定されたジョブ情報を見たい

schedule

redis > ZRANGEBYSCORE schedule -inf +inf
1) "{\"retry\":0,\"queue\":\"pull\",\"class\":\"LinkCrawlWorker\",\"args\":[107612902172888308],\"jid\":\"931a4d63f36331e08083fba2\",\"created_at\":1642042574.679665}"
2) "{\"retry\":0,\"queue\":\"pull\",\"class\":\"LinkCrawlWorker\",\"args\":[107612904029494959],\"jid\":\"fadcdc21e2d276a8e05e52ac\",\"created_at\":1642042602.9933386}"
3) "{\"retry\":0,\"queue\":\"pull\",\"class\":\"LinkCrawlWorker\",\"args\":[107612904933640479],\"jid\":\"cf78e974b8655b557108d101\",\"created_at\":1642042616.7966092}"
4) "{\"retry\":0,\"queue\":\"pull\",\"class\":\"LinkCrawlWorker\",\"args\":[107612830764161340],\"jid\":\"0f75778a31aeb12a12bb9276\",\"created_at\":1642042616.896532}"

再試行キューが見たい

retry

ZRANGEBYSCOREで詳細も見ることができるが出力が大量になることがあるので注意

redis > ZCARD retry
(integer) 15

redis > ZRANGEBYSCORE retry -inf +inf
 1) "{\"retry\":16,\"queue\":\"push\",\"dead\":false,\"class\":\"ActivityPub::DeliveryWorker\",\"args\":[\"{\\\"@context\\\":[\\\"https://www.w3.org/ns/activitystreams\\\",{\\\"ostatus\\\":\\\"http://ostatus.org#\\\",\\\"atomUri\\\":\\\"ostatus:atomUri\\\",\\\"inReplyToAtomUri\\\":\\\"ostatus:inReplyToAtomUri\\\",\\\"conversation\\\":\\\"ostatus:conversation\\\",\\\"sensitive\\\":\\\"as:sensitive\\\",\\\"toot\\\":\\\"http://joinmastodon.org/ns#\\\",\\\"votersCount\\\":\\\"toot:votersCount\\\"}],\\\"id\\\":\\\"h
 
 (後略)

あとがき

  • 自分用にメモした内容です。必要に応じて読み替えてください。
  • 使用は自己責任でお願いします。何か不具合が起こっても責任取れません。
  • こうしたほうがいいよ的なアドバイス、誤記や記載漏れ、動作の違い等の指摘を頂けると大変助かります。
  • 連絡先: コメント、もしくはメール、あるいは Mastodon へお願いします。

以上

Discussion

ログインするとコメントできます