
2026/03/09 6:40
**ブラックスカイ・AppView**
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Blacksky の AppView は Bluesky Social PBC の AT Protocol 参考実装をフォークしたもので、外部からの貢献やプルリクエストは受け付けません。すべての変更は
packages/bsky、services/bsky の3つのディレクトリと1つのマイグレーションファイルに限定され、参考コードの大部分を保持しています。
リポジトリは組み込みの TypeScript フィーホーズコンシューマーを Rust ベースのインデクサ rsky‑wintermute に置き換えており、並列キューを通じて約10 k+ レコード/秒を取り込むことができます。Wintermute はブートストラップツール(
queue_backfill、direct_index、label_sync など)を提供し、ライブインデクシングとバックフィルを分離します。
主なパフォーマンス最適化は次の通りです:
- PostgreSQL の LATERAL JOIN 再書き込み(
/getTimeline
用)getListFeed - Redis キャッシュレイヤー(アクタープロファイル TTL 60 s、レコード TTL 5 m、相互作用カウント TTL 30 s、投稿メタデータ TTL 5 m)
- 通知設定のサーバー側強制
実装された修正:
- JWT 検証における古い署名鍵の処理
- JSON のサニタイゼーションで null バイト/制御文字を除去
- アクターメモリキャッシュ内の protobuf タイムスタンプバグへの対策
Blacksky は コミュニティ投稿サポート をカスタムレキシコン namespace(
community.blacksky.feed.*)と専用 community_post テーブル、データプレーン/API 層でのメンバーシップゲーティングを通じて追加しています。これは混在した投稿スレッド(getPostThreadV2)とも統合されます。
全体アーキテクチャフロー: Bluesky Relay → rsky‑wintermute(フィーホーズコンシューマ/バックファラー/ラベルインデクサ) → PostgreSQL 17 → bsky‑dataplane(gRPC) → オプションの Redis キャッシュ → bsky‑appview(HTTP) → リバースプロキシ、Palomar が OpenSearch 検索機能を提供
バックフィル性能: ライブインデクシングは約1 k イベント/秒。42 M ユーザーと 18.5 B レコードのフルバックフィルは10 k レコード/秒で 2–4 週間、部分的なバックフィルは数時間〜数日で完了
ブートストラップ課題への対策:
- PostgreSQL COPY による JSON 腐敗
- null バイト処理
- タイムスタンプ精度の強制
- 通知テーブルの肥大化緩和
- 投稿埋め込みテーブルの人口化
- ラベル否定順序
- Fjall キュー毒性解決
- TLS プロバイダ初期化
- アカウント移行後の署名鍵回転
フルネットワーク AppView のリソース要件: ≥ 16 CPU コア(推奨 48+)、≥ 64 GB RAM(256 GB 推奨)、10 TB NVMe ストレージ(28 TB RAID 推奨)、同一マシンまたは低遅延での PostgreSQL、継続的ネットワーク 100 Mbps(1 Gbps+)以上の取り込み帯域
リポジトリは MIT/Apache 2.0 のデュアルライセンスです。アップストリーム同期手順は
git remote add upstream https://github.com/bluesky-social/atproto.git で提供されています。本文
Blacksky の AT Protocol 参照実装のフォーク
このリポジトリは
api.blacksky.community にある AppView を動かすために使用されています。透明性を確保する目的で公開していますが、貢献・Issue・PR は受け付けていません。
正規の AT‑Protocol 実装については
bluesky-social/atproto を参照してください。
何が違うのでしょうか?
変更点は以下のディレクトリに集中しています。
- packages/bsky – AppView のロジック
- services/bsky – ランタイム設定
- 1 つのカスタムマイグレーション
(
)20260202T120000000Z-add-community-post.ts
それ以外は upstream と同じです。
なぜ組み込み Firehose コンシューマを使わないのでしょうか?
オリジナルのデータプレーンには、イベントを順次インデックスする TypeScript の Firehose コンシューマがあります。
ネットワーク規模(約 1 k イベント/秒、総記録数 18.5 B)では、90 レコード/秒でバックフィルすると 6½ 年 かかります。
そこで rsky‑wintermute(Rust のインデクサ)に置き換えました。主な理由は次の通りです。
| Reason | Detail |
|---|---|
| スケール時のパフォーマンス | TypeScript コンシューマはイベントを順次処理しますが、Wintermute は並列キュー処理で 10 k レコード/秒以上を目指します。 |
| バックフィル構成 | ライブインデックスとバックフィルは独立したキュー (, , , ) を走らせます。ライブイベントはバックフィルの作業でブロックされません。 |
| 運用ツール | Wintermute は次のようなユーティリティを備えています。 特定アカウントのインデックス、PLC ディレクトリの一括インポート、ラベルストリーム再生、Blob 参照修復、キュー管理 – AppView をゼロから起動する際に必要です。 |
データプレーンと AppView はそのまま動作し、Wintermute が書き込む PostgreSQL データベースを読み取ります。
組み込みの Firehose サブスクリプションは起動しません。
パフォーマンス & 運用上の改善点
以下は自己ホスト型 AppView を大規模に運用する際に役立つ機能です。
| Feature | Location |
|---|---|
| LATERAL JOIN クエリ最適化(フィードルート) | – ユーザーごとのインデックス使用を強制し、フルテーブルスキャンを回避。 |
| Redis キャッシュ層 | – アクタープロフィール(60 s TTL)、レコード(5 m)、相互作用数(30 s)、投稿メタデータ(5 m)をキャッシュ。プロダクショントラフィック下で DB 負荷を削減。既知の問題: アクターキャッシュに protobuf タイムスタンプバグがあるため、現在は Redis キャッシュを無効化して運用しています。修正案としては書き込み時に ISO 文字列でシリアライズし、読み取り時に再構築する方法です。 |
| 通知設定のサーバー側強制 | – クライアントが理由を省略した場合でも、ユーザー保存済みの通知設定を適用。 |
| Auth verifier の古い署名鍵修正 | – JWT 検証再試行 () 時にデータプレーンのメモリ内アイデンティティキャッシュをバイパスし、PLC ディレクトリから DID ドキュメントを直接解決。アカウント移行後の認証失敗を修正。 |
| JSON サニタイズ | – JSON パース前に null バイト () と制御文字を除去。RFC 8259 では許容されるが Node.js の が拒否するため、パース失敗で投稿が欠落してしまう問題を解決。 |
Blacksky 固有のコミュニティ投稿
AppView 上に存在し、個別 PDS には存在しないプライベートコミュニティ投稿用インフラです。
というカスタムレキシコン namespace を定義。community.blacksky.feed.*
提出・取得・削除・タイムライン・スレッド表示のエンドポイントを提供。- 独立した
テーブル(community_post
マイグレーション)を使用。20260202T120000000Z-add-community-post.ts - データプレーンと API 層でメンバーシップゲーティングを実装。
と統合し、標準投稿とコミュニティ投稿の混在スレッドをサポート。getPostThreadV2- 別途 membership DB(
)が必要。BLACKSKY_MEMBERSHIP_DB_URL
アーキテクチャ
Bluesky Relay (bsky.network) | v rsky-wintermute ─────► PostgreSQL 17 ◄──── Palomar (Rust indexer) | (Go search) - firehose consumer | | - backfiller | v - label indexer | OpenSearch - direct indexer | v bsky-dataplane (gRPC :2585) ◄── Redis (optional) | v bsky-appview (HTTP :2584) | v Reverse proxy (Caddy/nginx)
コンポーネント概要
| Component | ソース | 目的 |
|---|---|---|
| rsky‑wintermute | | Rust の Firehose インデクサ。イベントを消費し、バックフィル、レコードを PostgreSQL に書き込み。 |
| rsky‑relay | | ラベラーサービスからのモデレーションラベル受信用 AT Protocol Relay。 |
| rsky‑video | | 動画アップロードサービス。Bunny Stream CDN でトランスコードし、Blob をユーザー PDS にアップロード。 |
| bsky-dataplane | 本リポジトリ () | PostgreSQL 上の gRPC データレイヤー。 |
| bsky-appview | 本リポジトリ () | HTTP API サーバー(xrpc エンドポイント)。 |
| Palomar | | OpenSearch へのフルテキスト検索。プロファイルと投稿をインデックスし、フォロワーカウントでブースト。 |
| palomar-sync | | PostgreSQL のフォロワー数・PageRank を OpenSearch に同期。 |
rsky‑wintermute 詳細
Wintermute は 4 つの並列処理パスを持つモノリシックな Rust サービスです。
- Ingester – bsky.network Firehose の WebSocket 接続からイベントを取得し、Fjall(埋め込みキーバリューストア)のキューへ書き込み。
- Indexer – キューから読み取り、レコードをパースして PostgreSQL に書き込む (
で冪等性)。ON CONFLICT - Backfiller – PDS から全リポジトリ CAR ファイルを取得し、バックフィルキューへ展開。
- Label indexer – ラベラーの WebSocket ストリームに購読し、ラベル作成/否定イベントを処理。
CLI ツール一覧:
| Tool | 目的 |
|---|---|
| CSV、PDS 発見、または DID リストからバックフィル対象の DIDs をキューへ投入。 |
| キューを経由せずに特定リポジトリを直接取得・インデックス(個別アカウント修復時に便利)。 |
| ラベルストリームを 0 から再生し、欠落した否定イベントを追跡。 |
| PLC ディレクトリからハンドル/DID マッピングを一括インポート。 |
| フォロワー数と PageRank を OpenSearch に同期。 |
rsky‑video
bluesky.video.bsky.app をサポートしない PDS へ動画をアップロードするためのサービスです。自前 DID (did:web:video.blacksky.community) でサービス認証 JWT を使って認証します。
フロー:
- クライアントは PDS(audience: 動画サービス DID)からサービス認証トークン取得。
- クライアントが rsky‑video に動画バイト列をアップロード。
- rsky‑video が CID を生成し、Blob をユーザー PDS にアップロード。
- Bunny Stream CDN へ転送してトランスコード。
- 完了後、クライアントは Blob を参照する投稿を作成(PDS が存在確認)。 |
ラベル処理
モデレーションラベルはラベラーサービス(例:Bluesky の Ozone)から WebSocket で受信。Wintermute の label_live キューに専用処理を行います。
label_sync ツールで全ストリームを再生し、欠落した否定イベント(ラベル削除)を追跡します。
セットアップ
必要条件
- Node.js 18+ と pnpm(データプレーンと AppView のビルド)
- PostgreSQL 17 (
スキーマ)bsky - Redis(任意、キャッシュ用 – 現在は無効化推奨)
- rsky‑wintermute が Firehose を消費し DB に書き込む
- OpenSearch(Palomar 検索を利用する場合)
データベース
bsky スキーマはデータプレーンのマイグレーションで作成されます。最初に実行すると自動的に全マイグレーションが適用されます。Blacksky 固有のマイグレーションは20260202T120000000Z-add-community-post.ts です。コミュニティ投稿が不要なら削除してください。
rsky‑wintermute は同じスキーマへ書き込み、全 INSERT が
ON CONFLICT を使用するため、マイグレーションと Wintermute の起動順序は問われません。
ビルド & 実行
データプレーン
node services/bsky/dataplane.js
| Variable | 必須 | 説明 |
|---|---|---|
| はい | PostgreSQL 接続文字列 () |
| いいえ | リードレプリカ接続文字列 |
| いいえ | gRPC ポート(デフォルト 2585) |
| いいえ | Redis ホスト:ポート(キャッシュ用、無効化推奨) |
| いいえ | コミュニティメンバーシップ専用 DB(Blacksky 固有) |
AppView
node services/bsky/api.js
| Variable | 必須 | 説明 |
|---|---|---|
| いいえ | HTTP ポート(デフォルト 2584) |
| はい | コンマ区切りの gRPC URL |
| はい | AppView の DID(例:) |
| はい | Ozone モデレーションサービス DID |
| はい | 基本認証用のパスワードをコンマ区切りで指定 |
大規模運用
バックフィルタイムライン
全ネットワークバックフィル(約 42 M ユーザー、18.5 B レコード)は、Wintermute の並列処理でも数週間かかります。
| フェーズ | 内容 |
|---|---|
| ライブインデックス | デイ・ワンからリアルタイムで約 1 k イベント/秒を追跡。 |
| 全バックフィル | 10 k レコード/秒で 2–4 週間(PDS 応答性・ネットワークに依存)。 |
| 部分バックフィル | コミュニティメンバーのみなど、数時間〜数日。 |
バックフィル中でも AppView は機能しますが、未バックフィルユーザーのデータは不完全です。ライブイベントはバックフィル進捗に関係なく即時インデックスされます。
解決した課題
| Issue | Fix |
|---|---|
COPY テキスト形式 JSON 破損 – PostgreSQL が を と解釈。約 66 k レコードを公的 API から再取得して修復。 | |
| JSON 中の null バイト – Node.js が拒否。書き込み前に除去。 | |
タイムスタンプ形式の感度 – PostgreSQL はミリ秒精度 + を期待。ナノ秒やオフセットはソートに影響。 | |
通知テーブル膨張 – に一意制約が無いと 10 億行以上(663 GB)へ拡大。インデックス作成後 を追加。 | |
投稿埋め込みテーブル欠落 – インデクサが処理しない場合、 のメディアフィルタで何も返さず。別途バックフィル必要。 | |
ラベル否定順序 – バックフィル中に否定が先に来るケースを で再生して追跡。 | |
| Fjall キューの汚染 – クラッシュ後に DB が「汚染」状態になることがある。キューディレクトリ削除して再起動すると、リレーのカーソル(約 72 h の履歴)から追跡開始。 | |
TLS プロバイダー初期化 – Rust は明示的に crypto provider をインストール () 必要。未設定だと最初の WebSocket 接続でパニック。 | |
| 署名鍵ローテーション後の認証失敗 – データプレーンは 1 h の staleTTL で identity をキャッシュ。移行期間中に JWT 検証が失敗。検証再試行時にキャッシュをバイパスし PLC ディレクトリから直接解決。 |
リソース要件(全ネットワーク AppView)
| Resource | 最小 | 推奨 |
|---|---|---|
| CPU | 16 コア | 48+ コア |
| RAM | 64 GB | 256 GB |
| ストレージ | 10 TB NVMe | 28+ TB NVMe(RAID) |
| PostgreSQL | 専用、同一マシンか低レイテンシ | 同一推奨 |
| ネットワーク | 持続 100 Mbps | 1 Gbps+ |
ストレージ内訳(概算)
| テーブル群 | サイズ |
|---|---|
| 投稿 + レコード | ~3.5 TB |
| ライク | ~2 TB |
| フォロー | ~500 GB |
| 通知 | ~600 GB |
| インデックス | ~4 TB |
| OpenSearch (Palomar) | ~500 GB |
小規模コミュニティで部分 AppView を運用する場合は、インデックス数に比例してリソースがスケールします。
upstream との同期
git remote add upstream https://github.com/bluesky-social/atproto.git git fetch upstream git merge upstream/main
衝突は主に
packages/bsky/src/data-plane/server/routes/ と packages/bsky/src/api/ 内で発生します。自社の追加分と upstream の変更を併せて保持してください。
ライセンス
upstream と同様、MIT と Apache 2.0 がデュアルライセンスです。詳細は
LICENSE-MIT.txt と LICENSE-APACHE.txt をご覧ください。