من مئة مليون إلى مليار سجل: دليلك العملي لتقسيم قواعد البيانات (Sharding) مع أبو عمر

يا جماعة الخير، السلام عليكم ورحمة الله.

بتذكر قبل كم سنة، كنا شغالين على تطبيق اجتماعي جديد، والأمور كانت “عال العال”. التطبيق انطلق، والناس حبته، وبدأ عدد المستخدمين يزيد بشكل جنوني. في البداية كنا مبسوطين وفرحانين، كل يوم نفتح لوحة التحكم ونلاقي آلاف المستخدمين الجداد. لكن الفرحة هاي ما طولت، وبدأت الكوابيس التقنية تظهر.

قاعدة البيانات اللي كانت سريعة زي الصاروخ، صارت “بتجرجر رجليها جر”. الاستعلام البسيط اللي كان ياخذ أجزاء من الثانية، صار بده 3 ثواني، وأحيانًا 5! الفريق كان يشتكي، والمستخدمين بلشوا يزهقوا من بطء التطبيق. وصلنا لمرحلة إنه مجرد إضافة عمود جديد على جدول المستخدمين صار مشروع بحد ذاته، وبده توقيف للنظام (Downtime) لساعات. قعدت مع حالي وحكيت: “يا أبو عمر، إحنا هيك بنغرق. السفينة بتتسع، بس المحرك هو هو، ومش راح يقدر يكمل”.

هون كان لازم ناخذ قرار مصيري. ومن هاي التجربة، بدي أحكيلكم اليوم عن الحل اللي أنقذنا، وهو الـ “Database Sharding”.

المشكلة: وحش المئة مليون سجل

لما قاعدة بياناتك تكبر وتوصل لمئات الملايين من السجلات (Rows)، بتتحول من صديقك الوفي إلى عدوك اللدود. المشاكل اللي واجهتنا كانت كالتالي:

  • بطء قاتل في الاستعلامات: استعلام بسيط مثل SELECT * FROM users WHERE id = ? صار يأخذ من 2 إلى 5 ثوانٍ. هذا زمن كارثي في عالم الويب.
  • فهارس (Indexes) عملاقة: الفهارس اللي المفروض تسرّع البحث، صارت هي نفسها مشكلة. حجمها وصل 10-20 جيجابايت، وكانت تلتهم الذاكرة (RAM) بشكل مرعب.
  • النسخ الاحتياطي (Backup) صار كابوس: عملية أخذ نسخة احتياطية كاملة كانت تستغرق ساعات طويلة، وهذا بيزيد من مخاطر فقدان البيانات في حال حدوث كارثة.
  • صعوبة التعديل: أي تعديل على بنية الجدول (Schema)، مثل إضافة عمود، كان يتطلب قفل الجدول (Table Lock) لساعات، يعني توقف كامل للخدمة.
  • لا يمكن التوسع أفقيًا (Horizontal Scaling): كنا محبوسين في خادم واحد، وكل ما زاد الضغط، ما كان عنا حل إلا نزيد قوة نفس الخادم.

الحل التقليدي (والمحدود): التوسع الرأسي (Vertical Scaling)

أول إشي بيخطر في بال أي حدا: “بسيطة، بنجيب سيرفر أقوى!”. وهذا ما يسمى بالتوسع الرأسي. بتشتري معالج (CPU) أسرع، وذاكرة (RAM) أكبر، وقرص تخزين (SSD) أسرع.

لكن هذا الحل مثل المسكّن المؤقت، وله حدود واضحة:

  • له حدود فيزيائية: ما بتقدر تضل تكبر السيرفر للأبد. بالنهاية راح توصل لأقوى خادم موجود في السوق.
  • مكلف جدًا: سعر الخوادم القوية يرتفع بشكل أُسّي. خادم “خارق” ممكن يكلفك ثروة صغيرة.

لما وصلنا لهي الحدود، كان لازم نفكر “خارج الصندوق”، أو بالأحرى، “خارج الخادم الواحد”.

مفهوم الـ Sharding: “فرّق تَسُد” في عالم البيانات

الـ Sharding ببساطة هو عملية تقسيم قاعدة بياناتك الضخمة إلى أجزاء أصغر وأكثر قابلية للإدارة، بنسمي كل جزء منها “شارد” (Shard). كل شارد هو عبارة عن قاعدة بيانات مستقلة بذاتها، موجودة على خادم منفصل.

بدلًا من هذا الوضع:

خادم واحد:
جدول المستخدمين: 1 إلى 100,000,000 سجل

ننتقل إلى هذا التصميم:

خادم 1 (Shard 1): جدول المستخدمين من 1 إلى 25,000,000
خادم 2 (Shard 2): جدول المستخدمين من 25,000,001 إلى 50,000,000
خادم 3 (Shard 3): جدول المستخدمين من 50,000,001 إلى 75,000,000
خادم 4 (Shard 4): جدول المستخدمين من 75,000,001 إلى 100,000,000

النتيجة فورية ومبهرة:

  • كل خادم يتعامل مع 25 مليون سجل فقط، وهذا يجعله أسرع بكثير.
  • الاستعلام على شارد واحد صار أسرع بـ 4 مرات (نظريًا).
  • إذا احتجت تتوسع بالمستقبل، ببساطة بتضيف خادم خامس (Shard 5) ليحتوي السجلات من 100 مليون إلى 125 مليون.

استراتيجيات الـ Sharding (كيف نختار طريقة التقسيم؟)

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

1. التقسيم حسب النطاق (Range-Based Sharding)

هاي أبسط طريقة. بنقسم البيانات بناءً على نطاق معين من القيم في “مفتاح التقسيم” (Shard Key)، مثل ID المستخدم أو تاريخ الإنشاء.

مثال حسب ID المستخدم:

  • Shard 1: للمستخدمين أصحاب ID من 1 إلى 10,000,000
  • Shard 2: للمستخدمين أصحاب ID من 10,000,001 إلى 20,000,000
  • وهكذا…

مثال حسب التاريخ:

  • Shard 1: بيانات سنة 2021
  • Shard 2: بيانات سنة 2022
  • Shard 3: بيانات سنة 2023

المميزات: بسيطة جدًا وسهلة الفهم والتطبيق.

العيوب: ممكن تؤدي إلى توزيع غير متساوٍ للبيانات والضغط. تخيل لو تطبيقك بيسجل مستخدمين جدد بكثرة، كل الضغط راح يكون على آخر شارد (Hotspot)، بينما الشاردات القديمة عليها ضغط قليل.

2. التقسيم حسب دالة التجزئة (Hash-Based Sharding)

هون بنستخدم دالة رياضية (Hash Function) على مفتاح التقسيم. ناتج الدالة بحدد الشارد اللي لازم نخزن فيه البيانات.

المعادلة بسيطة: رقم الشارد = hash(shard_key) % عدد الشاردات

مثال: لو عنا 10 شاردات، ومستخدم جديد الـ ID تبعه 12345.

  1. نحسب الـ hash لـ 12345، وليكن الناتج 4829302.
  2. نحسب باقي القسمة على عدد الشاردات: 4829302 % 10 = 2.
  3. إذًا، بيانات هذا المستخدم تُخزّن في الشارد رقم 2.

المميزات: تضمن توزيعًا عشوائيًا ومتساويًا للبيانات على كل الشاردات، وهذا بيمنع مشكلة الـ Hotspots.

العيوب: إذا قررت تضيف شارد جديد (مثلًا صاروا 11 بدل 10)، معادلة باقي القسمة بتتغير (% 11 بدل % 10)، وهذا يعني إنه معظم بياناتك راح تحتاج إعادة توزيع على الشاردات الجديدة، وهي عملية معقدة جدًا تُعرف بـ Re-sharding. (ملاحظة: فيه حلول متقدمة لهي المشكلة مثل Consistent Hashing).

3. التقسيم المعتمد على دليل (Directory-Based Sharding)

في هاي الاستراتيجية، بنعمل جدول مركزي صغير (Lookup Table أو Directory) بيشتغل زي “دليل الهاتف”. هذا الجدول بيحتوي على معلومتين: مفتاح التقسيم (مثل user_id) ورقم الشارد اللي موجودة فيه بياناته.

مثال للدليل:

+---------+--------------+
| user_id | shard_number |
+---------+--------------+
| 101     | 1            |
| 102     | 3            |
| 103     | 2            |
| 104     | 1            |
+---------+--------------+

كيف بتشتغل عملية البحث؟

  1. التطبيق يسأل الدليل: “وين بيانات المستخدم 102؟”
  2. الدليل يجيب: “موجودة في شارد رقم 3”.
  3. التطبيق يتوجه مباشرة إلى الشارد رقم 3 ويجلب البيانات.

المميزات: مرونة عالية جدًا. بتقدر تنقل مستخدم من شارد لآخر بسهولة بمجرد تحديث السجل تبعه في الدليل. إضافة شارد جديد سهل أيضًا.

العيوب: الدليل نفسه ممكن يصير نقطة ضعف (Single Point of Failure) أو عنق زجاجة (Bottleneck) لأنه كل عملية قراءة أو كتابة لازم تمر من خلاله أولًا.

مثال عملي: تطبيق اجتماعي بـ 500 مليون مستخدم

لنفترض أننا نبني تطبيقًا اجتماعيًا ونستخدم استراتيجية Hash-Based Sharding مع 5 شاردات.

عملية الكتابة (إضافة مستخدم جديد)

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

// دالة وهمية لحساب الـ hash
function hash(str) {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    hash |= 0; // Convert to 32bit integer
  }
  return Math.abs(hash);
}

// دالة كتابة بيانات المستخدم
function writeUser(user) {
  const totalShards = 5;
  // 1. تحديد الشارد المستهدف
  const shardId = hash(user.id.toString()) % totalShards; // الناتج سيكون بين 0 و 4

  // 2. الحصول على الاتصال بقاعدة البيانات الخاصة بالشارد
  const targetDbConnection = shards[shardId].database; // shards هو مصفوفة الاتصالات
  
  // 3. كتابة البيانات في الشارد الصحيح
  targetDbConnection.insert('users', user);
  console.log(`تمت كتابة المستخدم ${user.id} في الشارد رقم ${shardId}`);
}

عملية القراءة (جلب بيانات مستخدم)

عملية القراءة مشابهة جدًا، نعرف الشارد أولًا ثم نطلب منه البيانات.

function getUser(userId) {
  const totalShards = 5;
  // 1. تحديد الشارد الذي يحتوي على المستخدم
  const shardId = hash(userId.toString()) % totalShards;

  // 2. الحصول على الاتصال بقاعدة البيانات
  const targetDbConnection = shards[shardId].database;

  // 3. جلب البيانات من الشارد الصحيح
  return targetDbConnection.findById('users', userId);
}

عملية البحث الشامل (Scatter-Gather)

ماذا لو أردنا البحث عن كل المستخدمين الذين يسكنون في “القدس”؟ هذه المعلومة ليست في مفتاح التقسيم، لذلك لا نعرف في أي شارد هم موجودون. هنا نستخدم نمط “انشر واجمع” (Scatter-Gather).

  1. Scatter (النشر): نرسل نفس الاستعلام لكل الشاردات الخمسة في نفس الوقت (بشكل متوازٍ).
  2. Gather (الجمع): ننتظر الرد من كل الشاردات، ثم نجمع النتائج معًا في قائمة واحدة ونرجعها للمستخدم.
async function findUsersByCity(city) {
  const filter = { city: city };
  
  // Scatter: إرسال الاستعلام لكل الشاردات بالتوازي
  const promises = shards.map(shard => 
    shard.database.find('users', filter)
  );
  
  // Gather: انتظار كل النتائج
  const resultsFromAllShards = await Promise.all(promises);
  
  // دمج النتائج في مصفوفة واحدة
  return resultsFromAllShards.flat();
}

نصيحة أبو عمر: نمط Scatter-Gather قوي، ولكنه مُكلف. حاول تصميم مفتاح التقسيم (Shard Key) بحيث تكون معظم استعلاماتك موجهة لشارد واحد فقط. هذا هو مفتاح الأداء العالي في الأنظمة المقسمة.

مقارنة الأداء: قبل وبعد الـ Sharding

لنرى الفرق بالأرقام في مثالنا (500 مليون مستخدم):

المقياس قاعدة بيانات واحدة 5 شاردات
حجم البيانات 500 مليون سجل 100 مليون لكل شارد
وقت بحث بسيط 2-5 ثانية 400-1000 ميلي ثانية
استهلاك الذاكرة (RAM) ~100GB ~20GB لكل شارد
حجم الفهرس (Index) ~20GB ~4GB لكل شارد

الخلاصة ونصيحة أخيرة 💡

الـ Sharding ليس حلًا سحريًا، بل هو أداة معمارية قوية جدًا تأتي مع تعقيداتها الخاصة. هو أشبه بنقلة نوعية من إدارة “دكانة صغيرة” إلى إدارة “سوبر ماركت ضخم” له عدة فروع. كل فرع (شارد) أسهل للإدارة من المتجر الكبير الموحد، لكن إدارة كل الفروع معًا تحتاج لتنسيق وتخطيط دقيق.

نصيحتي الأخيرة لك: لا تقفز إلى الـ Sharding إلا عندما تحتاج إليه فعلًا. ابدأ بقاعدة بيانات واحدة، وحسّن أداءها قدر الإمكان (Optimization). عندما تصل إلى مرحلة يصبح فيها التوسع الرأسي (Vertical Scaling) غير كافٍ أو باهظ التكلفة، عندها فقط ابدأ بالتخطيط الجدي لرحلة الـ Sharding.

تذكر دائمًا، “ما تخاف من النمو، بس خطّطله صح!”.

ودمتم بخير يا جماعة.

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

التوظيف وبناء الهوية التقنية

سيرتي الذاتية عبرت فلتر الـ ATS لكنها فشلت أمام المدير التقني: كيف أعدت بناءها لتتحدث لغة المهندسين؟

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

28 فبراير، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

خدمة واحدة فاشلة كادت أن تسقط النظام بأكمله: كيف أنقذني نمط ‘قاطع الدائرة’ (Circuit Breaker) من كارثة متتالية؟

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

27 فبراير، 2026 قراءة المزيد
اختبارات الاداء والجودة

لقد ‘هاجمت’ تطبيقي بنفسي عمداً: كيف كشفت لي ‘هندسة الفوضى’ نقاط الضعف التي لم تظهرها الاختبارات التقليدية

أشارككم قصة حقيقية حول إطلاق فاشل كاد أن يدمر سمعتنا، وكيف قادتنا هذه التجربة المريرة إلى تبني "هندسة الفوضى" (Chaos Engineering). اكتشفوا معنا كيف يمكن...

26 فبراير، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

عاصفة من الطلبات كادت أن تغرق تطبيقي: كيف أنقذتني طوابير الرسائل (Message Queues) من كارثة الجمعة السوداء؟

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

24 فبراير، 2026 قراءة المزيد
البودكاست