noellabo's tech blog

@noellaboの技術ブログ

Mastodonが配送に失敗したジョブに対処する仕組み

追記:Mastodon v3.1.4以降は仕様が変わっています。続きの記事もあわせてお読みください。DeliveryFailureTrackerで閉鎖したサーバに対処する / DeliveryFailureTrackerがホスト単位になりました


Mastodonのサーバで投稿やブーストが行われると、フォロワーのいるサーバに向けてActivityが配送されますが、何らかの理由で配送できないことがあります。

Mastodonでは、Sidekiq、Stoplight、DeliveryFailureTrackerによって、この配送エラーに対処しています。

Sidekiq

配送を行うActivity毎にActivityPub::DeliveryWorkerというワーカー(ジョブ)を作成し、これをSidekiqのpushキューで処理します。

ActivityPub::DeliveryWorkerでは、ジョブの実行に失敗した場合は16回まで再試行し、それでもダメならあきらめます。

再試行回数が増える度に、次の実行までの時間を長くしていくようになっています。実行間隔は(retry_count ** 4) + 15 + (rand(30) * (retry_count + 1))という計算式だそうで、15、16、31、96、271、640、1,311、2,416、4,111、6,576、10,015、14,656、20,751、28,576、38,431、50,640秒と増えていきます(これにランダムに秒を加算してジョブ毎のタイミングをずらします)。トータルでだいたい50時間ぐらい頑張ることになりますね。

Stoplight

ひとつだけならジョブ単位での再試行で良いのですが、実際には次々とジョブが作成され、同じ相手に複数の配送が行われます。

すでに何度も失敗している場合、次々に接続しようとするのは無駄です。そこで、失敗回数をカウントして一定回数を超えたら、最初から試さずに失敗するようにします。これがStoplight::Error::RedLightというエラーです。

送っても大丈夫な状態をGreen、送らないで待つ状態をRedRedを解除しても良いか判断するためのお試し状態をYellowとして、信号機によって交通整理を行うのがStoplightです。

Mastodonでは、全ての配送ジョブを通じ、同じ相手に10回失敗するとRedに移行します。Redは60秒持続し、Yellowに移行します。Yellowは一度だけジョブを実行し、成否によりRedまたはGreenに移行します。

DeliveryFailureTracker

ジョブの成否を監視し、失敗だけで一度も成功しなかった日を記録します。記録が7日になったら、もう配送を行いません。

サービス終了したサーバ、いつまでも死んだままのサーバは、この仕組みにより捕捉されます。

ただし、先方からこちらのinboxに何か配送されてきた場合、生きているらしいと判断して、記録を破棄して配送を復活させます。

Redis

上記の情報は、すべてRedisに記録されています。

特に、DeliveryFailureTrackerの記録は長期的に保持した方が良い性質のものであるため、サーバ移転などの際にうっかり消してしまわないように注意しましょう。