قواعد البيانات في الإنتاج

توسيع نطاق قواعد البيانات

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

توسيع نطاق قواعد البيانات

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

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

المستوى الأول — التوسع الرأسي: أسرع رافعة لديك

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

ما يمنحك إياه التوسع الرأسي فعلًا:

  • الذاكرة العشوائية: المورد الأكثر تأثيرًا لمعظم أعباء OLTP. تُخزّن الذاكرة المؤقتة shared_buffers في PostgreSQL وinnodb_buffer_pool_size في MySQL صفحات البيانات الساخنة. حين يسع مجموعة العمل في الذاكرة، تنخفض قراءات الأقراص إلى ما يقارب الصفر. قاعدة بيانات على مضيف بـ 128 جيجابايت من الذاكرة كثيرًا ما تؤدي بـ 5–10 أضعاف مقارنةً بمضيف بـ 32 جيجابايت يشغّل الاستعلامات ذاتها.
  • المعالج: مهم للاستعلامات التحليلية المعقدة، ومعالجة الاتصالات، وعمليات الفرز والتجميع. مضاعفة نوى المعالج نادرًا ما تضاعف الإنتاجية لأعباء OLTP الكثيفة بالكتابة — الاختناق عادةً هو I/O أو تنازع الأقفال، لا الحوسبة.
  • الأقراص: التبديل من HDD الدوّار إلى SSD بـ NVMe كثيرًا ما يحقق تحسينًا بمقدار 10–50 ضعفًا في زمن استجابة القراءة/الكتابة العشوائية. إذا كان مضيفك لا يزال يستخدم EBS gp2 على AWS، فإن الترقية إلى gp3 أو io2 مع IOPS مزوّدة هي تغيير بدون توقف يلغي انتظار I/O في أغلب الأحيان.
تحقق من معدل الإصابة بالمخزن المؤقت أولًا قبل أي شيء. إذا أظهر PostgreSQL معدل إصابة بالمخزن المؤقت أقل من 95% أو أظهر MySQL قيمة Innodb_buffer_pool_reads ترتفع بسرعة أكبر من Innodb_buffer_pool_read_requests، فأنت مقيّد بالأقراص، وزيادة الذاكرة ستحل المشكلة قبل أي تغيير معماري. شغّل هذا على PostgreSQL لترى النتيجة فورًا:
-- PostgreSQL: معدل إصابة ذاكرة التخزين المؤقت لكل قاعدة بيانات SELECT datname, blks_hit, blks_read, round(blks_hit::numeric / nullif(blks_hit + blks_read, 0) * 100, 2) AS hit_rate_pct FROM pg_stat_database WHERE datname NOT IN ('template0','template1') ORDER BY hit_rate_pct ASC; -- MySQL/MariaDB: معدل إصابة مجمّع InnoDB buffer pool SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%'; -- معدل الإصابة ≈ 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) -- أي قيمة أقل من 0.99 (99%) تشير إلى أن حجم مجمّع المخزن المؤقت صغير جدًا

السقف الحقيقي للتوسع الرأسي ليس حجم النسخة — بل منحنى التكلفة وخطر نقطة الفشل المفردة. آلة بـ 192 نواة و24 تيرابايت من الذاكرة موجودة في السحابات الكبرى، لكنها تكلف أكثر من 40,000 دولار شهريًا. والأهم عمليًا، العقدة الواحدة تعني نطاق فشل واحد. مزيج تكلفة السقف ومتطلبات التوافر العالي هو ما يجبرك فعلًا على الانتقال إلى المستوى التالي.

المستوى الثاني — توسعة القراءة: إضافة نسخ متماثلة للقراءة

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

أنماط الإنتاج الاحترافية تختلف عن الأمثلة التعليمية البسيطة:

  • تأخر النسخة خطر حقيقي. تحت حمل الكتابة الثقيل، قد تتأخر النسخ عن العقدة الأساسية بثوانٍ أو أكثر. قراءة البيانات القديمة بعد كتابة تُسمى تناقض اتساق القراءة الذاتية — وهي نوع من الأخطاء التي يصعب للغاية تشخيصها في الإنتاج. وجّه القراءات الحرجة للجلسة (إعدادات الملف الشخصي، حالة الدفع) إلى العقدة الأساسية، ووجّه التحليلات ولوحات التحكم والبحث فقط إلى النسخ.
  • استخدم تجمّعات اتصال منفصلة لكل نقطة نهاية. يجب أن يُجمّع PgBouncer أو ProxySQL اتصالات العقدة الأساسية بشكل منفصل عن اتصالات تجمّع النسخ. لا تخلطهما في تجمّع واحد أبدًا.
  • راقب تأخر النسخة كمؤشر SLO من الدرجة الأولى. أنشئ تنبيهًا حين يتجاوز التأخر حد التحمل لديك (عادةً 1–5 ثوانٍ لتطبيقات الويب، وميلي ثانية للبيانات المالية). في PostgreSQL هذا عبر pg_stat_replication؛ وفي MySQL عبر حقل Seconds_Behind_Source في SHOW REPLICA STATUS.
  • نسخ القراءة لا تقلل حمل الكتابة على العقدة الأساسية. إذا كانت اختناقك في إنتاجية الكتابة، فالنسخ لا تحل شيئًا — أنت عند نقطة قرار التجزئة.
Read replica routing — writes to primary, reads distributed across replicas Application ProxySQL / PgBouncer Primary Reads + Writes Replica 1 Reads only Replica 2 Reads only writes reads reads WAL stream Monitor replica lag! pg_stat_replication / Seconds_Behind_Source
توجيه النسخة للقراءة: يوجّه البروكسي عمليات الكتابة إلى العقدة الأساسية ويوزّع القراءات على النسخ. يجب مراقبة تأخر النسخة كإشارة من الدرجة الأولى.

المستوى الثالث — التجزئة: حين تحتاجها فعلًا

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

علامات أنك وصلت فعلًا إلى عتبة التجزئة:

  • إنتاجية كتابة تتجاوز أقصى ما يمكن للنسخة الأكبر المتاحة توفيره (عادةً أكثر من 50,000 كتابة/ثانية لـ MySQL، وأكثر من 100,000 TPS لـ PostgreSQL على NVMe)
  • مجموعة بيانات أكبر من الحد الأقصى لتخزين عقدة واحدة (مئات التيرابايت)
  • متطلبات قانونية أو امتثالية لتقسيم البيانات حسب الجغرافيا أو المستأجر

بدائل يجب التحقق منها قبل التجزئة: تقسيم الجداول (مدمج في PostgreSQL وMySQL، دون تغييرات في التطبيق، يعالج مجموعات بيانات تصل إلى عشرات التيرابايت بكفاءة)، وCQRS مع نموذج قراءة منفصل (Elasticsearch، Redshift للقراءة فقط)، ونقل بيانات السلاسل الزمنية إلى مخزن متخصص (TimescaleDB, InfluxDB)، وأرشفة الصفوف الباردة في تخزين الكائنات (AWS S3 + Athena).

استراتيجيات التجزئة

التجزئة بالنطاق تُعيّن الصفوف إلى أجزاء بناءً على نطاق متواصل من مفتاح التجزئة (مثلاً معرفات المستخدمين من 1 إلى 10 ملايين على الجزء 1، ومن 10 إلى 20 مليون على الجزء 2). سهل الفهم؛ فظيع للنقاط الساخنة — إذا كان أحدث مستخدميك هم الأكثر نشاطًا، فإن كل حركة البيانات تضرب الجزء ذا النطاق الأعلى.

التجزئة بالتجزئة الرياضية تطبق دالة تجزئة على مفتاح التجزئة وتعيّن الصف إلى الجزء hash(key) % N. توزيع متساوٍ؛ إعادة التجزئة حين تضيف عقدًا تتطلب نقل بيانات (التجزئة المتسقة تقلل هذا لكن لا تلغيه).

التجزئة بالدليل تحتفظ بجدول بحث صريح يعيّن كل مفتاح إلى جزء. أقصى مرونة؛ يصبح جدول البحث نقطة فشل مفردة واختناق للكتابة إن لم يُدار بعناية.

مشكلة الاستعلامات عبر الأجزاء دائمة. بمجرد التجزئة، أي استعلام يمتد عبر أجزاء متعددة (JOINs والتجميعات والمعاملات) يتطلب منطق scatter-gather في طبقة التطبيق أو طبقة وسيطة (Vitess, Citus). لا يوجد محرك SQL يتعامل مع هذا بشفافية بدون مقايضات. العمليات التي تأخذ عبارة SQL واحدة على عقدة مفردة قد تتطلب عشرات الرحلات الذهاب والإياب عبر الأجزاء. لا يمكنك إضافة التجزئة لاحقًا والحفاظ على أنماط استعلاماتك — يجب إعادة تصميم التطبيق لها من اليوم الأول.

التجزئة المُدارة: ما تقوم به المنصة عنك

في مقياس الشركات الكبرى، تُعالج التجزئة عادةً بطبقة وسيطة بدلًا من كودك في التطبيق مباشرةً. الخيارات السائدة في 2025:

  • Vitess — طبقة تجزئة MySQL تستخدمها YouTube وSlack وPlanetScale. تتعامل مع إعادة التجزئة والاستعلامات عبر الأجزاء وتجميع الاتصالات بشفافية. تعقيد تشغيلي مرتفع.
  • Citus (PostgreSQL) — امتداد PostgreSQL موزّع أصيل. يوزّع الجداول عبر عقد العمال؛ شفاف لمعظم SQL. يناسب بشكل جيد SaaS متعدد المستأجرين والتحليلات.
  • CockroachDB / YugabyteDB / Spanner — محركات SQL موزّعة تتعامل مع التجزئة داخليًا. تكتب SQL قياسيًا؛ يتعامل المحرك مع التجزئة والنسخ المتماثل والفشل التلقائي. المقايضة: زمن استجابة أعلى (توافق موزع) وتكلفة أعلى من Postgres على عقدة واحدة.
-- Citus: توزيع جدول موجود بواسطة tenant_id (شغّل على عقدة المنسق) -- الخطوة 1: إضافة امتداد Citus CREATE EXTENSION citus; -- الخطوة 2: توزيع الجدول — جميع الاستعلامات ذات tenant_id تبقى على جزء واحد SELECT create_distributed_table('orders', 'tenant_id'); -- الخطوة 3: مشاركة موقع جدول مرتبط حتى تكون JOINs بينهما محلية SELECT create_distributed_table('order_items', 'tenant_id', colocate_with => 'orders'); -- التحقق من مواضع الأجزاء SELECT shardid, nodename, nodeport FROM pg_dist_shard_placement JOIN pg_dist_shard USING (shardid) WHERE logicalrelid = 'orders'::regclass LIMIT 10;

إطار القرار: أي مستوى، الآن؟

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

  1. هل معدل إصابة المخزن المؤقت أقل من 95%؟ → أضف ذاكرة عشوائية أولًا.
  2. هل العقدة الأساسية مثقلة على المعالج بسبب القراءات، لا الكتابات؟ → أضف نسخ قراءة متماثلة.
  3. هل إنتاجية الكتابة مشبعة؟ → تحقق من تقسيم الجداول وCQRS والأرشفة قبل التجزئة.
  4. هل قمت بتحليل الاستعلامات البطيئة والتخلص منها؟ فهرسة مفقودة تُصلح في خمس دقائق حلّت حوادث كانت تُشخَّص على أنها مشاكل سعة.
  5. فقط بعد كل ما سبق → قيّم التجزئة مع Vitess أو Citus أو محرك SQL موزّع.
واقع الشركات الكبرى: معظم الشركات التي تعتقد أنها تحتاج إلى التجزئة تحتاج في الواقع إلى فهرسة أفضل، أو نسخة أكبر، أو نسخ قراءة متماثلة. التجزئة هي الإجابة الصحيحة في مقياس لن تصله معظم المنتجات أبدًا. الشركات التي جزّأت قواعد بيانات إنتاجية على نطاق واسع (Google وMeta وStripe) لديها فرق مهندسي بنية تحتية متخصصة لإدارة التكلفة التشغيلية المستمرة. قِس طموحك على حجم فريقك.