قواعد بياناتنا كانت تحتضر: كيف أنقذتنا ‘مرشحات بلوم’ من جحيم الاستعلامات المكلفة؟

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

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

لكن لما كبر النظام وصار عنا ملايين المستخدمين، والآلاف بيحاولوا يسجلوا بنفس الدقيقة، “ولّعت الدنيا”. قاعدة البيانات صارت زي واحد حامل حمل ثقيل وبيمشي بصعوبة. كل عملية كتابة حرف في خانة اسم المستخدم كانت تعني استعلام جديد لقاعدة البيانات. السيرفرات صارت تصرخ، وقاعدة البيانات يا حرام كانت على وشك تلفظ أنفاسها الأخيرة. قضينا أيام وليالي نحاول نعمل “Optimization” ونضيف “Indexes” ونكبر حجم السيرفرات، بس المشكلة كانت أعمق من هيك.

في ليلة من الليالي، وأنا قاعد بقلّب في الأوراق وبشرب فنجان القهوة السادة تبعي، خطرتلي فكرة. لحظة شوي… أغلب الأسماء اللي الناس بتجربها هي أسماء جديدة! يعني 99% من استعلاماتنا لقاعدة البيانات بترجع بنتيجة “لأ، مش موجود”. إحنا بنستهلك أغلى مورد عنا (قاعدة البيانات) عشان نسمع جواب سلبي! يا زلمة، لازم يكون في حل أذكى. وهنا، لمعت في بالي بطلة قصتنا: مرشحات بلوم (Bloom Filters).

ما هي مرشحات بلوم؟ الساحر الذي ينقذ الموقف

ببساطة شديدة، مرشح بلوم هو عبارة عن “هيكل بيانات احتمالي” (Probabilistic Data Structure). انسوا الاسم المعقد شوي، وركزوا معي. وظيفته يجاوب على سؤال واحد فقط: “هل هذا العنصر محتمل أن يكون ضمن مجموعة؟”.

لاحظوا إني حكيت “محتمل”. وهنا سر قوته وضعفه بنفس الوقت.

  • إذا سألت مرشح بلوم عن عنصر معين (مثلاً اسم مستخدم) وأجابك: “قطعاً لا، هذا العنصر غير موجود“، فجوابه صحيح 100%. مستحيل يغلط.
  • أما إذا أجابك: “نعم، محتمل أن يكون هذا العنصر موجوداً“، فهنا يوجد احتمال صغير جداً إنه يكون غلطان. وهذا ما نسميه “الإيجابية الكاذبة” (False Positive).

تخيل معي إنه عندك حارس أمن (بواب) على باب بناية ضخمة. بدل ما نعطيه قائمة بأسماء كل سكان البناية (قاعدة البيانات)، بنعطيه “طبعة إصبع” مشوشة لكل ساكن. لما يجي زائر جديد، الحارس بياخذ طبعة إصبعه وبقارنها مع الطبعات المشوشة اللي عنده. إذا ما طابقت ولا أي وحدة، فهو متأكد 100% إنه هاد الشخص مش من السكان. أما إذا طابقت، بيقوله: “تفضل، ممكن تكون من السكان، روح اسأل الاستقبال للتأكيد”. الاستقبال هنا هو قاعدة البيانات. الحارس (مرشح بلوم) منع 99% من الزوار الغرباء من إزعاج الاستقبال!

كيف تعمل هذه الساحرة الصغيرة؟

الآلية خلف مرشحات بلوم عبقرية في بساطتها. تتكون من مكونين أساسيين:

1. مصفوفة البتات (Bit Array)

تخيلها كشريط طويل جداً من الخانات، وكل خانة فيها إما 0 أو 1. في البداية، كل الخانات بتكون 0.


[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

2. دوال التجزئة (Hash Functions)

مجموعة من الدوال (مثلاً 3 أو 5 دوال) وظيفتها تاخذ أي مدخل (زي اسم المستخدم “abu_omar”) وتحوله لرقم. كل دالة بتعطيك رقم مختلف لنفس المدخل.

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

لنفترض أننا نريد إضافة اسم المستخدم “Sami” إلى مرشح بلوم. نقوم بالخطوات التالية:

  1. نمرر “Sami” على دوال التجزئة الثلاثة.
  2. لنفترض أن النتائج كانت:
    • الدالة 1 أعطت الرقم: 2
    • الدالة 2 أعطت الرقم: 8
    • الدالة 3 أعطت الرقم: 14
  3. نذهب إلى مصفوفة البتات ونغير قيمة الخانات رقم 2 و 8 و 14 إلى 1.

المصفوفة الآن تبدو هكذا:


[0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]

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

الآن، لنفترض أن مستخدماً جديداً يحاول التسجيل باسم “Omar”.

  1. نمرر “Omar” على نفس دوال التجزئة.
  2. لنفترض أن النتائج كانت: 2، 5، 11.
  3. نذهب للمصفوفة ونتحقق من الخانات 2، 5، و 11.
  4. نجد أن الخانة 2 قيمتها 1، ولكن الخانة 5 قيمتها 0.
  5. بما أننا وجدنا خانة واحدة على الأقل قيمتها 0، فإننا متأكدون 100% أن “Omar” لم يتم إضافته من قبل. ونقول للمستخدم “الاسم متاح” بدون لمس قاعدة البيانات.

ماذا لو جرب مستخدم آخر التسجيل بنفس الاسم “Sami”؟

  1. نمرر “Sami” على الدوال، وستعطينا نفس الأرقام: 2، 8، 14.
  2. نتحقق من الخانات 2، 8، و 14 في المصفوفة.
  3. سنجد أن كل الخانات الثلاثة قيمتها 1.
  4. هنا، مرشح بلوم سيقول: “محتمل أن يكون هذا الاسم موجوداً“. في هذه الحالة فقط، نسمح للطلب بالوصول إلى قاعدة البيانات للتأكد النهائي.

لعنة الإيجابيات الكاذبة (False Positives): قد يحدث أن يتم إضافة عنصرين مختلفين (مثلاً “Sami” و “Nidal”) ويصادف أن مجموع “بصماتهم” الرقمية تُفعِّل نفس الخانات التي ينتجها عنصر ثالث لم يتم إضافته من قبل (مثلاً “Jamal”). عندها، عند التحقق من “Jamal”، سنجد كل الخانات المطلوبة تساوي 1 بالصدفة، وسيعطينا المرشح “إيجابية كاذبة”. لكن هذا الاحتمال يمكن التحكم به وتقليله بشكل كبير.

هيا بنا نُشَمِّر عن سواعدنا: مثال عملي بلغة Python

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

أولاً، قم بتثبيت المكتبة:

pip install pybloom_live

والآن، لنكتب الكود الذي أنقذ سيرفراتنا.


from pybloom_live import BloomFilter
import time

# الخطوة 1: تهيئة مرشح بلوم وملؤه من قاعدة البيانات
# هذا يتم مرة واحدة عند بدء تشغيل التطبيق أو بشكل دوري

# لنفترض أن هذه هي أسماء المستخدمين الموجودة لدينا في قاعدة البيانات
existing_usernames = ["abu_omar", "sami", "ahmad", "falasteen", "user123", "developer99"]

# نقوم بإنشاء مرشح بلوم
# capacity: عدد العناصر المتوقع تخزينها
# error_rate: نسبة الخطأ (الإيجابيات الكاذبة) التي نتقبلها
# كلما قلت نسبة الخطأ، زاد حجم المرشح في الذاكرة
username_filter = BloomFilter(capacity=1000000, error_rate=0.001)

print("بدء ملء مرشح بلوم من قاعدة البيانات...")
for username in existing_usernames:
    username_filter.add(username)
print("... انتهى الملء. المرشح جاهز للاستخدام.")

# --------------------------------------------------------------------

# الخطوة 2: دالة التحقق التي يستخدمها التطبيق في كل مرة
def is_username_available(username):
    print(f"n--- التحقق من اسم المستخدم: '{username}' ---")
    
    # هنا السحر يحدث!
    if username in username_filter:
        # المرشح يقول: "الاسم قد يكون موجوداً"
        # هذه هي الحالة الوحيدة التي نتصل فيها بقاعدة البيانات
        print(f"'{username}' قد يكون موجوداً (حسب المرشح). سنتحقق من قاعدة البيانات للتأكيد...")
        
        # في تطبيق حقيقي، هنا يتم استدعاء قاعدة البيانات
        # time.sleep(0.5) # محاكاة لتأخير قاعدة البيانات
        if username in existing_usernames:
            print(f"تأكيد قاعدة البيانات: '{username}' موجود بالفعل. ❌")
            return False
        else:
            # هذه هي حالة الإيجابية الكاذبة (False Positive)
            print(f"تأكيد قاعدة البيانات: '{username}' غير موجود. كانت إيجابية كاذبة. ✅")
            return True
    else:
        # المرشح يقول: "الاسم غير موجود قطعاً"
        # لا حاجة للاتصال بقاعدة البيانات على الإطلاق!
        print(f"'{username}' غير موجود قطعاً (حسب المرشح). الاسم متاح! ✅")
        return True

# --- تجربة عملية ---
is_username_available("sami")      # موجود بالفعل
is_username_available("new_user")  # جديد تماماً
is_username_available("ali")       # قد يسبب إيجابية كاذبة بالصدفة

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

نصائح أبو عمر الذهبية 📝

من خلال تجربتي مع مرشحات بلوم وغيرها من الخوارزميات، تعلمت بعض الدروس التي أحب أن أشارككم إياها:

  • اعرف متى تستخدمها: مرشحات بلوم مثالية لعمليات التحقق من الوجود (Existence checks) على مجموعات بيانات ضخمة جداً لا تسع في الذاكرة. أمثلة أخرى: التحقق من أن رابط URL ليس في قائمة الروابط الخبيثة، أو التحقق من أن المستخدم لم ير هذا المقال من قبل لتجنب عرضه مرة أخرى.
  • اعرف متى لا تستخدمها: لا يمكنك حذف عنصر من مرشح بلوم القياسي! إذا حاولت تغيير خانة من 1 إلى 0، قد تتسبب في “إتلاف” بصمة عنصر آخر. (توجد أنواع متقدمة مثل Counting Bloom Filters تسمح بالحذف ولكنها أكثر تعقيداً واستهلاكاً للذاكرة). أيضاً، لا تستخدمها إذا كانت نسبة الخطأ 0% هي مطلب أساسي ولا يمكنك تحمل أي إيجابيات كاذبة.
  • الحجم هو المفتاح: قبل استخدام مرشح بلوم في بيئة الإنتاج، يجب أن تحدد حجمه (m) وعدد دوال التجزئة (k) بعناية. استخدم حاسبات مرشح بلوم المتاحة على الإنترنت. كل ما عليك هو إدخال عدد العناصر المتوقعة (n) ونسبة الخطأ المقبولة (p)، وستعطيك الأرقام المثالية.
  • تذكر الحكمة القديمة: “درهم وقاية خير من قنطار علاج”. مرشح بلوم هو “درهم الوقاية” الذي يمنع “قنطار العلاج” المتمثل في ترقية السيرفرات وقواعد البيانات بشكل مكلف ومستمر.

الخلاصة: أداة بسيطة بتأثير هائل

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

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

فيا صديقي المبرمج، في المرة القادمة التي تواجه فيها مشكلة أداء بسبب الاستعلامات المتكررة، تذكر قصة أبو عمر، وفكر: هل يمكن لمرشح بلوم أن يكون بطلك القادم؟ 🚀

أبو عمر

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

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

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

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

آخر المدونات

ذكاء اصطناعي

بحثنا كان لا يفهم المعنى: كيف أنقذتنا ‘قواعد البيانات المتجهية’ (Vector Databases) من جحيم البحث الحرفي؟

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

10 أبريل، 2026 قراءة المزيد
تسويق رقمي

عملاؤنا المحتملون كانوا أشباحًا: كيف أنقذتنا “نمذجة الإحالة القائمة على البيانات” من جحيم تتبع الإعلانات الأعمى؟

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

10 أبريل، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

تطبيقاتنا كانت تستبعد الملايين: كيف أنقذتنا ‘إرشادات الوصول الرقمي’ (WCAG) من جحيم الإقصاء؟

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

10 أبريل، 2026 قراءة المزيد
الحوسبة السحابية

بيئاتنا السحابية كانت فوضى: كيف أنقذتنا البنية التحتية كشيفرة (IaC) من جحيم الانحراف؟

أشارككم قصة حقيقية من قلب المعركة التقنية، كيف انتقلنا من فوضى النقرات اليدوية والانحراف في إعدادات السحابة إلى عالم منظم ومتناغم بفضل "البنية التحتية كشيفرة"...

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