أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة

يقوم مستخدمونا بكتابة الرسائل لبعضهم البعض دون أن يشعروا بالتعب.
أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة
هذا كثير جدًا. إذا قررت قراءة جميع رسائل جميع المستخدمين، فسيستغرق الأمر أكثر من 150 ألف سنة. بشرط أن تكون قارئًا متقدمًا إلى حد ما ولا تقضي أكثر من ثانية في كل رسالة.

مع هذا الحجم من البيانات، من المهم أن يتم بناء منطق تخزينها والوصول إليها على النحو الأمثل. وإلا، ففي لحظة واحدة غير رائعة، قد يصبح من الواضح أن كل شيء سوف يسير على نحو خاطئ قريبًا.

بالنسبة لنا، جاءت هذه اللحظة منذ عام ونصف. كيف وصلنا إلى هذا وماذا حدث في النهاية - نخبرك بالترتيب.

خلفية

في التطبيق الأول، عملت رسائل فكونتاكتي على مزيج من PHP الخلفية وMySQL. يعد هذا حلاً طبيعيًا تمامًا لموقع ويب صغير للطلاب. ومع ذلك، نما هذا الموقع بشكل لا يمكن السيطرة عليه وبدأ يطالب بتحسين هياكل البيانات لنفسه.

وفي نهاية عام 2009، تم إنشاء أول مستودع لمحركات النصوص، وفي عام 2010 تم نقل الرسائل إليه.

في محرك النصوص، تم تخزين الرسائل في قوائم - نوع من "صناديق البريد". يتم تحديد كل قائمة من هذا القبيل بواسطة uid - المستخدم الذي يملك كل هذه الرسائل. تحتوي الرسالة على مجموعة من السمات: معرف المحاور، والنص، والمرفقات، وما إلى ذلك. معرف الرسالة داخل "المربع" هو local_id، ولا يتغير أبدًا ويتم تعيينه بالتسلسل للرسائل الجديدة. "الصناديق" مستقلة وغير متزامنة مع بعضها البعض داخل المحرك، ويتم الاتصال بينها على مستوى PHP. يمكنك إلقاء نظرة على بنية البيانات وقدرات محرك النصوص من الداخل هنا.
أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة
كان هذا كافياً للمراسلات بين اثنين من المستخدمين. خمن ماذا حدث بعد ذلك؟

في مايو 2011، قدمت فكونتاكتي ميزة المحادثات مع العديد من المشاركين — الدردشة المتعددة. للعمل معهم، قمنا بطرح مجموعتين جديدتين - محادثات الأعضاء وأعضاء الدردشة. يقوم الأول بتخزين البيانات حول الدردشات التي يجريها المستخدمون، بينما يقوم الثاني بتخزين البيانات حول المستخدمين من خلال الدردشات. بالإضافة إلى القوائم نفسها، يتضمن ذلك، على سبيل المثال، المستخدم الداعي ووقت إضافته إلى الدردشة.

يقول المستخدم: "PHP، دعنا نرسل رسالة إلى الدردشة".
يقول PHP: "هيا، {اسم المستخدم}".
أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة
هناك عيوب لهذا المخطط. المزامنة لا تزال مسؤولية PHP. تعد الدردشات الكبيرة والمستخدمين الذين يرسلون رسائل إليهم في نفس الوقت قصة خطيرة. نظرًا لأن مثيل محرك النص يعتمد على المعرف الفريد (uid)، يمكن للمشاركين في الدردشة تلقي نفس الرسالة في أوقات مختلفة. ويمكن للمرء أن يتعايش مع هذا إذا توقف التقدم. لكن هذا لن يحدث.

في نهاية عام 2015، أطلقنا رسائل مجتمعية، وفي بداية عام 2016، أطلقنا واجهة برمجة التطبيقات (API) لهم. مع ظهور روبوتات الدردشة الكبيرة في المجتمعات، كان من الممكن نسيان التوزيع المتساوي للأحمال.

يُنشئ الروبوت الجيد عدة ملايين من الرسائل يوميًا - حتى أكثر المستخدمين ثرثرة لا يمكنهم التباهي بهذا. وهذا يعني أن بعض حالات محرك النصوص، التي تعيش عليها مثل هذه الروبوتات، بدأت تعاني إلى أقصى حد.

محركات الرسائل في عام 2016 هي 100 مثيل لدردشة الأعضاء ومحادثات الأعضاء، و8000 محرك نصي. وقد تم استضافتها على آلاف الخوادم، كل خادم بسعة 64 جيجابايت من الذاكرة. وكإجراء طارئ أول، قمنا بزيادة الذاكرة بمقدار 32 جيجابايت أخرى. لقد قدرنا التوقعات. وبدون تغييرات جذرية، سيكون هذا كافيا لمدة عام آخر تقريبا. تحتاج إما إلى الحصول على الأجهزة أو تحسين قواعد البيانات نفسها.

نظرًا لطبيعة البنية، فمن المنطقي زيادة الأجهزة بشكل مضاعفات. وهذا يعني مضاعفة عدد السيارات على الأقل - ومن الواضح أن هذا طريق مكلف إلى حد ما. سوف نقوم بتحسين.

مفهوم جديد

الجوهر المركزي للنهج الجديد هو الدردشة. تحتوي الدردشة على قائمة بالرسائل المتعلقة بها. المستخدم لديه قائمة الدردشات.

الحد الأدنى المطلوب هو قاعدتي بيانات جديدتين:

  • محرك الدردشة. هذا مستودع لمتجهات الدردشة. تحتوي كل دردشة على ناقل للرسائل المتعلقة بها. تحتوي كل رسالة على نص ومعرف رسالة فريد داخل الدردشة - chat_local_id.
  • محرك المستخدم. هذا مخزن لمتجهات المستخدمين - روابط للمستخدمين. كل مستخدم لديه ناقل معرف النظير (المحاورون - المستخدمون الآخرون، الدردشة المتعددة أو المجتمعات) وناقل الرسائل. يحتوي كل معرف نظير على ناقل للرسائل المتعلقة به. تحتوي كل رسالة على chat_local_id ومعرف رسالة فريد لهذا المستخدم - user_local_id.

أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة
تتواصل المجموعات الجديدة مع بعضها البعض باستخدام TCP - وهذا يضمن عدم تغيير ترتيب الطلبات. يتم تسجيل الطلبات نفسها والتأكيدات الخاصة بها على القرص الصلب - حتى نتمكن من استعادة حالة قائمة الانتظار في أي وقت بعد فشل المحرك أو إعادة تشغيله. نظرًا لأن محرك المستخدم ومحرك الدردشة يتكونان من 4 آلاف جزء لكل منهما، فسيتم توزيع قائمة انتظار الطلبات بين المجموعات بالتساوي (ولكن في الواقع لا يوجد أي منها على الإطلاق - وهي تعمل بسرعة كبيرة).

يعتمد العمل مع القرص في قواعد بياناتنا في معظم الحالات على مزيج من سجل التغييرات الثنائي (binlog) واللقطات الثابتة وصورة جزئية في الذاكرة. تتم كتابة التغييرات خلال اليوم إلى binlog، ويتم إنشاء لقطة للحالة الحالية بشكل دوري. اللقطة هي عبارة عن مجموعة من هياكل البيانات المحسنة لأغراضنا. وهو يتألف من رأس (الفهرس التعريفي للصورة) ومجموعة من ملفات التعريف. يتم تخزين الرأس بشكل دائم في ذاكرة الوصول العشوائي (RAM) ويشير إلى مكان البحث عن البيانات من اللقطة. يتضمن كل ملف تعريف البيانات التي من المحتمل أن تكون مطلوبة في نقاط زمنية قريبة - على سبيل المثال، المتعلقة بمستخدم واحد. عند الاستعلام عن قاعدة البيانات باستخدام رأس اللقطة، تتم قراءة ملف التعريف المطلوب، ثم يتم أخذ التغييرات في سجل البيانات التي حدثت بعد إنشاء اللقطة في الاعتبار. يمكنك قراءة المزيد عن فوائد هذا النهج هنا.

في الوقت نفسه، تتغير البيانات الموجودة على القرص الصلب نفسه مرة واحدة فقط في اليوم - في وقت متأخر من الليل في موسكو، عندما يكون الحمل في حده الأدنى. بفضل هذا (مع العلم أن البنية الموجودة على القرص ثابتة طوال اليوم)، يمكننا استبدال المتجهات بمصفوفات ذات حجم ثابت - ونتيجة لذلك، زيادة في الذاكرة.

يبدو إرسال الرسالة في المخطط الجديد كما يلي:

  1. تتصل الواجهة الخلفية لـ PHP بمحرك المستخدم وتطلب منه إرسال رسالة.
  2. يقوم وكلاء محرك المستخدم بإرسال الطلب إلى مثيل محرك الدردشة المطلوب، والذي يعود إلى chat_local_id لمحرك المستخدم - وهو معرف فريد لرسالة جديدة داخل هذه الدردشة. يقوم محرك الدردشة بعد ذلك ببث الرسالة إلى كافة المستلمين في الدردشة.
  3. يتلقى محرك المستخدم chat_local_id من محرك الدردشة ويعيد user_local_id إلى PHP - معرف رسالة فريد لهذا المستخدم. يتم بعد ذلك استخدام هذا المعرف، على سبيل المثال، للعمل مع الرسائل عبر واجهة برمجة التطبيقات.

أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة
ولكن بالإضافة إلى إرسال الرسائل فعليًا، تحتاج إلى تنفيذ بعض الأشياء الأكثر أهمية:

  • القوائم الفرعية هي، على سبيل المثال، أحدث الرسائل التي تراها عند فتح قائمة المحادثات. الرسائل غير المقروءة، والرسائل ذات العلامات ("هام"، "البريد العشوائي"، وما إلى ذلك).
  • ضغط الرسائل في محرك الدردشة
  • تخزين الرسائل مؤقتًا في محرك المستخدم
  • البحث (من خلال جميع مربعات الحوار وداخل مربع حوار محدد).
  • التحديث في الوقت الحقيقي (Longpolling).
  • حفظ السجل لتنفيذ التخزين المؤقت على عملاء الهاتف المحمول.

جميع القوائم الفرعية تتغير بنياتها بسرعة. للعمل معهم نستخدم أشجار سبلاي. يتم تفسير هذا الاختيار من خلال حقيقة أننا في الجزء العلوي من الشجرة نقوم أحيانًا بتخزين جزء كامل من الرسائل من لقطة - على سبيل المثال، بعد إعادة الفهرسة الليلية، تتكون الشجرة من قمة واحدة تحتوي على جميع رسائل القائمة الفرعية. تجعل شجرة Splay من السهل إدخالها في منتصف هذه القمة دون التفكير في التوازن. بالإضافة إلى ذلك، لا يقوم Splay بتخزين البيانات غير الضرورية، مما يوفر لنا الذاكرة.

تتضمن الرسائل كمية كبيرة من المعلومات، معظمها نصية، وهو أمر مفيد للتمكن من ضغطها. من المهم أن نتمكن من إلغاء أرشفة رسالة فردية بدقة. يستخدم لضغط الرسائل خوارزمية هوفمان من خلال الاستدلال الخاص بنا - على سبيل المثال، نعلم أنه في الرسائل تتناوب الكلمات مع "غير الكلمات" - المسافات وعلامات الترقيم - ونتذكر أيضًا بعض ميزات استخدام الرموز للغة الروسية.

نظرًا لوجود عدد أقل بكثير من المستخدمين مقارنة بالمحادثات، لحفظ طلبات قرص الوصول العشوائي في محرك الدردشة، نقوم بتخزين الرسائل في محرك المستخدم.

يتم تنفيذ البحث عن الرسائل كاستعلام قطري من محرك المستخدم إلى كافة مثيلات محرك الدردشة التي تحتوي على محادثات هذا المستخدم. يتم دمج النتائج في محرك المستخدم نفسه.

حسنًا، لقد تم أخذ جميع التفاصيل في الاعتبار، ولم يتبق سوى التبديل إلى مخطط جديد - ويفضل أن يكون ذلك دون أن يلاحظه المستخدمون.

ترحيل البيانات

لذلك، لدينا محرك نصي يقوم بتخزين الرسائل حسب المستخدم، ومجموعتين من أعضاء الدردشة ومحادثات الأعضاء التي تخزن البيانات حول غرف الدردشة المتعددة والمستخدمين فيها. كيف يمكن الانتقال من هذا إلى محرك المستخدم ومحرك الدردشة الجديد؟

تم استخدام محادثات الأعضاء في المخطط القديم بشكل أساسي للتحسين. وسرعان ما قمنا بنقل البيانات اللازمة منه إلى أعضاء الدردشة، ومن ثم لم يعد يشارك في عملية الترحيل.

قائمة الانتظار لأعضاء الدردشة. ويشمل 100 حالة، بينما يحتوي محرك الدردشة على 4 آلاف. لنقل البيانات، تحتاج إلى جعلها متوافقة - لهذا، تم تقسيم أعضاء الدردشة إلى نفس الـ 4 آلاف نسخة، ثم تم تمكين قراءة سجل أعضاء الدردشة في محرك الدردشة.
أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة
الآن يعرف محرك الدردشة عن المحادثات المتعددة بين أعضاء الدردشة، لكنه لا يعرف حتى الآن أي شيء عن الحوارات مع اثنين من المحاورين. توجد مثل هذه الحوارات في محرك النص مع الإشارة إلى المستخدمين. هنا أخذنا البيانات "مباشرة": كل مثيل لمحرك الدردشة يسأل جميع مثيلات محرك النص عما إذا كان لديهم الحوار الذي يحتاجه.

رائع - يعرف محرك الدردشة ما هي الدردشات المتعددة الموجودة ويعرف ما هي الحوارات الموجودة.
تحتاج إلى دمج الرسائل في محادثات الدردشة المتعددة بحيث ينتهي بك الأمر بقائمة من الرسائل في كل دردشة. أولاً، يسترد محرك الدردشة من محرك النصوص جميع رسائل المستخدم من هذه الدردشة. في بعض الحالات، يوجد عدد كبير جدًا منها (يصل إلى مئات الملايين)، ولكن مع استثناءات نادرة جدًا، تتناسب الدردشة تمامًا مع ذاكرة الوصول العشوائي (RAM). لدينا رسائل غير مرتبة، كل منها في عدة نسخ - بعد كل شيء، يتم سحبها جميعًا من مثيلات مختلفة لمحركات النصوص المقابلة للمستخدمين. الهدف هو فرز الرسائل والتخلص من النسخ التي تشغل مساحة غير ضرورية.

تحتوي كل رسالة على طابع زمني يحتوي على وقت إرسالها والنص. نحن نستخدم الوقت للفرز - نضع مؤشرات على أقدم رسائل المشاركين في الدردشة المتعددة ونقارن التجزئات من نص النسخ المقصودة، ونتجه نحو زيادة الطابع الزمني. من المنطقي أن تحتوي النسخ على نفس التجزئة والطابع الزمني، ولكن من الناحية العملية ليس هذا هو الحال دائمًا. كما تتذكر، تم تنفيذ المزامنة في المخطط القديم بواسطة PHP - وفي حالات نادرة، اختلف وقت إرسال نفس الرسالة بين مستخدمين مختلفين. في هذه الحالات، سمحنا لأنفسنا بتعديل الطابع الزمني - عادةً خلال ثانية واحدة. المشكلة الثانية هي الترتيب المختلف للرسائل لمستلمين مختلفين. في مثل هذه الحالات، سمحنا بإنشاء نسخة إضافية، مع خيارات طلب مختلفة لمستخدمين مختلفين.

بعد ذلك، يتم إرسال البيانات المتعلقة بالرسائل في الدردشة المتعددة إلى محرك المستخدم. وهنا تأتي ميزة غير سارة للرسائل المستوردة. في التشغيل العادي، يتم ترتيب الرسائل التي تصل إلى المحرك بشكل صارم بترتيب تصاعدي بواسطة user_local_id. فقدت الرسائل المستوردة من المحرك القديم إلى محرك المستخدم هذه الخاصية المفيدة. في الوقت نفسه، من أجل راحة الاختبار، يجب أن تكون قادرا على الوصول إليها بسرعة، ابحث عن شيء ما فيها وإضافة أشياء جديدة.

نحن نستخدم بنية بيانات خاصة لتخزين الرسائل المستوردة.

وهو يمثل ناقل الحجم أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياةاين الجميع أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة - مختلفة ومرتبة تنازلياً، مع ترتيب خاص للعناصر. في كل شريحة مع المؤشرات أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة يتم فرز العناصر. يستغرق البحث عن عنصر في مثل هذا الهيكل وقتًا أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة من خلال أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة عمليات البحث الثنائية. يتم إطفاء إضافة العنصر أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة.

لذلك، اكتشفنا كيفية نقل البيانات من المحركات القديمة إلى محركات جديدة. لكن هذه العملية تستغرق عدة أيام - ومن غير المرجح أن يتخلى مستخدمونا عن عادة الكتابة لبعضهم البعض خلال هذه الأيام. ولكي لا نفقد الرسائل خلال هذا الوقت، ننتقل إلى نظام عمل يستخدم كلاً من المجموعات القديمة والجديدة.

تتم كتابة البيانات إلى أعضاء الدردشة ومحرك المستخدم (وليس إلى محرك النصوص، كما هو الحال في التشغيل العادي وفقًا للمخطط القديم). يقوم وكلاء محرك المستخدم بطلب محرك الدردشة - وهنا يعتمد السلوك على ما إذا كانت هذه الدردشة قد تم دمجها بالفعل أم لا. إذا لم يتم دمج الدردشة بعد، فلن يكتب محرك الدردشة الرسالة لنفسه، وتتم معالجتها فقط في محرك النص. إذا تم دمج الدردشة بالفعل في محرك الدردشة، فإنه يعيد chat_local_id إلى محرك المستخدم ويرسل الرسالة إلى جميع المستلمين. يقوم وكلاء محرك المستخدم بنقل جميع البيانات إلى محرك النص - بحيث إذا حدث شيء ما، يمكننا دائمًا التراجع، مع وجود جميع البيانات الحالية في المحرك القديم. يقوم محرك النص بإرجاع user_local_id، الذي يقوم محرك المستخدم بتخزينه وإعادته إلى الواجهة الخلفية.
أعد كتابة قاعدة بيانات رسائل فكونتاكتي من الصفر وابق على قيد الحياة
ونتيجة لذلك، تبدو عملية الانتقال كما يلي: نقوم بتوصيل مجموعات محرك المستخدم ومحركات الدردشة الفارغة. يقرأ محرك الدردشة السجل الكامل لأعضاء الدردشة، ثم يبدأ إنشاء الوكيل وفقًا للمخطط الموضح أعلاه. نقوم بنقل البيانات القديمة ونحصل على مجموعتين متزامنتين (قديمة وجديدة). كل ما تبقى هو تبديل القراءة من محرك النص إلى محرك المستخدم وتعطيل الوكيل.

النتائج

بفضل النهج الجديد، تم تحسين جميع مقاييس أداء المحركات وتم حل المشكلات المتعلقة بتناسق البيانات. يمكننا الآن تنفيذ ميزات جديدة في الرسائل بسرعة (وقد بدأنا بالفعل في القيام بذلك - قمنا بزيادة الحد الأقصى لعدد المشاركين في الدردشة، وقمنا بتنفيذ بحث عن الرسائل المعاد توجيهها، وأطلقنا الرسائل المثبتة ورفعنا الحد الأقصى لعدد الرسائل الإجمالية لكل مستخدم) .

التغييرات في المنطق هائلة حقا. وأود أن أشير إلى أن هذا لا يعني دائمًا سنوات كاملة من التطوير بواسطة فريق ضخم وعدد لا يحصى من أسطر التعليمات البرمجية. محرك الدردشة ومحرك المستخدم بالإضافة إلى جميع القصص الإضافية مثل Huffman لضغط الرسائل وأشجار Splay وبنية الرسائل المستوردة أقل من 20 ألف سطر من التعليمات البرمجية. وقد تمت كتابتها بواسطة 3 مطورين في 10 أشهر فقط (ومع ذلك، يجدر بنا أن نأخذ في الاعتبار ذلك جميع ثلاثة مطور - أبطال العالم في البرمجة الرياضية).

علاوة على ذلك، بدلاً من مضاعفة عدد الخوادم، انتهى بنا الأمر إلى تقليل عددها بمقدار النصف - الآن يعمل محرك المستخدم ومحرك الدردشة على 500 جهاز فعلي، في حين يتمتع النظام الجديد بمساحة تحميل كبيرة. لقد وفرنا الكثير من المال على المعدات - حوالي 5 ملايين دولار + 750 ألف دولار سنويًا في نفقات التشغيل.

نحن نسعى جاهدين لإيجاد أفضل الحلول للمشاكل الأكثر تعقيدًا وواسعة النطاق. لدينا الكثير منهم - ولهذا السبب نبحث عن المطورين الموهوبين في قسم قواعد البيانات. إذا كنت تحب وتعرف كيفية حل مثل هذه المشكلات، ولديك معرفة ممتازة بالخوارزميات وهياكل البيانات، فنحن ندعوك للانضمام إلى الفريق. اتصل بنا HRللتفاصيل.

حتى لو لم تكن هذه القصة عنك، يرجى ملاحظة أننا نقدر التوصيات. أخبر صديقًا عنه الوظائف الشاغرة للمطورينوإذا أكمل فترة الاختبار بنجاح، فستحصل على مكافأة قدرها 100 ألف روبل.

المصدر: www.habr.com

إضافة تعليق