Site icon image usounds

v usoundsの日常や技術的なメモを残すブログです

Starrysky Basic版のアーキテクチャ

はじめに

Blueskyのカスタムフィードを、ノーコード・サーバーレスで作成できるStarryskyのBasic版を公開して2週間ほど経ちました。私に何かがあっても良いように、アーキテクチャーなどをまとめておこうと思います。

アーキテクチャ図

Starrysky Basic版はすべてCloudlfareの仕組みを使っています。

Image in a image block

画像は全てさわらつきさんからお借りしました。この場を借りてお礼申し上げます。

Admin ConsoleはCloudflare Pagesで稼働するNext.jsベースのアプリケーションであり、検索条件を設定するための管理画面です。CloudflareのD1(ほぼSQlite)から検索条件を読み込んで、編集して、D1にその内容を戻します。

Blueskyの世界からは、フィードを表示すると都度Cloudflare WorkersのFeed Proxyが呼ばれます。実装的にはHonoを使っていますが、大先輩のContrails(同じくCloudflare Workersを使う、JavaScriptベースのフィードジェネレーターです)とほぼ同じことを行なっています。もしかしたらAdmin ConsoleのNext.jsのAPI Routingを何らか使えばFeed ProxyをNext.jsに寄せられたのかもしれませんが、そんな技術力はありませんでした。

Contrailsについて

若干話が脱線しますが、Starryskyの実装は、ほぼContrailsの焼き回しに近いです。検索条件を変更すると都度デプロイが必要であったContrailsを、Web上でできるようになった、というだけの代物でもあります。

ただし、Contrailsはすでに開発を実質終わらせているようで、最新APIの対応も行われていなかった事情もあり、私の方でFork版をメンテしておりましたが、なかなか作りが凝っていたりして私にはアグレッシブな機能追加ができませんでした。Starryskyでは後述の制限もありますが、可能な限り機能を追加していければと考えています。

緩和休題

公式のFeed-Generatorとは異なり、D1に投稿内容は保存されておりません。前述の通り、検索条件だけを保存しています。

シーケンス図っぽいものは下記となります。

Image in a image block

BlueskyからAPI要求を受け、D1から検索条件を取得し(ついでにアクセスがあったことをCloudflareのAnalytics Engineに保存する)、その検索条件を再度Blueskyに投げ返しています。searchPostはBlueskyのアプリの検索と同じ挙動をする検索用のAPIです。よって、Starryskyの検索結果はBlueskyアプリの検索結果と同じになります。

ただし、Starryskyでは、その検索結果を取得した後に「いい感じに処理」しています。Admin Consoleで設定できる項目のうち、下記は「いい感じに処理」をしている部分です

  • 投稿時・公式モデレーションサービスのラベル
  • 画像のみ・文字のみ投稿
  • リプライの表示制御
  • 画像のALT文字の検索(Blueskyアプリの検索はALT文字を検索するので、あえてそれを外すことができる)
  • 正規表現での検索
  • 正規表現での除外
  • ピン留めポスト
  • カスタムラベラーのラベルによるフィルタリング

唯一検索APIにぶん投げているのは、

  • 文字を検索するところ
  • 言語フィルタ

だけです。言語フィルタはQuery Engine版では独自実装していたのですが、searchPostがlang:jaでの言語パラメーターを受け付けるようになったので、そのまま値をパスするようにしています。

実装上苦労したところ

Workers 10ms制限

Feed ProxyはHonoで実装しており、全体でソースコード200行程度の本当に小規模なものです。逆にWorkersはCPU時間10ms以内で処理を完結する必要があり、あまり複雑な実装を入れることが出来ません。非同期処理、要するにFeed ProxyではBlueskyの検索APIを呼び出すところはこの時間に含まれません。

Query Engine版で実装したプロフィールマッチ(投稿者のプロフィールに書かれている文字列を検索する)も実装はしたものの、プロフィールを検索するために別のAPIを呼び出して・・・などなどしていたら50msをゆうに越してしまいさっくりと削除しました

Workers 容量圧縮後1MB

npmでライブラリを組み込むことが出来ますが、@atproto/apiなど読み込むとカツカツになります。かつ、ライブラリの読み込み時間もCPU時間に含まれるようなので、重たいライブラリを組み込むことは色々と苦しくなる原因になります。というか使ったら余裕で10msを越しました。だからHonoは軽量化を売りにしているんですねぇ。

そのため、Feed ProxyではBluesky関連のAPIは独自に呼び出す実装をしています。とはいえ、App Viewのエンドポイントを呼び出しており認証不要ですし、さほど複雑なAPIではないのでさほどの苦労はありませんでした。

ダイナミックインポート?よくわかりませんが対応していないのではないかと思います。

Admin Consoleの実装

Admin Consoleは実装部分だけで2000行を越してしまった、なかなか運用が辛いソースになっています。StarryskyはRenderを使用するQuery Engine版もあるのですが、この検索条件を設定するAdmin ConsoleはBasic版とQuery Engine版が共通になっています。本当のユーザビリティを考えるのであればAdmin Consoeを分けた方がいいに決まっていますが、同じようなものを2つ作ってメンテする自信がなかったので共通化しました。

実装してよかったこと

Cloudflare結構便利

便利です。無料でPages、Workers、D1、Analytics Engine、特に書いていませんがWeb Analyticsをここまで使わせてくれるとありがたいです。もしStarryskyのユーザーが増えて、Feed Proxyへのアクセスが10万回/1日を越すと課金が必要になりますが、それも月5ドルで1000万回/月に増えるのでそれなりに耐えられるのではないかと思います。

ChatGPTさん便利

Feed ProxyもAdmin ConsoleもTypescriptで実装していて、型やら厳密です。困ったらChatGPTさんにコード作って!と頼んでました。メジャーな言語ならそれなりの回答をしてくれるので助かりました。

おわりに

何とか日本語で自動収集型のカスタムフィードを作れるようにしたい、という思いだけで作りました。英語で苦戦されたり、GitHubやCloudflareわからんよ、というここまで読まれた方とは異なる方々がターゲットにはなりますが、フィード作りを楽しむ方が一人でも増えれば良いなと思います。

なお、Starryskyシリーズは全てオープンソースです。もし興味がある方はソースを覗いてみてください。