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

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

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

كنت أجلس في مكتبي، أراقب لوحات المراقبة (Dashboards) وأنا أرتشف قهوتي الصباحية. فجأة، بدأت ألوان الرسوم البيانية تتغير إلى الأحمر القاني. استخدام المعالج (CPU) لقاعدة البيانات قفز إلى 95%، 98%، ثم استقر عند 100% وهو يلهث. بدأت الشكاوى تنهال من المستخدمين: “التسجيل بطيء جداً!”، “الموقع لا يستجيب!”.

شعرت بتلك الغصة في حلقي، وقلت في نفسي “آخ يا قلبي، شو اللي بصير؟”. ركضنا أنا والفريق نحلل سجلات الخادم (Server Logs)، لنكتشف أن 90% من الاستعلامات التي تصل إلى جدول المستخدمين كانت من نوع: SELECT id FROM users WHERE username = 'some_random_name_that_does_not_exist'.

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

الجحيم الصامت: لعنة الاستعلامات عن العناصر غير الموجودة

قد تعتقد أن البحث عن شيء موجود في قاعدة البيانات هو الأصعب، لكن العكس هو الصحيح في كثير من الأحيان. عندما تبحث عن عنصر موجود ومُفهرَس (indexed)، فإن قاعدة البيانات تذهب مباشرة إلى مكانه في الفهرس وتجده بسرعة.

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

وبزغ الفجر: لقاؤنا الأول مع مرشّح بلوم

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

جماله يكمن في طبيعة إجابته:

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

وهذا بالضبط ما كنا نحتاجه! نريد أن نتخلص من الـ 90% من الاستعلامات عن أسماء مستخدمين غير موجودة بشكل مؤكد. لا يهمنا إذا سمحنا بمرور القليل من الاستعلامات الخاطئة إلى قاعدة البيانات عن طريق الخطأ، طالما أننا أوقفنا الطوفان الرئيسي.

كيف يعمل هذا السحر؟ تفكيك مرشّح بلوم

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

  1. مصفوفة بتات (Bit Array): تخيلها كسلسلة طويلة جداً من الأصفار، مثلاً مليون صفر متجاور.
  2. عدة دوال تجزئة (Hash Functions): مثلاً 3 دوال مختلفة. وظيفة دالة التجزئة هي أن تأخذ أي مُدخل (مثل اسم المستخدم) وتعطيك رقماً (فهرساً) بشكل شبه عشوائي.

عملية الإضافة (Add)

لإضافة اسم مستخدم جديد، مثلاً “abu_omar”، نقوم بالآتي:

  1. نُمرر “abu_omar” على دالة التجزئة الأولى، فتعطينا مثلاً الرقم 150. نذهب إلى الخانة رقم 150 في مصفوفة البتات ونحولها من 0 إلى 1.
  2. نُمرره على الدالة الثانية، فتعطينا مثلاً 8432. نذهب إلى الخانة 8432 ونحولها إلى 1.
  3. نُمرره على الدالة الثالثة، فتعطينا 981. نذهب إلى الخانة 981 ونحولها إلى 1.

وهكذا نكرر العملية مع كل أسماء المستخدمين الموجودة في قاعدة بياناتنا. ستمتلئ مصفوفة البتات بالتدريج بالآحاد في أماكن متفرقة.

عملية التحقق (Check)

الآن، يأتي مستخدم جديد ويحاول التسجيل باسم “ahmad123”. قبل أن نذهب إلى قاعدة البيانات، نسأل مرشح بلوم أولاً:

  1. نُمرر “ahmad123” على دوال التجزئة الثلاثة نفسها.
  2. الدالة الأولى تعطينا مثلاً 500، الثانية 12345، والثالثة 999.
  3. ننظر إلى البتات في هذه الخانات الثلاث في مصفوفتنا.

وهنا يكمن السر:
– إذا وجدنا أن أياً من هذه الخانات ما زال قيمته 0، فهذا دليل قاطع على أن “ahmad123” لم يُضف من قبل! لأنه لو أُضيف، لكانت كل هذه الخانات قد تحولت إلى 1. هنا نوقف العملية فوراً ونقول للمستخدم “الاسم غير موجود، تفضل بالتسجيل”. لقد أنقذنا قاعدة البيانات من استعلام لا داعي له!
– أما إذا وجدنا أن كل الخانات الثلاث قيمتها 1، فهذا يعني أن الاسم “قد يكون موجوداً”. لماذا “قد يكون”؟ لأنه من المحتمل أن تكون هذه الخانات الثلاث قد تم تحويلها إلى 1 بواسطة أسماء مستخدمين آخرين مختلفين تماماً تصادف أن دوال التجزئة الخاصة بهم أشارت إلى نفس هذه الخانات. هذا هو الـ “False Positive”. في هذه الحالة فقط، نسمح للاستعلام بالمرور إلى قاعدة البيانات للتأكد بشكل قاطع.

من النظرية إلى التطبيق: مثال بسيط بلغة Python

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


# نحتاج لتثبيت المكتبات أولاً
# pip install mmh3 bitarray

import mmh3
from bitarray import bitarray

class BloomFilter:
    def __init__(self, size, hash_count):
        self.size = size
        self.hash_count = hash_count
        self.bit_array = bitarray(size)
        self.bit_array.setall(0)

    def add(self, item):
        # إضافة عنصر
        for i in range(self.hash_count):
            # نستخدم "seed" مختلف لكل دالة تجزئة
            digest = mmh3.hash(item, i) % self.size
            self.bit_array[digest] = 1

    def check(self, item):
        # التحقق من وجود عنصر
        for i in range(self.hash_count):
            digest = mmh3.hash(item, i) % self.size
            if self.bit_array[digest] == 0:
                return False # غير موجود بالتأكيد
        return True # قد يكون موجوداً

# --- مثال الاستخدام ---

# لنفترض أن لدينا مليون اسم مستخدم، ونريد نسبة خطأ 1%
# باستخدام حاسبة مرشح بلوم (ابحث عنها اونلاين)
# نحتاج مصفوفة بحجم ~9.6 مليون بت، و 7 دوال تجزئة.
BF_SIZE = 9585059
HASH_COUNT = 7

# إنشاء المرشح
username_filter = BloomFilter(BF_SIZE, HASH_COUNT)

# إضافة المستخدمين الموجودين في قاعدة البيانات
existing_users = ["abu_omar", "sami", "leila", "khalid"]
for user in existing_users:
    username_filter.add(user)

# --- الآن يبدأ الاختبار الحقيقي ---

# 1. مستخدم يحاول التسجيل باسم موجود
if username_filter.check("abu_omar"):
    print("الاسم 'abu_omar' قد يكون موجوداً، لنتحقق من قاعدة البيانات.") # سيذهب لقاعدة البيانات
else:
    # هذا لن يحدث أبداً للأسماء الموجودة
    print("الاسم 'abu_omar' غير موجود بالتأكيد.")

# 2. مستخدم يحاول التسجيل باسم غير موجود
if username_filter.check("jad_the_new_user"):
    # هذا قد يحدث بنسبة 1% (false positive)
    print("الاسم 'jad_the_new_user' قد يكون موجوداً، لنتحقق من قاعدة البيانات.")
else:
    # هذا ما سيحدث في 99% من الحالات للأسماء الجديدة
    print("الاسم 'jad_the_new_user' غير موجود بالتأكيد. تفضل بالتسجيل!")

الهندسة المعمارية الجديدة: مرشح بلوم كحارس للبوابة

الحل كان بسيطاً. قمنا بتحميل كل أسماء المستخدمين عند بدء تشغيل الخادم ووضعناها في مرشح بلوم مُخزّن في الذاكرة (In-memory). أصبحت بنية النظام كالتالي:

  • قبل: طلب المستخدم -> الخادم -> قاعدة البيانات (دائماً).
  • بعد: طلب المستخدم -> الخادم -> مرشح بلوم.
    • إذا قال المرشح “غير موجود”: نرجع للمستخدم مباشرة. (90% من الحالات)
    • إذا قال المرشح “قد يكون موجود”: عندها فقط نذهب إلى قاعدة البيانات. (10% من الحالات)

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

نصائح من الخبير: كيف تستخدم مرشح بلوم كالمحترفين

من تجربتي، هذه بعض النصائح الذهبية:

1. اختيار الحجم وعدد دوال التجزئة هو مفتاح النجاح

هذا أهم قرار ستتخذه. لا تختر الأرقام عشوائياً. هناك معادلات رياضية لحساب الحجم الأمثل للمصفوفة (m) وعدد دوال التجزئة (k) بناءً على عدد العناصر المتوقع (n) ونسبة الإيجابية الكاذبة (p) التي يمكنك تحملها. استخدم حاسبة جاهزة على الإنترنت (ابحث عن “Bloom filter calculator”)، فهي تسهل المهمة جداً.

2. لا يمكنك الحذف! (في النسخة القياسية)

تذكر أنك لا تستطيع حذف عنصر من مرشح بلوم القياسي. لماذا؟ لأنك إذا حولت بتاً من 1 إلى 0، قد يكون هذا البت مشتركاً مع عنصر آخر، وبذلك تكون قد “أفسدت” وجوده في المرشح. إذا كنت تحتاج لعملية الحذف، فابحث عن أنواع متقدمة مثل “Counting Bloom Filter” الذي يسمح بالحذف مقابل استهلاك مساحة أكبر.

3. متى تستخدمه ومتى لا

  • استخدمه: عندما تكون تكلفة التحقق من المصدر الأساسي (قاعدة بيانات، API) عالية، وعندما تكون الاستعلامات عن عناصر غير موجودة شائعة، ويمكنك تحمل نسبة خطأ صغيرة. أمثلة رائعة: التحقق من توفر اسم مستخدم، حجب الروابط الخبيثة، طبقات التخزين المؤقت (Caching) لتجنب جلب نفس البيانات مراراً.
  • لا تستخدمه: عندما لا يمكنك تحمل الإيجابيات الكاذبة إطلاقاً. مثلاً، لا تستخدمه للتحقق إذا كان حساب بنكي يخص عميلاً ما، فالخطأ هنا قد يكون كارثياً.

الخلاصة: الأداة المناسبة في الوقت المناسب 💡

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

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

ففي المرة القادمة التي تجد فيها نظامك يئن تحت ضغط استعلامات لا طائل منها، تذكر صديقنا مرشح بلوم. قد يكون هو البطل الصامت الذي تنتظره. خليكوا مبدعين! 😉

أبو عمر

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

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

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

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

آخر المدونات

نصائح برمجية

كودنا كان مليئاً بالأرقام الغامضة: كيف أنقذتنا ‘التعدادات’ (Enums) من جحيم الأرقام السحرية؟

أتذكر ليلة طويلة من تصحيح الأخطاء، كان السبب رقماً غامضاً في الكود. في هذه المقالة، أشارككم قصة كيف أنقذتنا التعدادات (Enums) من فوضى "الأرقام السحرية"،...

24 أبريل، 2026 قراءة المزيد
ذكاء اصطناعي

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

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

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

كنا نكتب كل صفحة هبوط يدوياً: كيف أنقذنا ‘التحسين البرمجي لمحركات البحث’ (Programmatic SEO) من جحيم المحتوى المحدود؟

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

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

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

أنا أبو عمر، مبرمج فلسطيني. كنت أظن أن موقعنا تحفة فنية، حتى جاءتني رسالة من صديق كفيف قائلاً: "موقعك جميل، لكنني لا أرى منه شيئاً"....

24 أبريل، 2026 قراءة المزيد
برمجة وقواعد بيانات

تحديث قاعدة البيانات كان يعني إيقاف الخدمة: كيف أنقذتنا ‘الهجرات بدون توقف’ من جحيم الصيانة الليلية؟

وداعاً ليالي الصيانة الطويلة والمستخدمين الغاضبين! في هذه المقالة، أشارككم قصة حقيقية وكيف غيرت استراتيجيات 'الهجرات بدون توقف' (Zero-Downtime Migrations) طريقة عملنا، مع دليل عملي...

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

فاتورة السحابة كانت لغزاً: كيف أنقذتنا ‘عمليات الإدارة المالية’ (FinOps) من جحيم الإنفاق غير المتوقع؟

أشارككم قصة حقيقية من قلب المعركة مع فواتير الحوسبة السحابية التي كادت أن تغرق مشروعنا. اكتشفوا كيف كانت مبادئ FinOps طوق النجاة الذي حول الفوضى...

23 أبريل، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

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

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

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