
نسخة مشتقة من تنفيذ مرجع بروتوكول AT مع AppView محسّن للأداء، ومفهرس خرطوم يعتمد على Rust، وتخزين مؤقت Redis، وميزات مجتمعية للتواصل الاجتماعي المستضاف ذاتياً على نطاق واسع.
هذا هو فرع بلاك سكاي من التطبيق المرجعي لبروتوكول AT من قبل Bluesky Social PBC. يعمل على تشغيل AppView على api.blacksky.community.
نحن ننشر هذا من أجل الشفافية ولتستفيد المجتمعات الأخرى من العمل. هذا المستودع لا يقبل المساهمات أو القضايا أو طلبات السحب. إذا كنت ترغب في التطبيق الأصلي لـ atproto، استخدم bluesky-social/atproto.
جميع التغييرات موجودة في packages/bsky (منطق AppView)، services/bsky (إعدادات التشغيل)، وهجرة مخصصة واحدة. كل شيء آخر هو من المنبع.
تتضمن طبقة البيانات من المنبع مستهلك خدمة بلغة TypeScript (subscription.ts) يقوم بفهرسة الأحداث مباشرة. استبدلناه بـ rsky-wintermute، وهو مفهرس بلغة Rust، لعدة أسباب:
لا تزال طبقة البيانات و AppView من هذا المستودع تعمل كما هي. تقرأ من قاعدة بيانات PostgreSQL التي يكتب إليها Wintermute. نحن فقط لا نبدأ تشغيل اشتراك الخدمة المدمج.
هذه مفيدة على نطاق واسع لأي شخص يستضيف AppView بنفسه على نطاق واسع.
تحسين استعلام LATERAL JOIN (packages/bsky/src/data-plane/server/routes/feeds.ts)
getTimeline و getListFeed باستخدام LATERAL JOINs في PostgreSQL لفرض استخدام الفهرس لكل مستخدم بدلاً من مسح الجدول بالكامل. تحسن كبير للمستخدمين الذين يتابعون آلاف الحسابات.طبقة تخزين مؤقت Redis (packages/bsky/src/data-plane/server/cache/)
Timestamp طريقة .toDate() بعد إعادة التوزيع عبر Redis، مما يسبب تعبئة غير كاملة للملف الشخصي عند الوصول إلى ذاكرة التخزين المؤقت. نعمل حاليًا مع تعطيل التخزين المؤقت لـ 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) والأحرف التحكم من السجلات المخزنة قبل تحليل JSON. هذه صالحة وفقًا لـ RFC 8259 ولكن يتم رفضها بواسطة JSON.parse() في Node.js، مما يسبب فشل تحليل rowToRecord صامت في طبقة البيانات يظهر كمنشورات مفقودة.البنية التحتية للمنشورات المجتمعية الخاصة التي تعيش على AppView بدلاً من PDSes الفردية. خاصة بكيفية عمل بلاك سكاي، ولكن يمكن أن تكون مرجعًا للمجتمعات الأخرى.
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) | (بحث Go)
- مستهلك الخدمة | |
- معيد التعبئة | v
- مفهرس الوسوم | OpenSearch
- مفهرس مباشر |
v
bsky-dataplane (gRPC :2585) <--- Redis (اختياري)
|
v
bsky-appview (HTTP :2584)
|
v
خادم وكيل عكسي (Caddy/nginx)
Wintermute هي خدمة Rust متجانسة مع أربعة مسارات معالجة متوازية:
bsky.network عبر WebSocket، يكتب الأحداث إلى طوابير Fjall (مخزن قيم-مفاتيح مضمن)ON CONFLICT لضمان عدم التكرارأدوات CLI إضافية مضمنة في مستودع rsky:
queue_backfill — إدراج DIDs في قائمة انتظار إعادة التعبئة من CSV، أو اكتشاف PDS، أو قوائم DID مباشرةdirect_index — جلب وفهرسة مستودعات محددة متجاوزًا الطوابير (مفيد لإصلاح حسابات فردية)label_sync — إعادة تشغيل تدفق الوسوم من المؤشر 0 للحاق بالنفي المفقودplc_import — استيراد تعيينات handle/DID بكميات كبيرة من دليل PLCpalomar-sync — مزامنة أعداد المتابعين وPageRank إلى OpenSearchخدمة رفع الفيديو للمستخدمين الذين لا يدعم PDS الخاص بهم تطبيق Bluesky video.bsky.app. تستخدم DID الخاصة بها (did:web:video.blacksky.community) للمصادقة على PDSes الخاص بالمستخدم عبر JWTs مصادقة الخدمة. التدفق:
تأتي وسوم التعديل من خدمات الوسم (مثل Ozone من Bluesky) عبر اشتراك WebSocket. يقوم مبتلع Wintermute بمعالجة الوسوم في طابور مخصص label_live (حجم منخفض، منفصل عن الخدمة الرئيسية). يمكن لأداة label_sync إعادة تشغيل التدفق الكامل للوسوم للحاق بالنفي المفقود (إزالة الوسوم) دون إعادة إدراج الوسوم.
bskyيتم إنشاء مخطط bsky بواسطة هجرات طبقة البيانات. في التشغيل الأول، ستطبق طبقة البيانات جميع الهجرات تلقائيًا. الهجرة الوحيدة الخاصة ببلاك سكاي هي 20260202T120000000Z-add-community-post.ts (جدول المنشورات المجتمعية). إذا كنت لا تحتاج إلى منشورات مجتمعية، يمكنك إزالتها.
يكتب rsky-wintermute إلى نفس المخطط. جميع عبارات INSERT الخاصة به تستخدم ON CONFLICT لذلك من الآمن تشغيل wintermute وهجرات طبقة البيانات بأي ترتيب.
pnpm install
pnpm build
node services/bsky/dataplane.js
node services/bsky/api.js
تستغرق إعادة التعبئة الكاملة للشبكة (جميع ~42 مليون مستخدم، ~18.5 مليار سجل) أسابيع حتى مع المعالجة المتوازية لـ wintermute. توقع:
أثناء إعادة التعبئة، يكون AppView قيد التشغيل ولكنه سيظهر بيانات غير كاملة للمستخدمين الذين لم تتم إعادة تعبئتهم بعد. يتم فهرسة الأحداث الحية فورًا بغض النظر عن تقدم إعادة التعبئة.
هذه هي المشكلات التي واجهناها أثناء بدء تشغيل AppView للشبكة الكاملة. إذا كنت تفعل الشيء نفسه، فمن المحتمل أن تصادف بعضًا منها:
تلف JSON في تنسيق COPY النصي: يعامل بروتوكول COPY النصي في PostgreSQL الشرطة المائلة للخلف كحرف هروب. إذا لم يقم أداة التحميل الجماعي الخاص بك بهروب الشرطة المائلة للخلف في سلاسل JSON، فإن \" تصبح " وتحصل على سجلات تالفة بصمت. عمود record.json هو من نوع text (وليس jsonb)، لذلك لن يلتقط PostgreSQL ذلك. وجدنا حوالي 66,000 سجل تالف واضطررنا إلى إصلاحها عن طريق إعادة الجلب من API العام.
البايتات الفارغة في JSON: تحتوي بعض سجلات بروتوكول AT على \u0000 (بايت فارغ)، وهو JSON صالح وفقًا لـ RFC 8259 ولكن يتم رفضه بواسطة JSON.parse() في Node.js. تقوم طبقة البيانات بإرجاع null بصمت لهذه السجلات. قم بإزالة البايتات الفارغة قبل الكتابة إلى قاعدة البيانات.
حساسية تنسيق الطوابع الزمنية: تتوقع طبقة البيانات طوابع زمنية بدقة ملي ثانية ولاحقة Z (2026-01-12T19:45:23.307Z). الدقة النانوية أو تنسيق إزاحة المنطقة الزمنية (+00:00) يسبب مشكلات دقيقة في الفرز والمقارنة.
تضخم جدول الإشعارات: بدون قيد فريد على (did, recordUri, reason)، ينمو جدول الإشعارات بلا حدود مع التكرارات. وصل حجم جدولنا إلى 1.3 مليار صف (663 جيجابايت) قبل أن نلتقطه. إضافة ON CONFLICT DO NOTHING إلى INSERTs يساعد فقط إذا كان الفهرس الفريد موجودًا أولاً، ويتطلب إنشاء الفهرس إزالة التكرارات من البيانات الموجودة.
جداول الوسائط المضمنة في المنشورات: لا يتم ملء جداول post_embed_image و post_embed_video افتراضيًا إذا كان المفهرس الخاص بك لا يعالجها. بدون هذه الجداول، لا يعيد مرشح الوسائط في getAuthorFeed أي شيء. يجب إعادة تعبئة هذه الجداول بشكل منفصل.
ترتيب نفي الوسوم: تشير أحداث نفي الوسوم (الإزالة) إلى الوسم الأصلي بواسطة المصدر وURI والقيمة. إذا وصلت أحداث النفي قبل الوسم الأصلي (شائع أثناء إعادة التعبئة)، يتم إسقاطها بصمت. تقوم أداة label_sync بإعادة تشغيل التدفق الكامل لالتقاط هذه الحالات.
تسمم طابور Fjall: يمكن لقاعدة بيانات Fjall المضمنة (المستخدمة لطوابير wintermute) الدخول في حالة "مسمومة" بعد الأعطال، مما يمنع جميع عمليات الطابور. الإصلاح هو حذف دليل قاعدة بيانات الطابور وإعادة التشغيل — سيلحق wintermute بالركب من مؤشر المرحل (تحتفظ المرحلات بحوالي 72 ساعة من التاريخ).
تهيئة موفر TLS: يتطلب rustls في Rust تثبيت موفر تشفير صراحة قبل أي اتصال TLS. بدون rustls::crypto::aws_lc_rs::default_provider().install_default() عند بدء التشغيل، ينهار أول اتصال WebSocket بالخدمة.
تدوير مفتاح التوقيع بعد ترحيل الحساب: عندما يهاجر المستخدمون بين PDSes، يتغير مفتاح التوقيع الخاص بهم. تقوم طبقة البيانات بتخزين بيانات الهوية مؤقتًا مع staleTTL لمدة ساعة. خلال هذه النافذة، يفشل التحقق من JWT للمستخدمين المهاجرين. الإصلاح هو تجاوز ذاكرة التخزين المؤقت عند إعادة محاولة التحقق والحل مباشرة من دليل PLC.
استنادًا إلى تشغيل AppView للشبكة الكاملة (جميع ~42 مليون مستخدم، ~18.5 مليار سجل).
تفصيل التخزين (تقريبي، الشبكة الكاملة):
لمجتمع أصغر يشغل 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.
| المكون | المصدر | الغرض |
|---|
| rsky-wintermute | blacksky-algorithms/rsky | مفهرس خدمة بلغة Rust: يستهلك الأحداث، يعيد تعبئة المستودعات، يفهرس السجلات في PostgreSQL |
| rsky-relay | blacksky-algorithms/rsky | مكرر بروتوكول AT لتلقي وسوم التعديل من خدمات الوسم |
| rsky-video | blacksky-algorithms/rsky | خدمة رفع الفيديو: تحويل عبر CDN Bunny Stream، رفع مراجع البلوب إلى PDSes الخاص بالمستخدم |
| bsky-dataplane | هذا المستودع (services/bsky) | طبقة بيانات gRPC فوق PostgreSQL |
| bsky-appview | هذا المستودع (services/bsky) | خادم API HTTP لنقاط نهاية XRPC لـ app.bsky.* |
| Palomar | blacksky-algorithms/indigo | بحث كامل النص: يفهرس الملفات الشخصية والمنشورات في OpenSearch مع تعزيز عدد المتابعين |
| palomar-sync | blacksky-algorithms/rsky | يزامن أعداد المتابعين ونتائج PageRank من PostgreSQL إلى OpenSearch |
| المتغير | مطلوب | الوصف |
|---|
DB_PRIMARY_URL | نعم | سلسلة اتصال PostgreSQL مع ?options=-csearch_path%3Dbsky |
DB_REPLICA_URL | لا | سلسلة اتصال النسخة المتماثلة للقراءة |
BSKY_DATAPLANE_PORT | لا | منفذ gRPC (الافتراضي 2585) |
BSKY_REDIS_HOST | لا | عنوان Redis:المنفذ للتخزين المؤقت (يوصى حاليًا بتركه معطلاً) |
BLACKSKY_MEMBERSHIP_DB_URL | لا | قاعدة بيانات منفصلة للعضوية المجتمعية (خاصة ببلاك سكاي) |
| المتغير | مطلوب | الوصف |
|---|
BSKY_APPVIEW_PORT | لا | منفذ HTTP (الافتراضي 2584) |
BSKY_DATAPLANE_URLS | نعم | عناوين URL gRPC لطبقة البيانات مفصولة بفواصل |
BSKY_DID | نعم | DID الخاص بـ AppView (مثل did:web:api.example.com) |
BSKY_MOD_SERVICE_DID | نعم | DID خدمة التعديل Ozone |
BSKY_ADMIN_PASSWORDS | نعم | كلمات مرور المشرف مفصولة بفواصل للمصادقة الأساسية |
| المورد | الحد الأدنى | الموصى به |
|---|
| وحدة المعالجة المركزية | 16 نواة | 48+ نواة |
| ذاكرة الوصول العشوائي | 64 جيجابايت | 256 جيجابايت |
| التخزين | 10 تيرابايت NVMe | 28+ تيرابايت NVMe (RAID) |
| PostgreSQL | مخصص، نفس الجهاز أو زمن وصول منخفض | يوصى بنفس الجهاز |
| الشبكة | 100 ميجابت/ثانية مستدامة | 1 جيجابت/ثانية+ |
| مجموعة الجدول | الحجم |
|---|
| المنشورات + السجلات | ~3.5 تيرابايت |
| الإعجابات | ~2 تيرابايت |
| المتابعات | ~500 جيجابايت |
| الإشعارات | ~600 جيجابايت |
| الفهارس | ~4 تيرابايت |
| OpenSearch (Palomar) | ~500 جيجابايت |