ActivityPub RelayというActivityPubリレーサーバをRails 8を使って作った

はじめに

これはRuby Advent Calendarの7日目の記事です。

qiita.com

先日松江で開催されたRubyWorld Conference 2024でもLTしたActivityPub リレーサーバの話を、より詳細な経緯などを含めて書いていこうと思います。

speakerdeck.com

ActivityPub リレーサーバとは?

ActivityPub リレーサーバは簡単にいうとFediverseのサーバ同士の連合を強化するためのサーバになります。

Fediverseでは互いのサーバにいるユーザーがサーバを超えてフォローしあうことで互いの連合タイムラインに公開投稿が流れるようになります。

サーバを越えたフォローと配送の図

そのため2017年頃のMastodonではサーバを建てたばかりだとフォローしているアカウントがないため、連合タイムラインに外部のサーバの投稿が流れないということが発生していました。

当時の記憶は少しあやふやですが、僕個人の場合はTwitterなどでサーバを建てた人のアカウントを見つけてそれをリモートフォローしてサーバ同士のつながりを増やしていった覚えがあります。

そういった中でMastodonでは v2.5.0からリレーという機能が導入されました。

github.com

このリレー機能では、以下の図のようにサーバAとサーバBがそれぞれ連合していなかった場合でもそれぞれのサーバの投稿をリレーサーバを中継して配送してくれるようになりました。

リレーサーバの配送の仕組みについての図

ActivityPub リレーサーバとはFediverseの連合を強化するためのハブの役割を果たしてくれます。

ただリレーサーバは銀の弾丸ではなくデメリットも存在します。

例えばスペックを抑えた小規模なサーバを運用していた場合、リレーサーバから配送されてくる大量の公開投稿を処理する負荷に耐えられない可能性もあります。
またリレーサーバに参加しているサーバの規模によっては大量の公開投稿がリレーサーバを経由して自分のサーバに流れてくる可能性もありますし、リレーサーバ自体にも負荷がかかります。

なんで作ろうと思ったのか

まずリレーサーバの実装として現在日本でよく知られているものはおそらく以下の二つになります。

github.com

github.com

それぞれCrystalとGoで書かれています。

そのほかにもRustでの実装やNode.jsでの実装などもあります。

git.asonix.dog

github.com

Rubyの実装に関しては現在も開発されているものはなく、最初期にプロトタイプ版として開発されたものがあり、それを運用されている方がお一人いるという状況でした。
またRubyで実装されたリレーサーバのメンテナンスが大変そう(主にRubyRailsのアップデート対応)で、以前にRubyRailsのアップデート対応のPRを投げたりはしていましたが、それ以上のことは僕もできていない状態でした。
特にプロトタイプ版として開発された背景からかテストがなく、実際にサーバを建てて動作確認をしなければならないのも大変でした。

そんな状況だったので「このメンテナンスしにくいのどうにかできないかなぁ」と思い、「現存するRubyの実装を参考にリレーサーバの実装をテストも書きつつやりたいな」と考え始めました。
それがちょうど去年の年末から今年の年始にかけてくらいで、結構熱中してやっていたんですが他のところでリソースが必要になり徐々に熱が引いていきました。

そんなこんなしていた時に今年のRubyWorld ConferenceでMastodonの開発チームのAndy Piperさんが来日されるというのを聴き、「せっかくMastodonの人が来るんだからActivityPubやFediverseの話をしたい!」と思い、やる気が再燃し、リレーサーバの開発を再開しました。

2024.rubyworld-conf.org

またリレーサーバ開発を再開したぐらいにRails 8が登場しました。
これがリレーサーバの開発をより後押ししたところもあります。

rubyonrails.org

というのも

  • 証明書とかNginx使うかとかどうする?とか
  • Redisどうする?データベースどうする?とか
  • デプロイもSSHでサーバ内に入って手動やるか、もしくはDockerfileなどをいい感じにAWSとかでつかうかとか

という感じで、そもそもリレーサーバを建てる&メンテナンスするのが大変だったんですね。
個人的にはより良い感じにリレーサーバも分散が進む方が負荷も分散できると思っていたので、リレーサーバの建てにくさはどうにかできないかなと考えてました。

それがRails 8を使うことで

  • Thrusterでの証明書の自動更新&Nginx建てなくて良い
  • Solid Queueを使うのでRedisが不要になり、本番環境でSQLiteが使えるので良い
  • デプロイはKamal2がよしなにやってくれるので非常に良い

という感じになりそうだったんですよね。

そういった後押しもあり「いっちょやるか」と開発を再開したのが11月の中旬くらいでした。

作ったもの

そんなこんなで作ったものは以下になります。

github.com

Rails 8で入ったKamal2、Thruster、Solid Queueをそのまま使い、ささっと実装しました。

また本番環境のデータベースはSQLiteを採用しました。
リレーサーバの性質上、配送する公開投稿はデータベースに保持する必要はなく、必要なのは参加しているサーバの情報などだったので「SQLiteでも充分そうだ」という判断もあり、採用することにしました。

現状の機能としては

  • Mastodonのリレーをサポート
  • Misskey(とそのフォークなど)のリレーをサポート
  • PleromaやAkkomaなどのActorリレーも部分的にサポート(ただ問題があってドキュメントには書いていない)
  • 参加サーバの管理画面
  • ジョブの管理画面
  • Google Authenticatorなどで使えるOTPをサポート

という感じになっています。

試験的にリレーサーバとして運用した際にはメモリ1GB CPU1コアのスペックが抑え目なサーバでもサクサクと動いていたので中々いい感じでした。 のえるさんのご協力もあり、負荷試験としてFedibirdからの配送も処理できるか確認してみました。
ジョブの管理画面から見ていた感じだと、Fedibirdが参加する前が一時間で1000ジョブとかくらいだったんですが、参加後は大体毎分1000 ~ 2000ジョブ位を処理していて負荷がいい塩梅に増してました。
稀に配送先のサーバへのリクエストが長引き遅延することはありましたが、それでも基本的には即時でジョブを捌き切っていましたし、なおかつサーバ自体も負荷でダウンするということもなかったので良い感じでした。

現状の運用

現在はKAGOYAさんのメモリ1GB、CPU1コア 、ストレージ25GBでリレーサーバを運用しています。
なので月550円くらいでリレーサーバを建てて運用できるので大分お財布にも優しくて助かってますね。

relay.gamelinks007.net

参加サーバも小規模なサーバかお一人様サーバの人に絞っているので負荷の面も心配はなさそうという感じですね 。

今後

一日あたりの配送している公開投稿の数をグラフで表示したりとかもやっていきたいですね。
多分それがあるほうが「このリレーサーバに参加するとどれくらいの負荷が増すんだろう?」というのが確認しやすいと思いますし。

また小規模なリレーサーバを建てやすくなった&かなり安く済みそうというのもあり、リレーサーバを建ててみたい人向けにフォローとかしていきたいですね。
あと個人的には小規模なテーマリレーサーバとして運用する(古の同人関係のサーチエンジン的な感じで)とかもできそうなのでそのあたりもやってみると面白いのではと考えてます。

参考記事など

blog.noellabo.jp

blog.noellabo.jp

blog.yukimochi.jp

joinfediverse.wiki

2024年10月の振り返り

日々の生活

松江で松江Ruby会議11が開催されるのでそれに参加していた。

matsue.rubyist.net

 

プロポーザルは出してたんだけど、通らなかったので一般参加で参加していた。

翌日に予定があったため懇親会に参加できず、濃ゆい話が出来なかったのが残念。

 

ゲーム

Halo

store.steampowered.com

 

暇を見てはHalo Infiniteのファイアファイトをやってウィークリーミッションをこなしてた。

 

 

OSS

Rubyのパーサー周りとRBSとかにPR投げてた

 

github.com

 

github.com

 

github.com

 

github.com

 

github.com

 

2024年9月の振り返り

日々の生活

福岡Rubyist会議04 に登壇してきた。

 

regional.rubykaigi.org

 

その時の感想とかはこちらにまとめてある

 

 

gamelinks007.hatenablog.com

 

ゲーム

Halo

store.steampowered.com

 

ひたすたHalo Infiniteではファイアファイトをやって、Halo MCCではLegendaryキャンペーン周回をやってた

 

StarWars Outlaws

store.steampowered.com

 

8月末あたりからプレイしてて9月頭くらいにクリアした。

ネタバレになるので詳細は書けないけど最後の辺りはファンにとって大分嬉しい演出があるので是非確認して欲しい。

 

OSS

福岡Rubyist会議04で話すgemの実装とかを頑張ってて、あまりOSS業はできなかった感じがある。

終わった後もだいぶ放心状態だったので、あまり手が動かせなかった感。

 

とはいえMastodonをフォークされてるところのCI修正はやったりしてた。

 

github.com

2024年8月の振り返り

日々の生活

 

ゲーム

Halo

store.steampowered.com

 

相変わらずHalo Inifiniteでファイアファイトやってた。

あとはたまにHalo MCCのほうでキャンペーン周回やったりとかしてたかな

 

StarWars Outlaws

store.steampowered.com

 

スターウォーズの世界観でアウトローやるよってゲームをガンガンやってた。

基本的には敵地でステルスアクションしつつ、依頼の品を回収するとかなので人によってはプレイに時間がかかるのが嫌になるかもなぁと思った。

 

個人的には凄く面白かったのでオススメだけどね

OSS

HaloとStarWars Outlawsが楽しくて何も出来なかった......

2024年7月の振り返り

日々の生活

ヘイシャの集まりがあったのでそれに参加したりしてた

 

ゲーム

Halo

store.steampowered.com

 

相変わらずHalo Inifiniteのファイヤファイトやりつつ、ウィークリーミッションとかこなしてた。

 

OSS

7月は仕事が忙しかったりで特に目立ったOSS活動で出来ていなかったなぁ......

Creatodonで使っているRedisをValkeyへと移行した

Creatodonで利用しているRedisをValkeyへ移行した時のメモです。

利用環境としては

  • OS: Ubuntu 22.04.4 LTS
  • Redis: 7.4.0
  • Valkey : 8.0.0(Redis 7.2.5互換)

になります

やったこと

CIの修正

まずはCIの環境でもValkeyが利用できるように修正しました

修正箇所としては以下のPRにまとまっています。

github.com

基本的には以下のようにRedisのサービス部分をValkeyに変更しています。

      redis:
-        image: redis:7-alpine
-        options: >-
-          --health-cmd "redis-cli ping"
-          --health-interval 10s
-          --health-timeout 5s
-          --health-retries 5
+        image: valkey/valkey:8-alpine
        ports:
          - 6379:6379

Valkeyのインストール

README.mdを参考に、Valkeyをサーバーにインストールします。

github.com

まずはソースコードをダウンロードします。

wget https://github.com/valkey-io/valkey/archive/refs/tags/8.0.0.tar.gz

ダウンロードしたソースコードを解凍し、ビルドするディレクトリまで移動します。

tar -zxvf 8.0.0.tar.gz
cd valkey-8.0.0/src

srcディレクトリ内でmakeを実行するとビルドが始まります。 今回はjemallocとsystemdを使いたかったので、そちらのオプションも有効にしています。

make MALLOC=jemalloc USE_SYSTEMD=yes

サーバーにはsystemd関連のヘッダーがなかったのでそちらもインストールしてからmakeを実行しました。

sudo apt install libsystemd-dev

あとは以下のコマンドでインストールします。

sudo make install

またインストール時に、redis-cliなどのシンボリックリンクも作成されます。

インストール完了後、以下のコマンドを実行してValkeyのバージョンが表示さればOKです。

valkey-cli --version
# => valkey-cli 8.0.0

Redisの停止とValkeyの起動

Redisを停止します。

sudo systemctl stop redis-server

次に、Valkeyのserviceを参考に /lib/systemd/system/valkey-server.service を作成します。

[Unit]
Description=Valkey data structure server
Documentation=https://github.com/valkey-io/valkey-doc
#Before=your_application.service another_example_application.service
#AssertPathExists=/var/lib/valkey
Wants=network-online.target
After=network-online.target

[Service]
ExecStart=/usr/local/bin/valkey-server --supervised systemd --daemonize no
## Alternatively, have valkey-server load a configuration file:
#ExecStart=/usr/local/bin/valkey-server /path/to/your/valkey.conf
LimitNOFILE=10032
NoNewPrivileges=yes
#OOMScoreAdjust=-900
#PrivateTmp=yes
Type=notify
TimeoutStartSec=infinity
TimeoutStopSec=infinity
UMask=0077
#User=valkey
#Group=valkey
#WorkingDirectory=/var/lib/valkey

[Install]
WantedBy=multi-user.target

参考にしたのはこちらになります。

github.com

あとは 以下のコマンドでValkeyを起動します。

sudo systemctl start valkey-server

タイムラインの再構築

次に、Mastodon のタイムラインの再構築を行います。

# タイムラインをクリア
RAILS_ENV=production bundle exec ./bin/tootctl feeds clear

# タイムラインを再構築
RAILS_ENV=production bundle exec ./bin/tootctl feeds build --all --concurrency 15

Mastodonの再起動

最後にMastodonを再起動します。

sudo systemctl restart mastodon-*

現状

Valkeyに移行してからの変化としては - Redisの時よりメモリの使用量が減った - Redisの時は30mbほど - Valkeyは5mbほど - おおよそ六分の一ほどになった - ジョブの処理がスムーズな印象 - 実際にパフォーマンスを計測したわけではない - 体感での話なのでベンチマークはどこかでとりたい

という感じです。

福岡Rubyist会議04に登壇してきた

福岡Rubyist会議04に登壇してきた

Day 0

島根から山口を経由して、福岡まで移動。 福岡到着後、お昼を食べてホテルにチェックイン。

 

ホテルに荷物を置いた後、甚六さんと福岡空港でPrismの作者のkevinさんを出迎えました。 ただ僕の移動が少し間に合わなくて遅れてしまったのは申し訳なかったな......。

 

その後、甚六さんとkevinさんと僕の三人でご飯を食べに行き色々話をさせてもらいました。

主にPrismの実装の経緯とかShopifyのRuby開発チームの話とかをDeepLを使いつつ話してました。

その後、甚六さんとkevinさんと分かれて二次会、三次会に行き色々飲み食いしつつ明日への英気を養ってました。

Day 1

当日は「Try Make to Ruby's Parser Available as a Gem」というタイトルで登壇してきました

 

speakerdeck.com

 

内容としてはRubyのパーサー部分(parse.yやその周辺のソースコード)を利用して、Rubyソースコードを解析するgemを作る試みになります。

現状のRubyのパーサー部分はUniversal Parserとして使うことができるようにいくつかの変更が入っています。

 

bugs.ruby-lang.org

 

で、

  • Universal Parserを使ってみたかった
  • Universal Parserとして実際に使ったケースとしても面白そうなのでgemを作った
  • 実装時のアレコレや今後の課題など

を話しました。

 

で、懇親会ではパーサー周りの話したりとか、普段いかにHaloをプレイするための時間を捻出しているかなどを話したりしてました。

Day 2

翌日は島根まで帰る予定だったのでササッとお土産を買って、博多駅のラーメン街みたいなところで長浜ラーメン食べて帰りました。

 

個人的には三陽食堂のアジフライが食べたかったんですが、帰りの便に間に合わなくなりそうだったので断念。

sanyo-jp.co.jp

 

また福岡に来るときには絶対によりたい

 

感想など

今回は実質的にパーサー会議というようなパーサー関係の話が多かったのでかなり楽しかった。

 

特にyui-knkさんの話は、Rubyのパーサー周りの理解をより深められたので嬉しかったですねー。

 

speakerdeck.com

 

parse.yとかその周辺読んでても「なんでこれこうなってるんだろう?」みたいなのはありまして......

その辺の実装への理解がより深められたので個人的には大満足でした。