Software Engineering5 مارس 20268 min read

أنماط توسيع قواعد البيانات قبل اللجوء إلى التجزئة (Sharding)

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

بقلم Innovation T Team


كثيراً ما يُتحدث عن التجزئة (Sharding) وكأنها طقس عبور، أي اللحظة التي تصبح فيها قاعدة البيانات ناضجة أخيراً. أما في الواقع فهي واحدة من أكثر القرارات كلفة وأقلها قابلية للتراجع، ومعظم الفرق تلجأ إليها قبل سنوات من حاجتها الفعلية لها. هذا هو الترتيب الذي نتّبعه عندما تبدأ قاعدة البيانات في التعثّر، وسبب كون كل درجة تكسبك وقتاً قبل أن تلتزم بالخيار الصعب.

لماذا التجزئة هي الملاذ الأخير

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

بمجرد أن تعيش البيانات على أجزاء (shards) منفصلة، تتوقف عمليات الربط عبر الأجزاء (cross-shard joins) عن كونها مجانية، وتحتاج المعاملات التي تمتد عبر عدة أجزاء إلى تنسيق على مستوى التطبيق، وعلى كل استعلام أن يعرف أي جزء يخاطب. إعادة موازنة جزء مثقل بالحِمل تصبح مشروعاً كاملاً، لا مجرد تغيير في الإعدادات. والتحليلات التي كانت تُنجَز باستعلام واحد تصبح عملية بث وتجميع (scatter-gather) عبر العُقد. من واقع خبرتنا، تقضي الفرق التي تلجأ إلى التجزئة مبكراً وقتاً هندسياً في الالتفاف حول طوبولوجيتها الخاصة أكثر مما أنفقته يوماً على مشكلة الأداء التي دفعتها إلى ذلك.

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

قِس قبل أن تتحرك

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

اجمع الحد الأدنى التالي:

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

إذا لم تكن قادراً على رؤية هذه الأرقام اليوم، فتلك هي مهمتك الأولى. لا يمكنك توسيع ما لا يمكنك قياسه، وفي نصف الحالات تكشف البيانات عن حل يكلّف بعد ظهيرة واحدة بدلاً من ربع سنة كامل.

سُلّم التوسيع

هذا هو الترتيب الذي نصعد عبره، من الأرخص والأكثر أماناً إلى الأكثر تدخّلاً. تقدّم من الأعلى إلى الأسفل وتوقّف حالما تعود أرقامك إلى العافية.

  1. أصلِح الاستعلامات والفهارس.
  2. اضبط حجم اتصالاتك وجمّعها بالشكل الصحيح.
  3. أضِف التخزين المؤقت حيث يسمح نمط القراءة بذلك.
  4. حوّل القراءات إلى النسخ المتماثلة.
  5. وسّع القاعدة الأساسية عمودياً.
  6. قسّم الجداول الكبيرة داخل قاعدة بيانات واحدة.
  7. افصل حسب المجال (مخازن منفصلة أو CQRS).
  8. عندئذ فقط، لجّئ إلى التجزئة.

1. الاستعلامات والفهارس أولاً

العمل الأعلى مردوداً لا يكون معمارياً في الغالب. إنه فهرس مفقود على عمود في WHERE أو JOIN، أو استعلام من نوع N+1 يُطلَق مئات المرات في كل طلب، أو SELECT * يسحب صفوفاً عريضة بينما تكفي ثلاثة أعمدة.

نفّذ EXPLAIN ANALYZE (في Postgres) أو ما يعادله على أبطأ استعلاماتك. ابحث عن عمليات المسح المتسلسل (sequential scans) على الجداول الكبيرة، وعمليات الفرز التي تفيض إلى القرص، والحلقات المتداخلة على أعداد كبيرة من الصفوف. أضِف فهارس تغطية (covering indexes) لتجيب القاعدة مباشرة من الفهرس. لكن احذر من الإفراط في الفهرسة: فكل فهرس تضيفه يبطّئ عمليات الكتابة ويستهلك الذاكرة، لذا افهرس للاستعلامات التي تنفّذها فعلاً، ثم احذف تلك التي لا تُستخدم أبداً.

تخفض هذه الدرجة بانتظام زمن الاستجابة p99 بهامش كبير مقابل كلفة بنية تحتية تقارب الصفر. افعلها قبل أي شيء آخر.

2. تجميع الاتصالات وحدودها

تتعامل قواعد البيانات مع عدد من الاتصالات المتزامنة أقل بكثير مما يفترضه معظم الناس. فآلة Postgres مرتاحة عند 100 اتصال نشط قد تنهار عند 500، لأن كل اتصال يحمل عبئاً حقيقياً على الذاكرة والجدولة. وطبقات التطبيقات من نوع serverless والتوسّع التلقائي (autoscaling) تزيد الأمر سوءاً: فكل نسخة جديدة تفتح تجمّعها الخاص، وينفجر عدد الاتصالات.

ضع أداة تجميع (pooler) في المقدمة (PgBouncer لـ Postgres، وProxySQL لـ MySQL، أو المكافئ المُدار من مزوّد السحابة لديك). استخدم التجميع على مستوى المعاملات لتُشارَك الاتصالات بشكل مكثّف. حدّد سقف حجم التجمّع عن قصد، واضبط حجم تجمّعات طبقة التطبيق مع مراعاة الحد الفعلي لقاعدة البيانات، لا العدد الافتراضي لإطار عمل تطبيقك.

3. التخزين المؤقت حيث تتكرر القراءات

إذا كانت البيانات نفسها تُقرأ أكثر بكثير مما تتغيّر، فإن وضع طبقة تخزين مؤقت (cache) أمام القاعدة يمتص الحِمل. Redis أو Memcached لعمليات البحث المتكررة عن أزواج المفتاح والقيمة، والتخزين المؤقت عبر HTTP وشبكات توصيل المحتوى (CDN) لكل ما يمكن تقديمه بقدر يسير من التقادم، وطرق العرض المُجسَّدة (materialized views) للتجميعات المكلفة التي لا تحتاج إلى أن تكون في الزمن الحقيقي.

الجزء الصعب في التخزين المؤقت هو الإبطال (invalidation)، لذا كن صريحاً بشأن استراتيجيتك: انتهاء صلاحية قائم على الزمن للبيانات التي يمكن أن تكون متقادمة قليلاً، وإبطال قائم على الأحداث للبيانات التي يجب أن تكون طازجة عند الكتابة. خزّن مؤقتاً القراءات المكلفة، لا كل شيء، وقِس دائماً معدل الإصابة (hit rate). فالتخزين المؤقت الذي يعمل بمعدل إصابة 40% كثيراً ما يضيف زمن استجابة بدل أن يزيله.

4. النسخ المتماثلة للقراءة

عندما تهيمن القراءات وتكون قد خزّنت مؤقتاً المكاسب الواضحة، اِنسَخ. تتولى القاعدة الأساسية عمليات الكتابة، وتتولى نسخة متماثلة أو أكثر للقراءة (read replicas) الاستعلامات، ويوجّه التطبيق الطلبات وفقاً لذلك. قواعد البيانات المُدارة في السحابة تجعل هذا قريباً من مجرد خانة اختيار.

المقايضة هي تأخّر النسخ المتماثل (replication lag). فالنسخة المتماثلة متسقة في نهاية المطاف (eventually consistent)، لذا قد يقرأ مستخدم حفظ تغييراً للتو نسخة متماثلة لم تُدرِك بعد فيرى بيانات متقادمة. وجّه إلى القاعدة الأساسية القراءات التي يجب أن تكون متسقة فوراً (مستخدم يقرأ نموذجه الذي أرسله لتوّه)، وأرسِل إلى النسخ المتماثلة كل ما يحتمل تأخيراً بسيطاً (لوحات المعلومات، والقوائم، والبحث). واستعلامات إعداد التقارير والتحليلات على وجه الخصوص مكانها نسخة متماثلة كي لا تتنازع أبداً مع عمليات الكتابة في الإنتاج.

5. التوسيع العمودي

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

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

6. التقسيم داخل قاعدة بيانات واحدة

يقسّم التقسيم (partitioning) جدولاً كبيراً إلى قطع مادية أصغر مع الإبقاء عليه جدولاً منطقياً واحداً والبقاء داخل قاعدة بيانات واحدة. هذا ليس تجزئة: لا يوجد تنسيق موزّع، وتكاد استعلاماتك لا تتغيّر.

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

7. الفصل حسب المجال

قبل أن تقسّم جدولاً واحداً عبر آلات عديدة، فكّر في تقسيم مخطّطك (schema) عبر عدد قليل منها. فإذا كانت الفوترة والتحليلات والمنتج الأساسي جميعها تعيش في قاعدة بيانات واحدة وتتنافس على الموارد نفسها، فإن فصلها حسب السياق المحدود (bounded context) يمنح كلاً منها مساحة للتنفّس والتوسّع وفق شروطه الخاصة.

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

متى تكون التجزئة منطقية فعلاً

أحياناً تحتاج إليها حقاً. الإشارات الصادقة:

  • نما جدول واحد إلى ما يتجاوز ما يمكن لعُقدة واحدة احتواؤه أو فهرسته بكفاءة، حتى بعد التقسيم.
  • إنتاجية الكتابة على القاعدة الأساسية هي عنق الزجاجة، والنسخ المتماثلة لا تساعد لأنها لا توسّع سوى القراءات.
  • لديك مفتاح تجزئة طبيعي ومستقر (معرّف المستأجر، معرّف المستخدم، المنطقة) يُبقي جميع الاستعلامات تقريباً داخل جزء واحد.
  • صعدت بالفعل كل الدرجات أعلاه ولا يزال أمامك جدار.

إذا كان هذا حالك، فلجّئ إلى التجزئة عن قصد: اختر مفتاح تجزئة يطابق أنماط وصولك، وخطّط لإعادة الموازنة منذ اليوم الأول، واعتمد على نظام يتولى التوزيع نيابةً عنك (Citus أو Vitess أو قاعدة بيانات موزّعة أصلاً) بدلاً من كتابة التوجيه يدوياً في شيفرة التطبيق.

قائمة تحقّق ما قبل التجزئة

راجِع هذه القائمة قبل أن يفتح أي أحد مستند تصميم للتجزئة:

  1. هل أبطأ 20 استعلاماً مفهرسة وخالية من عمليات المسح المتسلسل على الجداول الكبيرة؟
  2. هل جرى القضاء على أنماط N+1 في أكثر نقاط النهاية ازدحاماً؟
  3. هل توجد أداة تجميع اتصالات (pooler) بأحجام تجمّع مقصودة؟
  4. هل تُخزَّن القراءات المتكررة مؤقتاً، بمعدل إصابة قِسته فعلاً؟
  5. هل نُقلت الاستعلامات كثيفة القراءة واستعلامات التقارير إلى النسخ المتماثلة؟
  6. هل جُرّب التوسيع العمودي أو حُسبت كلفته على الأقل؟
  7. هل الجداول الكبيرة للسلاسل الزمنية أو كثيفة الإلحاق مقسّمة؟
  8. هل فصلت المجالات غير المترابطة إلى مخازنها الخاصة؟

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

كيف يمكن لـ Innovation T المساعدة

معظم آلام قواعد البيانات التي نُستدعى لإصلاحها ليست مشكلة سعة، بل هي مشكلة تجهيز وقياس واستعلامات مرتدية ثوب مشكلة سعة. في Innovation T، تبدأ فرقنا لهندسة البرمجيات والسحابة من حيث تشير الأرقام: نحلّل ملف حِمل العمل الفعلي (profiling)، ونصلح الاستعلامات والفهارس التي تؤلم فعلاً، ثم نصعد السُّلّم (التجميع، والتخزين المؤقت، والنسخ المتماثلة، والتقسيم) بحيث لا تضيف تعقيداً إلا حيث يستحقّه. وحين تكون التجزئة أو قاعدة بيانات موزّعة هي الجواب حقاً، نصمّم معك مفتاح التجزئة ومسار الترحيل بحيث يصمدان سنوات، لا أشهراً.

إذا بدأت قاعدة بياناتك تبدو كأنها السقف، فاطّلع على خدماتنا أو تواصل معنا. فنحن نفضّل مساعدتك على تجنّب جزء (shard) على أن ننظّف بعد جزء نُفِّذ على عجل.

#قاعدة البيانات#التوسيع#الأداء#المعمارية

جاهز للبناء مع Innovation T؟

سواء كان الأمر يتعلق بالأمن أو النمو أو الهندسة، يمكن لفريقنا مساعدتك على تنفيذه بإتقان.