noellabo's tech blog

@noellaboの技術ブログ

DeliveryFailureTrackerで閉鎖したサーバに対処する

以前に書いたMastodonが配送に失敗したジョブに対処する仕組みの続きです。

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


DeliveryFailureTrackerの仕組み

DeliveryFailureTrackerは、Redisに2種類のデータを記録します。

exhausted_deliveries

ActivityPubで配送先のエンドポイントとなるinboxのURLをキーとして、配送に失敗した年月日%Y%m%dを記録するSetです。

例えばinboxがhttps://fedibird.com/inboxであれば、exhausted_deliveries:https://fedibird.com/inboxというキーになります。

redis-cliを使って確認する場合は、こうです。

SMEMBERS "exhausted_deliveries:https://fedibird.com/inbox"

1) "20200401"
2) "20200402"
3) "20200403"

unavailable_inboxes

配送を行わないinboxを記録するSetです。

ActivityPub::DeliveryWorker(Activityの配送を担うワーカー)は、ここに記録されているinboxへの配送を行わず、何もせずにWorkerを正常終了させます。

前述のexhausted_deliveriesが7日分記録されると登録されるようになっています。

挙動

  • 配送に失敗したら、年月日をexhausted_deliveries:#{inbox}に記録する
  • exhausted_deliveries:#{inbox}の記録が7日分貯まったら、unavailable_inboxesinbox_urlを記録する
  • 配送に成功したら、exhausted_deliveries:#{inbox}を空にし、unavailable_inboxesから削除する(これまでの失敗の記録をすべてクリアする)
  • Activityが配送されてきたら、その配送元のactorのinbox_urlshared_inboxのそれぞれについて、exhausted_deliveries:#{inbox}を空にし、unavailable_inboxesから削除する(これまでの失敗の記録をすべてクリアする)

配送を止めたいinbox_urlを登録する

DeliveryFailureTrackerの仕組みがわかれば、何をすれば良いかは自明ですね。unavailable_inboxesに任意のinbox_urlを記録すれば良いワケです。

ここでは、rails consoleを使って、Redisのキーを直接操作します。

任意のinbox_urlを記録する

https://mini.gorone.xyz/inboxを登録してみます。

bin/rails c

Redis.current.sadd('unavailable_inboxes', 'https://mini.gorone.xyz/inbox')

これで、mini.gorone.xyzのshared_inboxへのActivityの配送は行われなくなります。sidekiqの再試行に積み上がり、存在しないサーバへアクセスを行ってはタイムアウトを待つことを繰り返していたジョブも、即座に正常終了して消えていきます。スッキリ!

ユーザー毎のinbox_urlを記録する

投稿へのリアクション(お気に入りやブースト)、ユーザーへのアクション(フォローやブロック)を行った場合、特定の相手への配送なので、shared_inboxではなくユーザーの個別のinboxへ配送が行われます。

これは、shared_inboxとはURLが異なるため、先程の登録を行っただけではまだ配送されてしまいます! ユーザー毎に、7日分の配送失敗がないといけないワケです。(これは単なる設計ミスだと思います)

一般に、既に新しい投稿が流れてこないサーバのユーザーへは、それ以上、投稿へのリアクションやユーザーへのアクションは行われないため、shared_inboxだけが先に停止し、個々のユーザーのinboxは有効になったままになっています。

ということで、これらも登録してみましょう!

bin/rails c

Redis.current.sadd('unavailable_inboxes', Account.where(domain: 'mini.gorone.xyz').pluck(:inbox_url))

補足

shared_inboxとは?

投稿のように、一つのActivityを多数の宛先(フォロワーなど)へ配送する場合、基本的には全ての配送先のinboxへ個別に送るのですが、結果として同じサーバに同一Activityを何回も送ることになる場合があります。これはたいへん非効率です。そこで、受け取るサーバ側に代表の送り先を用意し、受け取った側で宛先のユーザーに振り分けてもらうようにすることで、同一サーバ宛の配送を1回で済ませられるような仕組みが用意されています。

この、代表の共有inboxを、shared_inboxと呼んでいます。

詳しくはこちらを規格書を参照してください。 ActivityPub - 7.1.3 Shared Inbox Delivery

Set型とは?

Redisは、キーと値の組みを記録できるデータベースですが、値として様々な型のデータを記録できます。

Set型は、文字列型の順不同の集合です。

上述のunavailable_inboxesは、Set型で値を記録しているため、inboxのURL文字列を複数登録することができます。順不同なので、順番は忘れてしまいます。同じ文字列は記録できず、記録しようとすると無視されます。文字列が含まれているか、素早くチェックすることができます。今回のような用途に最適な型となっています。