
AT प्रोटोकॉल संदर्भ कार्यान्वयन का फोर्क प्रदर्शन-अनुकूलित AppView, Rust-आधारित फायरहोज़ इंडेक्सर, Redis कैशिंग, और बड़े पैमाने पर स्व-होस्टेड सामाजिक नेटवर्किंग के लिए सामुदायिक सुविधाओं के साथ।
यह Blacksky द्वारा AT Protocol reference implementation (जो Bluesky Social PBC द्वारा है) का फोर्क है। यह api.blacksky.community पर AppView को संचालित करता है।
हम इसे पारदर्शिता के लिए प्रकाशित कर रहे हैं ताकि अन्य समुदाय इस काम से लाभान्वित हो सकें। यह रिपॉजिटरी योगदान, मुद्दे या PR स्वीकार नहीं कर रही है। यदि आप विहित atproto कार्यान्वयन चाहते हैं, तो bluesky-social/atproto का उपयोग करें।
सभी बदलाव packages/bsky (appview लॉजिक), services/bsky (रनटाइम कॉन्फ़िगरेशन), और एक कस्टम माइग्रेशन में हैं। बाकी सब ऊपरी स्रोत (upstream) से है।
ऊपरी स्रोत के डेटाप्लेन में एक TypeScript फायरहोज़ कंज़्यूमर (subscription.ts) शामिल है जो सीधे इवेंट को इंडेक्स करता है। हमने इसे rsky-wintermute से बदल दिया, जो एक Rust इंडेक्सर है, कई कारणों से:
इस रिपॉजिटरी के डेटाप्लेन और ऐपव्यू अभी भी ज्यों के त्यों चलते हैं। वे PostgreSQL डेटाबेस से पढ़ते हैं, जिसमें wintermute लिखता है। हम सिर्फ बिल्ट-इन फायरहोज़ सब्सक्रिप्शन शुरू नहीं करते।
ये किसी भी व्यक्ति के लिए व्यापक रूप से उपयोगी हैं जो पैमाने पर AppView को स्वयं-होस्ट कर रहा है।
LATERAL JOIN क्वेरी ऑप्टिमाइज़ेशन (packages/bsky/src/data-plane/server/routes/feeds.ts)
getTimeline और getListFeed को PostgreSQL LATERAL JOINs के साथ फिर से लिखा गया ताकि पूर्ण तालिका स्कैन के बजाय प्रति-उपयोगकर्ता इंडेक्स उपयोग को बल दिया जा सके। हजारों खातों का अनुसरण करने वाले उपयोगकर्ताओं के लिए बड़ा सुधार।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) पर, डेटाप्लेन के इन-मेमोरी आइडेंटिटी कैश को दरकिनार करता है और DID दस्तावेज़ को सीधे PLC निर्देशिका से हल करता है। खाता माइग्रेशन के बाद प्रमाणीकरण विफलताओं को ठीक करता है जहां हस्ताक्षर कुंजी घूमती है लेकिन कैश पुरानी कुंजी रखता है।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 फायरहोज़ इंडेक्सर: इवेंट का उपभोग करता है, रिपॉजिटरी को बैकफिल करता है, PostgreSQL में रिकॉर्ड इंडेक्स करता है |
| rsky-relay | blacksky-algorithms/rsky | लेबलर सेवाओं से मॉडरेशन लेबल प्राप्त करने के लिए AT प्रोटोकॉल रिले |
| rsky-video | blacksky-algorithms/rsky | वीडियो अपलोड सेवा: Bunny Stream CDN के माध्यम से ट्रांसकोड करता है, ब्लॉब रेफरेंस को उपयोगकर्ता 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 | PostgreSQL से OpenSearch में अनुयायी गणना और PageRank स्कोर सिंक करता है |
Wintermute चार समानांतर प्रसंस्करण पथों वाली एक मोनोलिथिक Rust सेवा है:
bsky.network फायरहोज़ से जुड़ता है, इवेंट को 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 का समर्थन नहीं करता है। सेवा प्रमाणीकरण JWT के माध्यम से उपयोगकर्ता PDS को प्रमाणित करने के लिए अपने स्वयं के DID (did:web:video.blacksky.community) का उपयोग करता है। प्रवाह:
मॉडरेशन लेबल WebSocket सब्सक्रिप्शन के माध्यम से लेबलर सेवाओं (जैसे, Bluesky का Ozone) से आते हैं। Wintermute का इंजेस्टर एक समर्पित label_live कतार (कम वॉल्यूम, मुख्य फायरहोज़ से अलग) में लेबल प्रोसेस करता है। 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 | हाँ | बेसिक प्रमाणीकरण के लिए अल्पविराम से अलग व्यवस्थापक पासवर्ड |
पूर्ण-नेटवर्क बैकफिल (सभी ~42M उपयोगकर्ता, ~18.5B रिकॉर्ड) में wintermute के समानांतर प्रसंस्करण के बावजूद सप्ताह लगते हैं। अपेक्षा करें:
बैकफिल के दौरान, AppView कार्यात्मक है लेकिन उन उपयोगकर्ताओं के लिए अधूरा डेटा दिखाएगा जिनका अभी तक बैकफिल नहीं हुआ है। बैकफिल प्रगति की परवाह किए बिना लाइव इवेंट तुरंत इंडेक्स किए जाते हैं।
ये ऐसी समस्याएँ हैं जिनका सामना हमने पूर्ण-नेटवर्क AppView बूटस्ट्रैप करते समय किया। यदि आप भी ऐसा ही कर रहे हैं, तो आप इनमें से कुछ से टकरा सकते हैं:
COPY टेक्स्ट फॉर्मेट JSON भ्रष्टाचार: PostgreSQL का COPY टेक्स्ट प्रोटोकॉल बैकस्लैश को एक एस्केप कैरेक्टर मानता है। यदि आपका बल्क लोडर JSON स्ट्रिंग्स में बैकस्लैश को एस्केप नहीं करता है, तो \" " बन जाता है और आपको मौन रूप से भ्रष्ट रिकॉर्ड मिलते हैं। record.json कॉलम प्रकार text ( jsonb नहीं) है, इसलिए PostgreSQL इसे पकड़ नहीं पाएगा। हमें लगभग 66,000 भ्रष्ट रिकॉर्ड मिले और उन्हें सार्वजनिक API से पुनः लाकर मरम्मत करनी पड़ी।
JSON में नल बाइट्स: कुछ AT प्रोटोकॉल रिकॉर्ड में \u0000 (नल बाइट) होता है, जो RFC 8259 के अनुसार मान्य JSON है लेकिन Node.js के JSON.parse() द्वारा अस्वीकार कर दिया जाता है। डेटाप्लेन इन रिकॉर्ड के लिए मौन रूप से null लौटाता है। डेटाबेस में लिखने से पहले नल बाइट्स हटा दें।
टाइमस्टैम्प प्रारूप संवेदनशीलता: डेटाप्लेन मिलीसेकंड सटीकता और Z प्रत्यय (2026-01-12T19:45:23.307Z) वाले टाइमस्टैम्प की अपेक्षा करता है। नैनोसेकंड सटीकता या समयक्षेत्र ऑफसेट प्रारूप (+00:00) सूक्ष्म सॉर्टिंग और तुलना समस्याओं का कारण बनता है।
अधिसूचना तालिका का फूलना: (did, recordUri, reason) पर अद्वितीय बाधा के बिना, अधिसूचना तालिका डुप्लिकेट के साथ असीमित रूप से बढ़ती है। हमारी 1.3 बिलियन पंक्तियों (663 GB) तक पहुँच गई थी, इससे पहले कि हम इसे पकड़ पाते। INSERT में ON CONFLICT DO NOTHING जोड़ना तभी मदद करता है जब अद्वितीय इंडेक्स पहले मौजूद हो, और इंडेक्स बनाने के लिए मौजूदा डेटा का डीडुप्लीकेशन आवश्यक है।
पोस्ट एम्बेड तालिकाएँ: post_embed_image और post_embed_video तालिकाएँ डिफ़ॉल्ट रूप से आबाद नहीं होती हैं यदि आपका इंडेक्सर उन्हें संभालता नहीं है। इनके बिना, getAuthorFeed पर मीडिया फ़िल्टर कुछ भी नहीं लौटाता। इन्हें अलग से बैकफिल करने की आवश्यकता है।
लेबल नकार क्रम: लेबल नकार (हटाने) की घटनाएँ मूल लेबल को स्रोत, URI और मान द्वारा संदर्भित करती हैं। यदि नकार मूल लेबल से पहले आते हैं (बैकफिल के दौरान सामान्य), तो उन्हें मौन रूप से छोड़ दिया जाता है। label_sync टूल इन्हें पकड़ने के लिए पूर्ण स्ट्रीम को रीप्ले करता है।
Fjall कतार विषाक्तता: Fjall एम्बेडेड डेटाबेस (wintermute की कतारों के लिए उपयोग किया जाता है) क्रैश के बाद "विषाक्त" स्थिति में प्रवेश कर सकता है, सभी कतार संचालन को अवरुद्ध कर सकता है। समाधान कतार डेटाबेस निर्देशिका को हटाना और पुनरारंभ करना है - wintermute रिले के कर्सर से पकड़ लेगा (रिले लगभग 72 घंटे का इतिहास रखते हैं)।
TLS प्रदाता आरंभीकरण: Rust के rustls को किसी भी TLS कनेक्शन से पहले स्पष्ट रूप से एक क्रिप्टो प्रदाता स्थापित करने की आवश्यकता है। स्टार्टअप पर rustls::crypto::aws_lc_rs::default_provider().install_default() के बिना, फायरहोज़ के लिए पहला WebSocket कनेक्शन पैनिक करता है।
खाता माइग्रेशन के बाद हस्ताक्षर कुंजी रोटेशन: जब उपयोगकर्ता PDS के बीच माइग्रेट होते हैं, तो उनकी हस्ताक्षर कुंजी बदल जाती है। डेटाप्लेन आइडेंटिटी डेटा को 1 घंटे के staleTTL के साथ कैश करता है। उस विंडो के दौरान, माइग्रेटेड उपयोगकर्ताओं के लिए JWT सत्यापन विफल हो जाता है। समाधान सत्यापन पुनर्प्रयास पर कैश को दरकिनार करना और सीधे PLC निर्देशिका से हल करना है।
पूर्ण-नेटवर्क AppView (सभी ~42M उपयोगकर्ता, ~18.5B रिकॉर्ड) चलाने के आधार पर।
| संसाधन | न्यूनतम | अनुशंसित |
|---|---|---|
| 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 चला रहा है (केवल सामुदायिक सदस्यों को इंडेक्स करना), आवश्यकताएँ लगभग इंडेक्स किए गए खातों के साथ रैखिक रूप से बढ़ती हैं।
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/ में होंगे। ऊपरी स्रोत के परिवर्तनों के साथ अपने अतिरिक्त को रखकर हल करें।
ऊपरी स्रोत के समान: MIT और Apache 2.0 के तहत दोहरी-लाइसेंस। LICENSE-MIT.txt और LICENSE-APACHE.txt देखें।