التوسّع وموازنة الأحمال

التجزئة من أجل التوسع

18 دقيقة الدرس 8 من 10

التجزئة من أجل التوسع

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

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

التجزئة مقابل التقسيم: كثيرًا ما يُستخدم المصطلحان بالتبادل، لكن ثمة فرق مفيد: التقسيم (Partitioning) يشطر الجدول داخل خادم قاعدة بيانات واحد (مثل التقسيم الشهري في MySQL). أما التجزئة (Sharding) فتوزع البيانات عبر خوادم قواعد بيانات مستقلة تمامًا. التجزئة هي التقنية التي تُتيح التوسع الأفقي الحقيقي في الكتابة والتخزين.

لماذا التجزئة أصلًا؟

قبل الالتزام بالتجزئة — التي تضيف تعقيدًا كبيرًا — ضع في اعتبارك المشكلات التي تحلها:

  • معدل الكتابة: يمكن لـ MySQL الأساسي الواحد التعامل مع نحو 20,000–30,000 عملية كتابة في الثانية في ظروف مثالية. بأربعة أجزاء يمكنك الحفاظ على ~80,000–120,000 كتابة/ثانية. بـ32 جزءًا، تصبح الملايين في الثانية ممكنة.
  • حجم مجموعة البيانات: مجموعة بيانات بـ50 تيرابايت على آلة واحدة تعني أن كل استعلام يُمسح فهرسًا بـ50 تيرابايت. موزعةً على 10 أجزاء بـ5 تيرابايت لكل منها، يلمس كل استعلام جزءًا واحدًا فحسب — ومسح الفهرس أصغر بـ10 مرات.
  • ضغط الذاكرة: تعمل قواعد البيانات على أفضل وجه حين تسع مجموعة العمل (البيانات الساخنة) في ذاكرة RAM. التجزئة تُصغّر مجموعة بيانات كل جزء لتتسع مجموعة عمله في RAM مثيل متواضع.
  • العزل: مهمة دفعية صاخبة على جزء مستأجر لا تُبطئ أجزاء المستأجرين الآخرين.

اختيار مفتاح الجزء

أهم قرار في أي نظام مُجزَّأ هو مفتاح الجزء (Shard Key) — العمود (أو تركيبة الأعمدة) المستخدم لتحديد الجزء الذي ينتمي إليه صف معين. مفتاح الجزء السيئ يُفرز نقاطًا ساخنة واستعلامات غير كفؤة وإعادة تجزئة مؤلمة. أما المفتاح الجيد فيوزّع الحمل بالتساوي ويتوافق مع أنماط وصولك.

استراتيجيات مفتاح الجزء الشائعة

1. التجزئة المستندة إلى الهاش: تُطبَّق دالة هاش على قيمة مفتاح الجزء، ويُقسَّم الناتج على عدد الأجزاء للحصول على معرّف الجزء.

shard_id = hash(user_id) % number_of_shards # user_id 10001 → hash = 9283 → 9283 % 4 = 3 → Shard 3 # user_id 10002 → hash = 1147 → 1147 % 4 = 3 → Shard 3 # user_id 10003 → hash = 6721 → 6721 % 4 = 1 → Shard 1

المزايا: توزيع متساوٍ على الأجزاء. العيوب: استعلامات النطاق تستلزم المسح على جميع الأجزاء؛ وإضافة جزء دون الهاش المتسق تُعيد توجيه معظم المفاتيح.

2. التجزئة المستندة إلى النطاق: تُسنَد الصفوف إلى أجزاء وفق نطاق رقمي أو أبجدي للمفتاح — مثلًا، معرفات المستخدمين 1–1,000,000 تذهب إلى الجزء 1، ومعرفات 1,000,001–2,000,000 إلى الجزء 2.

المزايا: استعلامات النطاق كفؤة. العيوب: نقاط ساخنة إذا كانت البيانات تُنشأ بشكل متسلسل.

3. التجزئة المستندة إلى الدليل: خدمة بحث منفصلة ("دليل الأجزاء") تُعيّن كل مفتاح إلى جزئه. الاستعلامات تستشير هذا الدليل قبل التوجيه.

المزايا: مرونة قصوى. العيوب: الدليل تبعية حرجة جديدة؛ يضيف قفزة شبكية لكل استعلام.

4. التجزئة الجغرافية / القائمة على المستأجر: تُستخدم في أنظمة SaaS. جميع بيانات مستأجر (أو منطقة) معين تقيم على جزء واحد. الاستعلامات دائمًا أحادية الجزء، والامتثال القانوني (إقامة البيانات) سهل التطبيق.

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

مخطط: توجيه مفتاح الجزء

Shard Key Routing — Hash-Based Sharding Application Shard Router hash(user_id) % 4 Shard 0 users 0, 4, 8… ~250M rows DB Host: db-0 + replica Shard 1 users 1, 5, 9… ~250M rows DB Host: db-1 + replica Shard 2 users 2, 6, 10… ~250M rows DB Host: db-2 + replica Shard 3 users 3, 7, 11… ~250M rows DB Host: db-3 + replica Cross-shard query (e.g. leaderboard): fan out to all 4 shards → merge results in application layer Expensive — design your shard key to make these rare Single-shard query: fast, cheap
التجزئة بالهاش: يُعيّن الموجّه هاش معرف المستخدم لاختيار جزء واحد. استعلامات عبر الأجزاء تتفرع إلى جميعها وتُدمج نتائجها في طبقة التطبيق.

مشكلة النقاط الساخنة

تنشأ النقطة الساخنة (Hotspot) حين يستقبل جزء واحد حصةً غير متناسبة من حركة المرور بينما تجلس الأجزاء الأخرى خاملة. تتسبب في ذلك مصدران:

  • المفاتيح الساخنة: مفتاح واحد (أو مجموعة صغيرة منها) يستقبل حركة مرور بالغة الحدة — تصوّر تغريدة فيروسية من حساب يتابعه 100 مليون شخص. إذا كان كل نشاط هذا الحساب يُوجَّه إلى جزء واحد فهو مُثقَل. الحل: الجزء بمفتاح أكثر دقة (مثل tweet_id بدلًا من author_id)، أو إضافة كاش تفريخ على مستوى التطبيق.
  • توزيع غير متكافئ للبيانات: بعض نطاقات الأجزاء أكثر كثافةً بطبيعتها. إذا جزّأت باسم العائلة أبجديًا، فالأسماء التي تبدأ بـ"S" قد تُمثّل 15% من البيانات بينما "X" لا تُمثّل 0.01%. الحل: استخدام التجزئة بالهاش بدلًا من التجزئة بالنطاق، أو إعادة توازن دورية.
لا تستخدم معرف الزيادة التلقائية مفتاحَ جزءٍ مستنِدًا إلى النطاق. ستُدرَج جميع الصفوف الجديدة دائمًا في الجزء الأخير (النطاق الأعلى). يصبح ذلك الجزء وجهةً دائمة لكتابات ساخنة بينما تجلس جميع الأجزاء الأخرى خاملة من حيث الكتابة. استخدم هاش المعرف، أو UUID، أو معرف Snowflake الذي يوزّع الكتابات بشكل موحد.

الاستعلامات والانضمامات عبر الأجزاء

في عالم قاعدة البيانات الواحدة، الانضمام (JOIN) بين جدولين عملية رخيصة داخل العملية. في عالم الأجزاء، البيانات المرتبطة بمفتاح أجنبي قد تقيم على أجزاء مختلفة. يصبح الانضمام انضمامًا موزعًا: جلب السجلات من الجزء A، وجلب السجلات ذات الصلة من الجزء B، والدمج في ذاكرة التطبيق. هذا مكلف ومعرض لزمن استجابة عالٍ ويصعب أن يكون معاملاتيًا.

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

بعض البيانات — كفهرس المنتجات العالمي أو جدول تحويل العملات — لا يمكن تجزئتها بشكل ذي معنى. احتفظ بهذه البيانات المرجعية على كل جزء (مُكررة)، أو قدّمها من كاش قراءة مشترك أمام جميع الأجزاء.

الهاش المتسق: إضافة أجزاء دون إعادة هاش كاملة

العيب المميت في هاش المودولو البسيط (hash(key) % N) هو أن تغيير N (إضافة أجزاء أو إزالتها) يُبطل تقريبًا كل تعيينات المفاتيح. إذا انتقلت من 4 إلى 5 أجزاء، فإن ~80% من المفاتيح تُعيَّن إلى أجزاء مختلفة — مما يُطلق هجرة بيانات ضخمة.

الهاش المتسق (Consistent Hashing) يحل هذا بترتيب الأجزاء على حلقة افتراضية. إضافة جزء جديد لا تُهجّر إلا المفاتيح التي كانت مُسنَدة إلى جيرانه المباشرين على الحلقة — عادةً 1/N فقط من إجمالي المفاتيح. Cassandra وAmazon DynamoDB ومعظم قواعد البيانات المُجزَّأة الحديثة تستخدم الهاش المتسق تحت الغطاء.

Consistent Hashing Ring A B C D k1 → B k2 → C k3 → D k4 → A E new shard Hash Ring keys → nearest shard clockwise Adding Shard E: Only k1 moves to E k2, k3, k4 stay on their shards Naive hash % N: ~80% of keys must migrate to new shards
الهاش المتسق يضع الأجزاء على حلقة افتراضية؛ إضافة جزء جديد لا تُهجّر إلا المفاتيح القريبة منه، مما يتجنب هجرة البيانات الكاملة.

إعادة التجزئة وتغييرات المخطط

حتى مع الهاش المتسق، تُعدّ إعادة التجزئة (تغيير عدد الأجزاء أو مفتاح الجزء نفسه) حدثًا تشغيليًا بالغ التعقيد. تشمل:

  1. تشغيل الإعداد القديم والجديد للأجزاء بالتوازي (الكتابة المزدوجة).
  2. ترحيل البيانات الموجودة في الخلفية دون توقف.
  3. التحقق من الاتساق بين الأجزاء القديمة والجديدة.
  4. تحويل حركة القراءة إلى الإعداد الجديد بشكل ذري.

منصات مثل Vitess (تستخدمها YouTube) وCitus (امتداد تجزئة PostgreSQL) تُؤتمت كثيرًا من هذه العملية. لكن الدرس الجوهري هو: اختر مفتاح الجزء بعناية شديدة في المرة الأولى. تغييره لاحقًا من أكثر العمليات تكلفةً في تصميم الأنظمة.

ما لا تستطيع التجزئة إصلاحه

التجزئة تُساعد في معدل الكتابة وحجم البيانات، لكنها لا تحل كل المشكلات تلقائيًا:

  • المعاملات عبر الأجزاء: ضمانات ACID عبر الأجزاء تستلزم معاملات موزعة (Two-Phase Commit) وهي بطيئة ومعقدة. معظم الأنظمة المُجزَّأة تُتبنى الاتساق النهائي (Eventual Consistency) للعمليات عبر الأجزاء.
  • التجميعات العالمية: COUNT(*) أو SUM أو AVG على مجموعة البيانات بالكامل تستلزم الحساب على كل جزء والدمج. لتحليلات على نطاق واسع، قاعدة بيانات تحليلية منفصلة (مستودع بيانات) عادةً أنسب من الاستعلام المباشر على الأجزاء.
  • الانضمامات بين الكيانات: إذا كانت الطلبات والعملاء على أجزاء مختلفة، فالانضمام مكلف. الإقامة المشتركة هي الحل لكنها ليست ممكنة دائمًا.
أرجئ التجزئة أطول وقت ممكن. التعقيد المضاف ضخم: تفقد الانضمامات السهلة والمعاملات الذرية وعمليات تغيير المخطط البسيطة. كثير من الأنظمة التي تصل إلى 500 جيجابايت–1 تيرابايت من البيانات يمكنها استخدام توليفة من نسخ القراءة والكاش والتوسع الرأسي للأساسي لسنوات قبل أن تصبح التجزئة ضرورية حقًا. أجّل التجزئة حتى تُستنزف البدائل.

الخلاصة الجوهرية

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