اكتشف لماذا لم تعد قواعد البيانات التقليدية كافية لاحتياجات LinkedIn، وكيف صممت Espresso قاعدة بيانات موزعة تعتمد على تقسيم البيانات وتكرارها وإدارة الخوادم لتلبية متطلبات التوسع والأداء.تخيّل أنك تعمل في شركة لديها ملايين المستخدمين، وتحتاج إلى تخزين بيانات الملفات الشخصية والرسائل، وتقديمها بسرعة للتطبيقات، مع إمكانية تحديثها باستمرار.
هل تختار قاعدة بيانات علائقية مثل Oracle؟ أم مخزن Key-Value مثل Voldemort؟ أم تحاول بناء نظام يجمع ما تحتاجه من الاثنين؟
ده كان جزءًا من التحدي الذي واجهته LinkedIn.
المشكلة لم تكن في قواعد البيانات نفسها
كانت LinkedIn تستخدم قواعد البيانات العلائقية (RDBMS) مثل Oracle، إلى جانب مخازن Key-Value مثل Voldemort، لأن لكل نوع منها استخداماته المناسبة.
لكن مع نمو الأنظمة، ظهرت مجموعة من المشكلات العملية.
1. تطور الـ Schema أصبح عبئًا
في قواعد البيانات العلائقية، قد يتطلب تعديل بنية الجدول تنفيذ أوامر ALTER TABLE مكلفة، مع الحاجة إلى تدخل فريق إدارة قواعد البيانات (DBA).
وده يبطّئ فرق التطوير، خصوصًا في شركة تحتاج إلى تطوير منتجاتها بسرعة.
2. إضافة Shards جديدة ليست سهلة
توسيع قاعدة البيانات بإضافة أجزاء جديدة (Shards) لم يكن مجرد إضافة خادم؛ بل كان يتطلب أعمالًا يدوية وإعدادات خاصة بكل تطبيق.
3. التعافي من أعطال مراكز البيانات كان معقدًا
كانت قواعد البيانات التقليدية لدى LinkedIn تعمل بنمط Master/Slave. والانتقال من مركز بيانات إلى آخر عند حدوث عطل كان يتطلب التنسيق مع فريق الـ DBA، وقد يستلزم توقفًا مؤقتًا للخدمة.
4. التكلفة
كانت حلول قواعد البيانات العلائقية القديمة تعتمد على أجهزة مرتفعة التكلفة وتراخيص برمجية سنوية مكلفة.
وفي المقابل، كان Voldemort مناسبًا للتطبيقات التي تحتاج إلى الوصول للبيانات باستخدام المفتاح (key)، لكنه لم يكن يوفر كل الخصائص التي تحتاجها LinkedIn، ومنها تدفق تغييرات مرتب زمنيًا يمكن للأنظمة الأخرى استهلاكه.
إذًا، لم تكن المشكلة أن أحد الخيارين سيئ، بل أن كلًا منهما لم يكن يحقق جميع المتطلبات المطلوبة.
ماذا كانت LinkedIn تحتاج فعلًا؟
في عام 2011، حدد فريق Distributed Data Systems مجموعة من المتطلبات لنظام البيانات الجديد.
أولًا: التوسع الأفقي (Horizontal Scalability)
إمكانية توسيع ال (clusters) ببساطة عن طريق إضافة خوادم جديدة، بدلًا من الاعتماد على خادم أكبر أو إجراءات يدوية معقدة.
ثانيًا: الاتساق (Consistency)
كان النظام مطلوبًا منه دعم القراءة التي ترى الكتابة السابقة مباشرةً (Read-after-write)، وكذلك القراءات ذات الاتساق النهائي (Eventual Consistency) عندما يكون ذلك مناسبًا.
وكان يجب أن تظل الفهارس الثانوية (secondary index) متسقة مع البيانات الأساسية، مع دعم المعاملات داخل الجزء نفسه من البيانات (Partition).
ثالثًا: التوزيع وتحمل الأعطال
توزيع قاعدة البيانات على عدة خوادم، مع إمكانية تحديد عدد الأجزاء (Partitions) وعدد النسخ المكررة (Replicas) لكل قاعدة بيانات.
والأهم: استمرار النظام في العمل عند تعطل خادم، دون الحاجة إلى تدخل يدوي من المشغّلين.
رابعًا: البحث وتطور بنية البيانات
احتاجت LinkedIn إلى فهارس ثانوية (secondary index) للبحث النصي والبحث باستخدام الخصائص، مع إمكانية تغيير الـ Schema دون توقف الخدمة أو التنسيق المستمر مع فريق DBA.
خامسًا: إتاحة التغييرات للأنظمة الأخرى
لم تكن قاعدة البيانات مجرد مكان لحفظ البيانات. كان يجب أن تستطيع الأنظمة الأخرى استهلاك التغييرات التي تحدث فيها، وأن يكون استيراد البيانات المُنتجة خارج النظام، مثل البيانات المعالجة باستخدام Hadoop على HDFS، ممكنًا أيضًا.
ومن هنا بدأت فكرة Espresso.
Espresso: قاعدة بيانات بوظيفة محددة
Espresso هي قاعدة بيانات NoSQL موزعة، تعمل على الإنترنت، ومصممة لتحمل الأعطال.
وفقًا للمقال، كانت تشغّل نحو 30 تطبيقًا لدى LinkedIn، بما في ذلك ملفات الأعضاء الشخصية، ونظام InMail للرسائل بين الأعضاء، وأجزاء من الصفحة الرئيسية وتطبيقات الهاتف.
كما كانت تخدم ملايين السجلات في الثانية عند ذروة الاستخدام، وتحتفظ بمئات التيرابايت من البيانات الأساسية، دون احتساب النسخ المكررة.
لكن المثير هنا ليس حجم البيانات فقط، بل طريقة تنظيمها.
كيف تنظم Espresso البيانات؟
تعتمد Espresso على تسلسل هرمي:
Database → Table → Collection → Document
Database: حاوية للجداول. جميع الجداول داخل قاعدة البيانات الواحدة تشترك في طريقة تقسيم البيانات وموارد المعالجة المادية.
Table: تحتوي على مستندات من النوع نفسه. ويحدد مخطط الجدول بنية المفتاح (key) الذي تُستخدم أجزاؤه للوصول إلى المستند.
Collection: مجموعة من المستندات يمكن تحديدها باستخدام جزء من المفتاح (key)، بدلًا من تحديد المفتاح كاملًا.
Document: سجل مستقل يحدده مفتاح كامل، ويُعرّف مخططه باستخدام Avro.
لنأخذ مثالًا قريبًا من الواقع: جدول للرسائل يحتوي على MailboxID وMessageID.
يكون MailboxID هو مفتاح التقسيم (Partitioning Key)، بينما يكون MessageID جزءًا إضافيًا من المفتاح.
وبالتالي، فإن:
MailboxID = 100يحدد مجموعة الرسائل الخاصة بصندوق بريد معين.MailboxID = 100, MessageID = 1يحدد رسالة بعينها.
هذه البنية تسمح بتجميع الرسائل المرتبطة بصندوق بريد واحد ضمن الجزء نفسه من البيانات، وهو ما يساعد في تنفيذ عمليات مترابطة عليه.
وتضمن Espresso السلوك المعاملي (transactional behavior) لتحديث موارد متعددة تشترك في مفتاح التقسيم (partition key) نفسه، عندما تُرسل التحديثات ضمن طلب HTTP واحد.
لاحظ القيد هنا: المعاملة ليست متاحة بلا حدود عبر جميع أجزاء قاعدة البيانات؛ بل ترتبط بنطاق محدد.
ماذا يحدث عندما يصل طلب إلى Espresso؟
تتكون البنية الأساسية من عدة مكونات، لكل منها مسؤولية واضحة.
1. Router
هو نقطة الدخول لطلبات العملاء، ويعمل كوسيط HTTP عديم الحالة (Stateless Proxy).
يفحص عنوان الطلب، ويحدد قاعدة البيانات، ثم يستخدم مفتاح التقسيم لمعرفة الجزء الذي يحتوي على البيانات المطلوبة، ويوجه الطلب إلى خادم التخزين المناسب.
ويحتفظ بجدول توجيه محلي، يُحدّث عند تغير حالة العنقود (cluster) باستخدام ZooKeeper.
كما يستطيع تنفيذ عمليات قراءة متعددة عبر أجزاء مختلفة بالتوازي، ثم تجميع النتائج وإعادتها للعميل.
2. Storage Node
هذا هو المكوّن المسؤول عن تخزين البيانات ومعالجتها.
تستضيف كل عقدة مجموعة من الأجزاء، وتتولى معالجة الاستعلامات (queries)، وحفظ المستندات)(documents)، وإدارة البيانات الوصفية (metadata)، والفهارس الثانوية (secondary index)، والمعاملات المحلية (local transactions).
ومن التفاصيل المهمة أن Espresso صُممت لتسمح باستخدام محرك تخزين (storage engine) قابل للاستبدال، لكن جميع عمليات الإنتاج المذكورة في المقال كانت تستخدم MySQL مع InnoDB.
أي أن بناء قاعدة بيانات موزعة جديدة لا يعني بالضرورة كتابة محرك تخزين جديد بالكامل.
3. Helix
تستخدم Espresso مشروع Apache Helix لإدارة العنقود (cluster) وتوزيع الأجزاء بين الخوادم.
يقارن Helix بين التوزيع المثالي المطلوب والتوزيع الفعلي، ثم يحدد الانتقالات اللازمة للوصول إلى الحالة المطلوبة.
ومن القيود التي يفرضها نموذج الحالة:
- لكل Partition نسخة رئيسية واحدة (Master).
- يمكن أن توجد نسخ تابعة (Slaves) متعددة حسب الإعداد.
- تُوزع الأجزاء على الخوادم بصورة متوازنة.
- لا توجد نسختان من الجزء نفسه على الخادم ذاته.
- عند تعطل الـ Master، تُرقّى إحدى النسخ التابعة لتصبح Master.
وهكذا تتوزع المسؤوليات: الـ Router يوجّه الطلبات، وStorage Nodes تخزن وتعالج البيانات، وHelix يدير حالة العنقود (cluster).
الدرس الهندسي
ما يمكن تعلمه من تصميم Espresso هو أن اختيار قاعدة البيانات لا ينبغي أن يبدأ بالسؤال: «ما أفضل قاعدة بيانات؟».
بل يبدأ بأسئلة أكثر تحديدًا:
- ما نمط الوصول إلى البيانات؟
- ما مستوى الاتساق (consistency) المطلوب؟
- هل نحتاج إلى معاملات (Transactions)، وما نطاقها؟
- كيف سنوزع البيانات ونزيد السعة؟
- ماذا سيحدث عند تعطل خادم؟
- كيف ستصل التغييرات إلى الأنظمة الأخرى؟
- ما التكلفة التشغيلية التي يمكننا تحملها؟
Espresso لم تحاول أن تكون بديلًا شاملًا لكل قواعد البيانات. لقد بُنيت لتلبية مجموعة محددة من احتياجات LinkedIn، مع الاستفادة من تقنيات موجودة مثل MySQL وInnoDB وZooKeeper وHelix.
المبدأ: لا تصمم نظام البيانات بناءً على أسماء التقنيات أو شهرتها؛ صممه انطلاقًا من أنماط الوصول، ومتطلبات الاتساق، وحدود الأعطال، والتكلفة التشغيلية.
في النشرة القادمة، سننتقل إلى سؤال آخر: إذا وزّعت بياناتك على عدة خوادم، فكيف تضمن استمرار الخدمة عند تعطل أحدها، وكيف توصل التغييرات إلى الأنظمة الأخرى دون فقدان ترتيبها؟
المصدر: LinkedIn Engineering — Introducing Espresso: LinkedIn’s Hot New Distributed Document Store.

