
대규모 자가 호스팅 소셜 네트워킹을 위한 성능 최적화된 AppView, Rust 기반 firehose 인덱서, Redis 캐싱 및 커뮤니티 기능을 갖춘 AT Protocol 참조 구현의 포크
이것은 Bluesky Social PBC의 AT 프로토콜 참조 구현을 포크한 Blacksky의 포크입니다. 이 포크는 api.blacksky.community에서 AppView를 구동합니다.
투명성과 다른 커뮤니티가 이 작업의 혜택을 받을 수 있도록 공개합니다. 이 저장소는 기여, 이슈 또는 PR을 받지 않습니다. 표준 atproto 구현을 원한다면 bluesky-social/atproto를 사용하세요.
모든 변경 사항은 packages/bsky (appview 로직), services/bsky (런타임 설정) 및 하나의 사용자 정의 마이그레이션에 있습니다. 나머지는 업스트림과 동일합니다.
업스트림 데이터플레인에는 이벤트를 직접 인덱싱하는 TypeScript firehose 컨슈머(subscription.ts)가 포함되어 있습니다. 여러 가지 이유로 이를 Rust 인덱서인 rsky-wintermute로 교체했습니다.
이 저장소의 데이터플레인과 appview는 그대로 실행됩니다. wintermute가 쓰는 PostgreSQL 데이터베이스에서 읽습니다. 기본 제공 firehose 구독을 시작하지 않을 뿐입니다.
이러한 수정 사항은 대규모로 AppView를 자체 호스팅하는 모든 사람에게 광범위하게 유용합니다.
LATERAL JOIN 쿼리 최적화 (packages/bsky/src/data-plane/server/routes/feeds.ts)
getTimeline 및 getListFeed가 PostgreSQL LATERAL JOIN을 사용하도록 다시 작성되어 전체 테이블 스캔 대신 사용자별 인덱스 사용을 강제합니다. 수천 개의 계정을 팔로우하는 사용자에게 큰 개선입니다.Redis 캐싱 계층 (packages/bsky/src/data-plane/server/cache/)
Timestamp 객체가 Redis를 통한 JSON 왕복 후 .toDate() 메서드를 잃어버려 캐시 적중 시 프로필 하이드레이션이 불완전해집니다. 현재 Redis 캐싱을 비활성화한 상태로 실행 중입니다. 수정 방법은 캐시 쓰기 시 타임스탬프를 ISO 문자열로 직렬화하고 읽을 때 재구성하는 것입니다.알림 기본 설정 서버 측 적용 (packages/bsky/src/api/app/bsky/notification/listNotifications.ts)
reasons를 지정하지 않으면 서버는 사용자의 저장된 알림 기본 설정을 적용합니다. 이것이 없으면 기본 설정은 클라이언트 측에서만 적용되며 효과가 없습니다.인증 검증기 만료된 서명 키 수정 (packages/bsky/src/auth-verifier.ts)
forceRefresh) 시 데이터플레인의 인메모리 ID 캐시를 우회하고 PLC 디렉터리에서 직접 DID 문서를 확인합니다. 계정 마이그레이션 후 서명 키가 회전되지만 캐시에 이전 키가 남아 있을 때 발생하는 인증 실패를 수정합니다.JSON 정리 (packages/bsky/src/data-plane/server/routes/records.ts)
\u0000) 및 제어 문자를 제거합니다. 이는 RFC 8259에 유효하지만 Node.js JSON.parse()에서 거부되어 데이터플레인에서 rowToRecord 파싱이 조용히 실패하고 누락된 게시물로 나타납니다.개별 PDS가 아닌 AppView에서 호스팅되는 비공개 커뮤니티 게시물을 위한 인프라입니다. Blacksky의 작동 방식에 특화되어 있지만 다른 커뮤니티의 참조가 될 수 있습니다.
community.blacksky.feed.*와 제출, 가져오기, 삭제, 타임라인 및 스레드 보기 엔드포인트community_post 테이블 (마이그레이션: 20260202T120000000Z-add-community-post.ts)getPostThreadV2와의 통합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)
| 구성 요소 | 소스 | 목적 |
|---|---|---|
| rsky-wintermute | blacksky-algorithms/rsky | Rust firehose 인덱서: 이벤트 소비, 리포지토리 백필, 레코드를 PostgreSQL에 인덱싱 |
| rsky-relay | blacksky-algorithms/rsky | 레이블러 서비스의 조정 레이블을 수신하기 위한 AT 프로토콜 릴레이 |
| rsky-video | blacksky-algorithms/rsky | 동영상 업로드 서비스: Bunny Stream CDN을 통해 트랜스코딩, blob 참조를 사용자 PDS에 업로드 |
| bsky-dataplane | 이 저장소 (services/bsky) | PostgreSQL 위의 gRPC 데이터 계층 |
| bsky-appview | 이 저장소 (services/bsky) | app.bsky.* XRPC 엔드포인트용 HTTP API 서버 |
| Palomar | blacksky-algorithms/indigo | 전체 텍스트 검색: 팔로워 수 부스팅으로 프로필 및 게시물을 OpenSearch에 인덱싱 |
| palomar-sync | blacksky-algorithms/rsky | 팔로워 수 및 PageRank 점수를 PostgreSQL에서 OpenSearch로 동기화 |
Wintermute는 4개의 병렬 처리 경로를 가진 모놀리식 Rust 서비스입니다.
bsky.network firehose에 연결, 이벤트를 Fjall(임베디드 키-값 저장소) 큐에 기록ON CONFLICT로 PostgreSQL에 기록하여 멱등성 보장rsky 저장소에 포함된 추가 CLI 도구:
queue_backfill -- CSV, PDS 검색 또는 직접 DID 목록에서 백필할 DID 큐에 추가direct_index -- 큐를 우회하여 특정 리포지토리 가져오기 및 인덱싱 (개별 계정 수정에 유용)label_sync -- 커서 0에서 레이블 스트림 재생하여 누락된 부정 처리 따라잡기plc_import -- PLC 디렉터리에서 핸들/DID 매핑 대량 가져오기palomar-sync -- 팔로워 수 및 PageRank를 OpenSearch에 동기화PDS가 Bluesky의 video.bsky.app를 지원하지 않는 사용자를 위한 동영상 업로드 서비스입니다. 자체 DID(did:web:video.blacksky.community)를 사용하여 서비스 인증 JWT를 통해 사용자 PDS에 인증합니다. 흐름:
조정 레이블은 WebSocket 구독을 통해 레이블러 서비스(예: Bluesky의 Ozone)에서 제공됩니다. Wintermute의 ingester는 전용 label_live 큐(낮은 볼륨, 기본 firehose와 분리)에서 레이블을 처리합니다. label_sync 도구는 레이블러의 전체 스트림을 재생하여 레이블을 다시 삽입하지 않고도 누락된 부정(레이블 제거)을 따라잡을 수 있습니다.
bsky 스키마)bsky 스키마는 데이터플레인의 마이그레이션에 의해 생성됩니다. 처음 실행 시 데이터플레인이 모든 마이그레이션을 자동으로 적용합니다. Blacksky 전용 마이그레이션은 20260202T120000000Z-add-community-post.ts(커뮤니티 게시물 테이블)뿐입니다. 커뮤니티 게시물이 필요하지 않으면 제거할 수 있습니다.
rsky-wintermute는 동일한 스키마에 씁니다. 모든 INSERT 문은 ON CONFLICT를 사용하므로 wintermute와 데이터플레인 마이그레이션을 어떤 순서로 실행해도 안전합니다.
pnpm install
pnpm build
node services/bsky/dataplane.js
| 변수 | 필수 | 설명 |
|---|---|---|
DB_PRIMARY_URL | 예 | ?options=-csearch_path%3Dbsky가 포함된 PostgreSQL 연결 문자열 |
DB_REPLICA_URL | 아니오 | 읽기 복제본 연결 문자열 |
BSKY_DATAPLANE_PORT | 아니오 | gRPC 포트 (기본값 2585) |
BSKY_REDIS_HOST | 아니오 | Redis 호스트:포트 (캐싱 용, 현재는 비활성화 상태로 두는 것이 좋음) |
BLACKSKY_MEMBERSHIP_DB_URL | 아니오 | 커뮤니티 멤버십용 별도 DB (Blacksky 전용) |
node services/bsky/api.js
| 변수 | 필수 | 설명 |
|---|---|---|
BSKY_APPVIEW_PORT | 아니오 | HTTP 포트 (기본값 2584) |
BSKY_DATAPLANE_URLS | 예 | 쉼표로 구분된 데이터플레인 gRPC URL |
BSKY_DID | 예 | AppView의 DID (예: did:web:api.example.com) |
BSKY_MOD_SERVICE_DID | 예 | Ozone 조정 서비스 DID |
BSKY_ADMIN_PASSWORDS | 예 | 기본 인증용 쉼표로 구분된 관리자 비밀번호 |
전체 네트워크 백필(약 4,200만 사용자, 약 185억 레코드)은 wintermute의 병렬 처리에도 불구하고 몇 주가 걸립니다. 예상:
백필 중에 AppView는 기능하지만 아직 백필되지 않은 사용자에 대해 불완전한 데이터를 표시합니다. 라이브 이벤트는 백필 진행 상황에 관계없이 즉시 인덱싱됩니다.
전체 네트워크 AppView를 부트스트래핑할 때 겪었던 문제들입니다. 동일한 작업을 수행하는 경우 이러한 문제 중 일부를 겪을 가능성이 높습니다.