この記事が語ること
SNSのタイムラインは、攻撃性・ノイズ・広告で埋まりがちだ。
そこで、Blueskyで、Firehoseなし・月1000円未満で、日本語圏の“使える”ベースフィードを組んだ。
つまり、「Xから脱出しよう!!」 ということだ。
アーキテクチャ
- AWS Lambda(Docker含む)
- EventBridge
- S3
- API Gateway
- Valkey Serverless
完全AWS構成でサーバーレス。
Firehoseを使わず、Lambdaのバッチ処理でコストを抑え、月1000円未満に収めている。
なお、EventBridgeによる実行は現在のところ10分間隔だ。
扱った技術と工夫
- Lambda × Valkey × S3の小さな構成
- fastText / Janome / バッドワード辞書によるスコアリング
- Firehoseなしでも擬似的なストリーム体験を実現する「可視性タイムスタンプ」の仕組み
処理フロー
- Blueskyから日本語の条件指定でポストを取得
- fastTextで日本語ポストである確率が高いものに絞る
- Janomeで形態素解析し、L2ノルム平均でDenseスコアを算出
- ポストのメタデータ(ハッシュタグ、画像などの要素)によりスコアを調整
- バッドワード辞書に基づき指数関数的にスコアを減点
- シグモイドで正規化
- 閾値以上のポストのみSkeleton(Blueskyが扱う投稿IDのリスト)として返却
"Dense"の工夫
上記のステップ4と5では、Denseスコアに調整をかけている。
したがってこのスコアは、最終的には純粋な密度ではなく、「品質×穏やかさ」の混合指標として扱っている。
バッドワード辞書
ここは要件に合致する既存のものが見つからなかった。
そのため、手動で更新する自前の辞書を用意し、スコア計算に使っている。
可観測性
処理フローの中で、S3に統計情報も出力している。
内容は以下のようなものだ。
- フィルタ通過率
- バッドワード検出率
- Dense判定率
これらをS3上のSPAダッシュボードで可視化。
観測→調整→再観測のループを回している。
ストリーム性を再現する工夫
バッチで取得→Skeleton返却、という流れでは、ポストがチャンクとなってタイムラインに出現する。
これはUXを損なう挙動だと判断した。
そこで、"可視性タイムスタンプ"を導入した。
- 各ポストのタイムスタンプに線形な時間差を加算したフィールドを、別途valkeyにZSET(Sorted Set)で格納
- このフィールドが現在時刻を過ぎているSkeletonだけを返却
これで、Firehoseなしでも、徐々にポストが流れ込んでくる“ストリーム風”タイムラインを再現している。
UXと厳密なリアルタイム性のトレードオフだが、実用性は十分だ。
なぜ自作したのか
- 「騒がしいSNSから離れたい」「Blueskyに移りたい」と思った。
- Blueskyでは日本語圏の投稿を追えるキュレーションなどの仕組みが未だに弱く、精度も低かった。
- 日本語圏の穏やかなポストを俯瞰したかった。
- XからBlueskyへの移住を後押しする手段を提供したかった。
このプロダクトが、安住できるSNS探しの助けになれば幸いだ。
おわりに
以下が実際に公開しているフィードだ。
Japanese Dense
https://bsky.app/profile/did:plc:yqlqxclzbkxgft7atmezp7eg/feed/japanese-dense-feed
スコアを加味しない"無調整"版は以下。
Japanese Raw
https://bsky.app/profile/did:plc:yqlqxclzbkxgft7atmezp7eg/feed/japanese-raw-feed
リポジトリは以下。
https://github.com/paseri-kurosawa/bluesky-feeds-jp
この記事では設計の全体像に留め、スコアリングなどの詳細はZenn側にまとめている。
そちらも参照いただきたい。
https://zenn.dev/paseri_kurosawa/articles/8847886cee8bac
