الجواب باختصار.
تجد search_legislation النصوص من كلماتها. حتى 9 تشرين الثاني كان PostgreSQL يجمع كل نص مرشّح ويحسب درجته عند كل بحث. صار بناء الصورة يكتب، لكل كلمة، قائمة النصوص التي تحتويها، ويرتّب الخادم النتائج من هذه القوائم. على نواة معالج واحدة ينتهي ستة طلبات بحث متزامنة ضمن 54 إلى 56 ميلي ثانية عند المئين 95، مقابل 134 إلى 140 سابقاً. وكل جواب هو نفسه.
أولاً، عمل أقل للجواب نفسه.
الخطوة الأولى لم تغيّر أي بنية بيانات. في كلمة شائعة مثل «العمل»، الموجودة في 38,269 مادة، كان الاستعلام يربط كل نص مرشّح بالقوانين الـ103,112 كلها ليتأكد من أن القانون معروض، ويفرز المرشّحين بحثاً عن تكرار لا يمكن أن يوجد، ويعدّهم جميعاً من أجل مجموع لا يقرأه خادم MCP أبداً. من دون هذا العمل انخفض المئين 95 لـ30 سؤالاً حقيقياً من ما بين 176 و213 ميلي ثانية إلى 45. وحين تكون كل كلمات السؤال شائعة كان PostgreSQL يختار أيضاً قراءة كل الأحكام بدلاً من استخدام الفهرس: 165 ميلي ثانية مقابل نحو 25.
ملف يكتبه البناء.
سبق أن قسّم PostgreSQL كل نص إلى كلمات ليبني فهارس البحث. تقرأ مرحلة من البناء هذه الكلمات وتكتب قائمة لكل كلمة: أرقام النصوص التي تحتويها، مخزّنة كفروق صغيرة. وإلى جانب القوائم تكتب ما يحتاجه الترتيب عن كل نص: طوله وتاريخه وسنته ورقمه. حجم الملف 87.5 ميغابايت، و52 مضغوطاً، وتستغرق كتابته 15 ثانية على معالج واحد. يُصنع من قاعدة البيانات نفسها التي تحملها الصورة، ويتحقق الخادم من بصمة تلك القاعدة قبل أن يستخدم الملف.
ربط بالذاكرة، لا تحميل.
لا يقرأ الخادم الملف إلى الذاكرة. بل يربطه بها (mmap): يجلب نظام التشغيل الصفحات التي يلمسها البحث ويستطيع إسقاطها من جديد، كما يفعل مع ملفات PostgreSQL نفسها. لا يُنسخ إلا 12 ميغابايت من أعمدة النصوص. يصبح الخادم جاهزاً بعد 70 إلى 110 ميلي ثانية من تشغيله، ويبحث بالطريقة السابقة حتى ذلك الحين. نسخة أولى كانت تفكّ كل شيء في الذاكرة وتشغل 200 إلى 225 ميغابايت. أما هذه فتشغل 51 إلى 61 ميغابايت، إضافة إلى صفحات مربوطة يستطيع النظام استعادتها.
في الذاكرة أم في قاعدة البيانات؟
بنيناه بالطريقتين. بالقوائم نفسها مخزّنة في جدول PostgreSQL وبشيفرة الترتيب نفسها، انخفض وقت المعالج لكل بحث من ما بين 13 و15 ميلي ثانية إلى ما بين 7 و9. وبقراءتها من الملف المربوط انخفض إلى ما بين 4 و6. نسخة الجدول تدفع 3 إلى 5 استعلامات إضافية لكل بحث، وعلى PostgreSQL أن يفكّ كل قائمة وينسخها، وهي تكبر مع شيوع الكلمة. وكلّفت أيضاً 432 ميغابايت في قاعدة البيانات مقابل ملف من 88 ميغابايت، و133 ثانية من البناء مقابل 15. تستطيع قاعدة البيانات أن تحمل الفهرس. لكن ما يربح هو قراءته داخل العملية نفسها.
مطابق حتى آخر بت.
بحث أسرع يرتّب بشكل مختلف هو بحث آخر. قارنّا 21,169 جواباً حقلاً بحقل، مع مقارنة الدرجات بتاً ببت، ولم يختلف أي منها. اقتضى ذلك حساب كل درجة كما يحسبها PostgreSQL، بالضرب بدقة عشرية تامة قبل القسمة بالفاصلة العائمة. الصيغة البديهية أعطت نتيجة مختلفة في 692 من 2,000 قيمة. وحيث لا يكون جواب الاستعلام القديم نفسه محسوماً، لأن نصوصاً متساوية الطول تقع على جانبي حدّ القطع، يعيد الفهرس البحث إلى SQL. حصل ذلك في 4.9% من الأجوبة.
خطأ كشفه الفحص الحي.
مباشرة بعد النشر استغرق سؤال واحد 3,154 ميلي ثانية. هو من الأسئلة التي يعيدها الفهرس إلى SQL، وقبل إعادته كان الفهرس يفرز المطابقات الـ102,605 كلها بلا فائدة. عدّ المطابقات بحسب الطول يجد حدّ القطع نفسه من دون فرز. دُمج الإصلاح بعد ساعة. وصرنا نقيس زمن الحالات التي تعود إلى SQL أيضاً، لا الحالات السريعة وحدها.
أرقام من الخادم الحي.
زمن الخادم لستة طلبات بحث أُرسلت معاً إلى الموقع الحي: بين 19 و178 ميلي ثانية قبل التغيير، وبين 17 و90 بعده، بعد أن تسخن الحاوية. وبعد ثانية واحدة من النشر، على حاوية باردة: بين 104 و298 ميلي ثانية، ولم يُرفض أي طلب. أما الدقيقة الأولى بعد نشر البحث السابق فشهدت 88 إلى 1,050 ميلي ثانية وطلباً مرفوضاً.