كانت قاعدة بياناتنا تستغيث: كيف أنقذتنا فلاتر بلوم (Bloom Filters) من جحيم الاستعلامات المكلفة؟

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

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

لكن مع الوقت، كبرت المنصة، وصار عنا ملايين المستخدمين وملايين الكوبونات المستخدمة. وهنا بلشت المصايب. قاعدة البيانات صارت تصرخ وتستغيث مع كل عملية تسجيل مستخدم جديد أو استخدام كوبون. كل عملية تحقق بسيطة مثل “هل اسم المستخدم ‘ahmad123’ موجود؟” كانت تعني استعلام SELECT على جدول فيه ملايين السجلات. 99% من هذه الاستعلامات كانت ترجع بنتيجة “لا، غير موجود”، لكنها كانت تستهلك موارد السيرفر وتخنق قاعدة البيانات خنق.

وصلنا لمرحلة إنه السيرفرات صارت “تلعلع” من الضغط، والمستخدمين يشتكوا من بطء التسجيل. قعدنا كفريق، حطينا إيدينا على خدنا، والكل صافن. جربنا كل الحلول التقليدية: عملنا indexing للجداول، وحسّنا الاستعلامات، وعملنا caching… تحسنت الأمور شوي، بس المشكلة الأساسية بعدها موجودة: إحنا بنسأل قاعدة البيانات سؤال مكلف ملايين المرات، مع إنه جوابه في أغلب الأحيان هو “لأ”.

في ليلة من الليالي وأنا بقلّب في دفاتري القديمة وملاحظات الجامعة، لمعت في بالي فكرة من محاضرة عن هياكل البيانات المتقدمة. اسم غريب رن في أذني: “فلاتر بلوم” أو Bloom Filters. تذكرت إنه الدكتور وقتها وصفها بأنها “حارس بوابة ذكي وكسول”. ذكي لأنه بيمنع معظم الزوار غير المرغوب فيهم، وكسول لأنه ما بيعرف كل التفاصيل. قلت لحالي: “يا ولد، يمكن هذا هو الحل اللي بندور عليه!”.

ما هي فلاتر بلوم (Bloom Filters)؟ مش سحر، بس قريب!

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

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

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

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

كيف تعمل هذه “الخزعبلات” التقنية؟ (آلية العمل بالتفصيل)

لنفهم كيف تعمل، تخيلوا معنا ثلاثة مكونات رئيسية.

1. المصفوفة السحرية (The Bit Array)

تخيل عندنا شريط طويل جداً من خانات الذاكرة، كل خانة فيها إما 0 أو 1. هذا الشريط بنسميه مصفوفة البتات (Bit Array). في البداية، كل الخانات بتكون قيمتها 0.

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

2. الطباخون الماهرون (The Hash Functions)

عندنا مجموعة من “الطباخين” أو دوال التجزئة (Hash Functions). وظيفة كل دالة هي أنها تأخذ أي عنصر (مثل اسم مستخدم “abu_omar”) وتحوله إلى رقم فريد يمثل موقعاً (index) على شريط البتات الطويل تبعنا. نستخدم عادةً عدة دوال (مثلاً 3 دوال) لزيادة الدقة.

3. عملية الإضافة (Adding an Element)

لنفترض أننا نريد إضافة اسم المستخدم “khalid” إلى الفلتر. ماذا نفعل؟

  1. نمرر “khalid” على دوال التجزئة الثلاثة.
  2. الدالة الأولى تعطينا الرقم 2.
  3. الدالة الثانية تعطينا الرقم 5.
  4. الدالة الثالثة تعطينا الرقم 9.
  5. بكل بساطة، نذهب إلى المواقع 2، 5، و9 في مصفوفة البتات ونغير قيمتها من 0 إلى 1.

فتصبح المصفوقة هكذا:

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

لاحظ أننا لم نخزن اسم “khalid” نفسه! فقط علمنا بعض الأماكن في الذاكرة.

4. عملية التحقق (Checking for an Element)

هنا يظهر الجمال الحقيقي. لنفترض أن مستخدماً جديداً يريد التسجيل باسم “omar”.

  • السيناريو الأول: التحقق من اسم “omar” (غير موجود)
    1. نمرر “omar” على نفس دوال التجزئة.
    2. الدوال تعطينا الأرقام: 1، 4، و8.
    3. نذهب ونتفحص القيم في المواقع 1، 4، و8. نجدها كلها 0.
    4. النتيجة: بما أن أحد المواقع على الأقل قيمته 0، فالفلتر يجيب بثقة 100%: “omar بالتأكيد غير موجود”. انتهى. لا داعي لسؤال قاعدة البيانات.
  • السيناريو الثاني: التحقق من اسم “khalid” (موجود)
    1. نمرر “khalid” على دوال التجزئة.
    2. الدوال تعطينا نفس الأرقام: 2، 5، و9.
    3. نتفحص القيم في المواقع 2، 5، و9. نجدها كلها 1.
    4. النتيجة: بما أن كل المواقع المطلوبة قيمتها 1، الفلتر يجيب: “khalid ربما موجود”. في هذه الحالة، نذهب ونتأكد من قاعدة البيانات.

الجانب المظلم: احتمالية الخطأ (False Positives) وكيف نتعامل معها

قد تسأل: “طيب يا أبو عمر، شو قصة الإيجابية الكاذبة هاي؟”.

لنفترض أننا أضفنا “khalid” (الذي حجز المواقع 2, 5, 9) ثم أضفنا “samir” (الذي حجز المواقع 3, 7, 10). ثم أتينا لنتحقق من اسم جديد وليكن “hassan”. بالصدفة البحتة، دوال التجزئة لـ “hassan” أعطتنا المواقع 2، 7، و10. عندما يفحص الفلتر هذه المواقع، سيجدها كلها 1 (الموقع 2 من “khalid”، والمواقع 7 و10 من “samir”). سيقول الفلتر إن “hassan” ربما موجود، مع أننا لم نضفه أبداً! هذا هو الـ False Positive.

هل هذا سيء؟ ليس بالضرورة! الهدف ليس الدقة المطلقة، بل تخفيف الحمل. الفلتر سيجعلنا نقوم باستعلام إضافي غير ضروري لقاعدة البيانات في حالة “hassan”. لكن مقابل كل حالة “false positive” واحدة، يكون الفلتر قد منع آلاف الاستعلامات الصحيحة عن عناصر غير موجودة فعلاً.

يمكننا التحكم في معدل الخطأ هذا عبر تغيير حجم مصفوفة البتات (m) وعدد دوال التجزئة (k). كلما زاد حجم المصفوفة، قل احتمال تصادم المواقع وقل معدل الخطأ.

مثال عملي: منقذنا في مشروع “التحقق من اسم المستخدم”

لنعد إلى قصتنا. هذا ما فعلناه بالضبط لحل مشكلة التحقق من اسم المستخدم.

قبل فلاتر بلوم: الجحيم بعينه

كان الكود ببساطة يقوم بالتالي (مثال بلغة بايثون للتوضيح):

def is_username_taken_slow(username):
    # مكلف جداً: يضرب قاعدة البيانات في كل مرة!
    # SELECT COUNT(*) FROM users WHERE username = '...'
    count = db.users.count_documents({'username': username})
    return count > 0

بعد فلاتر بلوم: يا سلام سلّم!

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

# pip install pybloom-live
from pybloom_live import BloomFilter

# عدد المستخدمين المتوقع ومليون، ومعدل خطأ مقبول 0.1%
# سيتم حساب حجم المصفوفة وعدد الدوال تلقائياً
user_filter = BloomFilter(capacity=1000000, error_rate=0.001)

# عند بدء تشغيل الخادم، نملأ الفلتر مرة واحدة
all_users = db.users.find({}, {'username': 1})
for user in all_users:
    user_filter.add(user['username'])

# دالة التحقق الجديدة والسريعة
def is_username_taken_fast(username):
    # الخطوة 1: تحقق سريع جداً في الذاكرة
    if username not in user_filter:
        # إذا قال الفلتر "لأ"، فهو "لأ" بالتأكيد. ارجع فوراً.
        return False

    # الخطوة 2: الفلتر قال "ربما". الآن فقط نتأكد من قاعدة البيانات.
    # هذا يحدث فقط لأسماء المستخدمين الموجودة فعلاً + نسبة صغيرة من الإيجابيات الكاذبة
    count = db.users.count_documents({'username': username})
    return count > 0

# عند تسجيل مستخدم جديد بنجاح
def register_new_user(username, ...):
    # ... منطق تسجيل المستخدم في قاعدة البيانات ...
    db.users.insert_one({'username': username, ...})
    # لا تنسَ إضافة الاسم الجديد إلى الفلتر في الذاكرة!
    user_filter.add(username)

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

نصائح أبو عمر الذهبية لاستخدام فلاتر بلوم

  • نصيحة 1: اختر الحجم المناسب. لا تبخل في حجم الفلتر (الذاكرة). قبل أن تبدأ، قدّر عدد العناصر التي ستخزنها (n) وحدد معدل الخطأ (p) الذي يمكنك تحمله. استخدم حاسبات فلاتر بلوم الموجودة على الإنترنت لتعرف الحجم الأمثل للمصفوفة (m) وعدد دوال التجزئة (k).
  • نصيحة 2: لا يمكن الحذف! فلاتر بلوم القياسية لا تدعم حذف العناصر. لماذا؟ لأنك إذا حذفت بت (bit) معين، قد يكون هذا البت مشتركاً مع عنصر آخر، وبالتالي تكون قد “أفسدت” بيانات عنصر آخر دون قصد. إذا كنت تحتاج للحذف، ابحث عن أنواع متقدمة مثل “Counting Bloom Filters”.
  • نصيحة 3: متى تستخدمها؟ هي مثالية لأي سيناريو تحتاج فيه إلى التحقق من عضوية عنصر في مجموعة كبيرة جداً، ولا يهمك وجود نسبة خطأ صغيرة جداً. أمثلة:
    • التحقق من أن المستخدم لم يرَ هذا الخبر/المنتج من قبل.
    • حجب المواقع الضارة أو عناوين IP المعروفة.
    • تجنب تكرار معالجة البيانات في أنظمة البيانات الضخمة.
  • نصيحة 4: متى لا تستخدمها؟ لا تستخدمها عندما تكون الإيجابيات الكاذبة غير مقبولة على الإطلاق، أو عندما تحتاج إلى قائمة بالعناصر نفسها وليس فقط التحقق من وجودها. هي للتحقق، وليست للتخزين.

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

أبو عمر

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

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

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

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

آخر المدونات

​معمارية البرمجيات

نمط الخانق (Strangler Fig): كيف تخنق المونوليث القديم تدريجياً دون أن تخنق فريقك؟

أنا أبو عمر، وهذا درسي لكم عن نمط الخانق (Strangler Fig)، الاستراتيجية التي تعلمتها من الطبيعة لتحديث الأنظمة القديمة "المونوليث" خطوة بخطوة، دون المخاطرة الكبيرة...

6 مايو، 2026 قراءة المزيد
تسويق رقمي

رسائلنا التسويقية كانت تضيع: كيف أنقذنا “التخصيص الفائق” والذكاء الاصطناعي من جحيم التجاهل؟

كنا نصرخ في فراغ رقمي ورسائلنا لا تصل. في هذه المقالة، أشارككم قصة حقيقية كيف استخدمنا الذكاء الاصطناعي وتقنيات التخصيص الفائق (Hyper-personalization) لتحويل حملاتنا التسويقية...

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

الهمسات الرقمية: كيف تحوّل التفاعلات الدقيقة (Microinteractions) تجربة المستخدم من عادية إلى ساحرة؟

أنا أبو عمر، وفي هذه المقالة سأشارككم سرّاً من أسرار التصميم الاحترافي. سنتحدث عن "الهمسات الرقمية" أو التفاعلات الدقيقة (Microinteractions)، تلك التفاصيل الصغيرة التي تحوّل...

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

كانت استعلاماتنا تبحث في كل صف: كيف أنقذتنا ‘فهارس قواعد البيانات’ من جحيم المسح الكامل للجداول (Full Table Scans)؟

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

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

كانت خوادمنا تستهلك وقتنا ومواردنا: كيف أنقذتنا ‘الحوسبة بدون خوادم’ (Serverless) من جحيم إدارة البنية التحتية؟

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

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

كان طلب واحد يُجمّد النظام بأكمله: كيف أنقذتنا ‘طوابير الرسائل’ من جحيم المهام المتزامنة؟

أشارككم قصة حقيقية عن يوم كاد فيه طلب واحد أن يُسقط نظامنا بالكامل، وكيف كانت "طوابير الرسائل" (Message Queues) هي طوق النجاة. سنتعمق في فهم...

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