B-tree Indexes في PostgreSQL: ازاي تنزّل query من 4.2 ثانية لـ 5 مللي ثانية بسطر واحد
لو عندك جدول فيه مليونين صف وكتبت SELECT * FROM users WHERE email = 'ali@example.com' ولقيت الـ query بياخد 4 ثوانٍ، السيرفر مش ضعيف، والـ DB مش بطيئة. اللي بيحصل فعلاً إن PostgreSQL بيقرا كل صف من أول الجدول لآخره عشان يلاقي الإيميل ده. سطر واحد CREATE INDEX بيخلّي نفس الـ query يرجع في 5 مللي ثانية. ده تحسّن 840 ضعف، بدون ما تغيّر سطر كود في تطبيقك.
المشكلة باختصار
كل جدول في PostgreSQL مخزّن على القرص كصفحات (Pages) كل صفحة 8KB. لما تكتب WHERE email = 'ali@example.com' من غير index، PostgreSQL مالوش طريقة يعرف الإيميل ده في أي صفحة. فبيعمل حاجة اسمها Sequential Scan (أو Full Table Scan): بيقرا كل الصفحات واحدة واحدة من أول الجدول. على جدول 2 مليون صف، ده تقريباً 35,000 صفحة لازم تتقرا من القرص. الـ I/O ده هو سبب الـ 4 ثواني.
الـ Index بيحل المشكلة دي بإنه يبني هيكل بيانات مساعد بيقولّك "الإيميل ده موجود في الصفحة رقم كذا"، فـ PostgreSQL يقفز للصفحة دي مباشرةً بدل ما يقرا الجدول كله.
مثال من الحياة: دليل التليفونات
تخيّل إنك في مكتبة فيها 2 مليون كتاب، ومحدّش رتّبهم، ومفيش فهرس. سألتك "فين كتاب الأيام لطه حسين؟". الإجابة الوحيدة: تفتح كل كتاب وتشوف عنوانه. ممكن تلاقيه في الكتاب رقم 7، وممكن في الكتاب رقم 1,999,998. متوسط البحث: مليون عملية.
الآن خلّي معاك ورقة مرتبة أبجدياً فيها اسم كل كتاب ومكانه على الرف ("الأيام — رف 14، خانة 3"). البحث في الورقة دي مش بيستغرق ثوانٍ، لأنها مرتبة. ده هو الـ Index. الورقة المرتبة دي مش هي الكتب، هي مساعد للوصول للكتب بسرعة.
لكن في فرق جوهري: الورقة المرتبة لو طبعتها على هيئة قائمة طويلة، البحث فيها لسه بيتطلب مرور على الأسماء واحد واحد. الـ B-tree بيرتّبها كشجرة، وده اللي بيخلّي البحث أسرع جداً.
إيه هو الـ B-tree علمياً
الـ B-tree (اختصار Balanced Tree) هيكل بيانات شجري متوازن، اخترعه Rudolf Bayer و Edward McCreight سنة 1972 في Boeing Research Labs. كل عقدة (Node) في الشجرة بتحتوي على مجموعة قيم مرتبة + مؤشرات للعقد التابعة لها. خاصية "المتوازن" معناها إن المسافة من الجذر لأي ورقة (Leaf) متساوية تقريباً.
النتيجة العملية: للوصول لأي قيمة في جدول فيه N صف، الـ B-tree بيحتاج log₂(N) خطوة فقط. لجدول 2 مليون صف، ده 21 مقارنة بدل 2,000,000. هنا منشأ الفرق بين 4 ثوانٍ و 5 مللي ثانية.
تعريف "Balanced" مهم لأن الـ DB بيحدّث الشجرة تلقائياً مع كل INSERT و UPDATE و DELETE علشان تفضل متوازنة، يعني الأداء مستقر حتى لو الجدول كبر.
المثال التنفيذي: قبل وبعد على جدول 2 مليون صف
افترض إن عندنا جدول users فيه 2 مليون مستخدم. هنشغّل نفس الـ query قبل الـ Index وبعده، ونقيس الفرق باستخدام EXPLAIN ANALYZE.