إيقاظ قواعد البيانات دون اتصالات ميتة
التطبيقات المنشورة على Mythex، وقواعد بياناتها، تدخل في السكون عندما لا يستخدمها أحد. وعندما يستيقظ التطبيق، كان يمكن أن يحتجز أول طلب له 10 إلى 12 ثانية، لأنه يحاول إعادة استخدام اتصالات قاعدة البيانات التي ماتت أثناء سكون الجميع. وأصبحنا الآن نمنح تطبيقات Spring Boot إعدادات قاعدة بيانات آمنة عبر فترات السكون، ونخبر الوكيل بأن يبني التطبيقات بأي تقنية بالطريقة نفسها. وعلى منصة الاستضافة، انتقل أول طلب بعد الاستيقاظ من 12.7 ثانية إلى 2.3 ثانية.
لماذا تنام التطبيقات وقواعد البيانات؟
يعمل التطبيق المنشور على Mythex على جهاز يتعلّق بعد دقائق قليلة من الخمول، وقاعدة بياناته هي Postgres بلا خادم تنام بعد خمس دقائق خاملة وتستيقظ مع الاستعلام التالي. وهذا ما يُبقي التطبيق الهادئ رخيصاً: تدفع مقابل ما استخدمته قاعدة البيانات فعلياً، والتطبيق النائم لا يصرف شيئاً على الحوسبة. كتبنا عن جانب الفوترة في تسعير قواعد البيانات بحسب ما تستخدمه فعلياً.
المشكلة أن الاثنين ينامان بشكل منفصل، ولا يتفقان على ما هو الاتصال.
العَرَض: زيارة أولى بطيئة
معظم تطبيقات الخوادم تحتفظ بـ مجمع اتصالات (connection pool): عدد قليل من اتصالات قاعدة البيانات المفتوحة والجاهزة، حتى لا يدفع كل طلب تكلفة فتح اتصال جديد. هذا التصميم صحيح لتطبيق يعمل دائماً.
وإليك ما يحدث مع السكون:
- التطبيق يعمل، ومعه بضعة اتصالات خاملة في مجمعه.
- لا يزور أحد الموقع. يتعلّق جهاز التطبيق، أي يتجمد مع بقاء مجمعه سليماً.
- تدخل قاعدة البيانات في السكون وتُسقط طرفها من كل اتصال.
- يصل زائر. يستيقظ التطبيق كما كان تماماً، ما زال يحمل اتصالات لم تعد قاعدة البيانات تعرفها.
- يسلّم المجمع أحد تلك الاتصالات. يجب أن يكتشف أنه ميت، بمحاولة استخدامه والانتظار، قبل أن يتخلى عنه.
في تطبيق Spring Boot، كان التحقق من الاتصالات الميتة يحجب الطلب الأول لنحو 10 ثوانٍ قبل أن يفشل بخطأ «Connection is not available». وبالنسبة للزائر، كان الموقع يبدو معلقاً، ثم يعرض ربما خطأً.
لماذا كانت تطبيقات Spring الأسوأ حظاً؟
Spring Boot إطار عمل Java شائع، ويستخدم افتراضياً مجمع اتصالات يُدعى HikariCP. ويستطيع Spring أن يهيّئ هذا المجمع من الإعدادات، بما في ذلك عدد الاتصالات الخاملة التي يُحتفظ بها.
لكن Mythex يمنح كل تطبيق قاعدة بياناته كمتغير واحد DATABASE_URL بالشكل postgresql://user:pass@host/db. ولا يستطيع Spring أن يقرأ هذا الشكل مباشرة. لذلك كانت تطبيقات Spring المبنية على Mythex تُنشئ مجمعها بنفسها في الكود عبر تحليل هذا الرابط، والمجمع المبني يدوياً يتجاهل كل إعدادات spring.datasource.* في Spring. وحتى لو عرف أحدهم إعدادات المجمع الصحيحة، لم يكن هناك مكان تُطبَّق فيه.
الإصلاح، الجزء الأول: إعدادات يستطيع Spring استخدامها
عندما يحتوي مشروع على قاعدة بيانات، نمنحه الآن أيضاً متغيرات بيئة يقرؤها Spring مباشرة:
SPRING_DATASOURCE_URLوSPRING_DATASOURCE_USERNAMEوSPRING_DATASOURCE_PASSWORD، مشتقة منDATABASE_URL(مع اشتراط SSL ما لم يحدد الرابط غير ذلك).- إعدادات مجمع تصمد أمام السكون:
| الإعداد | القيمة | السبب |
|---|---|---|
| الحد الأدنى للاتصالات الخاملة | 0 | لا يُحتفظ بشيء مفتوح بين الطلبات، فلا يوجد ما يتقادم |
| مهلة الخمول | 30 ثانية | إغلاق الاتصالات غير المستخدمة قريباً |
| أقصى عمر للاتصال | دقيقتان | إنهاء كل اتصال قبل أن يتقادم |
| مهلة الاتصال | 5 ثوانٍ | عدم حجز الطلب طويلاً إذا تباطأت قاعدة البيانات في الرد |
ومع عدم وجود اتصالات خاملة محتفظ بها، يفتح التطبيق المستيقظ اتصالاً واحداً جديداً بدلاً من الخوض في اتصالات ميتة.
وعندما تكون هذه المتغيرات موجودة، يهيّئ Spring المجمع بنفسه، وتُطبَّق الإعدادات. وأي شيء يضبطه المشروع بنفسه يبقى له الأولوية دائماً؛ نحن نملأ فقط ما هو ناقص. التطبيقات غير المبنية على Spring تتجاهل هذه المتغيرات.
التفصيلة التي كادت تجعله بلا أثر
يربط Spring متغيرات البيئة بإعداداته وفق قاعدة سهل الخطأ فيها. الإعداد minimum-idle يجب أن يُكتب SPRING_DATASOURCE_HIKARI_MINIMUMIDLE، بإسقاط الشرطات ودمج الكلمات. كتابته كـ SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE، وهي تبدو أطبع، تُحوَّل إلى إعداد مختلف غير موجود (minimum.idle) فيُتجاهل بصمت.
وهذا النوع من الأخطاء يمر من كل مراجعة ولا يفعل شيئاً في الإنتاج. لذلك يتحقق اختبار من أن القيم مضبوطة، ومن أن لا متغير يستخدم الصيغة المفصولة بالشرطات السفلية.
الإصلاح، الجزء الثاني: كل التقنيات الأخرى
Spring ليس الإطار الوحيد الذي يملك مجمعاً، ولا نستطيع أن نهيّئ كل مكتبة في كل لغة مسبقاً. لذلك يتلقى وكيل Mythex، أي الذكاء الاصطناعي الذي يكتب تطبيقك، الآن تعليمة دائمة لأي تقنية: التطبيقات المنشورة وقواعد بياناتها تنام عند الخمول، لذلك قد يكون اتصال مفتوح ميتاً عند الطلب التالي. وعليه:
- ألا يحتفظ بأي اتصالات خاملة مفتوحة لقاعدة البيانات، أو أن يتحقق من كل اتصال قبل استخدامه،
- أن يعيد المحاولة مرة واحدة عند خطأ في الاتصال، و
- أن يستخدم إعدادات قاعدة البيانات التي توفرها المنصة في البيئة، لا أن يبني مجمعه بنفسه بتحليل
DATABASE_URL.
هذه النقطة الأخيرة تغلق الحلقة مع الجزء الأول: التطبيق المبني على الإعدادات المقدمة يلتقط الإعدادات الآمنة عند السكون تلقائياً.
النتيجة
عند القياس على منصة الاستضافة التي نشغّل عليها التطبيقات المنشورة، انتقل أول طلب بعد الاستيقاظ من 12.7 ثانية إلى 2.3 ثانية.
ما الذي استخلصناه
- كل طبقة تنام يجب أن تتفق على ما يبقى. التطبيق المجمّد وقاعدة البيانات النائمة اتخذ كل منهما قراراً معقولاً. ومعاً، استيقظ التطبيق حاملاً حالة لم تعد صحيحة.
- الافتراضات المصممة لخوادم تعمل دائماً لا تناسب التطبيقات التي تنزل إلى الصفر. الإبقاء على الاتصالات دافئة فكرة جيدة إلى أن يصبح كل ما حولها بارداً.
- أعطِ الأطر بياناتها بصيغتها الخاصة. رابط واحد كان بسيطاً بالنسبة لنا، لكنه أجبر كل تطبيق Spring على بناء مجمع يدوي يتجاهل إعداداته.
- اختبر أن الإعداد مكتوب بالاسم الذي يقرؤه الإطار. قيمة صحيحة تحت اسم خاطئ لا تفعل شيئاً، وبصمت.
إذا كنت جديداً على طريقة عمل قواعد البيانات في Mythex، فانظر وثائق قواعد البيانات وشرحنا عن ما هو Postgres.