
الاختبارات الوظيفية هي شيء مفيد. في البداية ، لا يستغرقون الكثير من الوقت ، لكن المشروع ينمو ، وهناك حاجة إلى المزيد والمزيد من الاختبارات. لم ننوي تحمل تباطؤ في سرعة التسليم ، وجمع قوتنا ، قمنا بتسريع الاختبارات الوظيفية ثلاث مرات. في المقالة ستجد نصائح عامة ، ومع ذلك ، ستلاحظ تأثيرًا خاصًا على المشاريع الكبيرة.
باختصار حول التطبيق
يعمل فريقي على تطوير واجهة برمجة تطبيقات عامة توفر البيانات لمستخدمي 2GIS. عندما تذهب إلى 2gis.ru وتبحث عن "سوبر ماركت" ، تحصل على قائمة بالمنظمات - هذه هي البيانات من API الخاص بنا. في 2000+ RPS لدينا ، تصبح كل مشكلة حرجة تقريبًا إذا انهار نوع من الوظائف.
التطبيق مكتوب بلغة سكالا ، الاختبارات مكتوبة بلغة PHP ، قاعدة البيانات هي PostgreSQL-9.4. لدينا حوالي 25000 اختبار وظيفي ، تستغرق 30 دقيقة لإكمالها على جهاز افتراضي مخصص للتراجع العام. لم تزعجنا مدة الاختبارات كثيرًا - لقد اعتدنا على حقيقة أن الاختبارات يمكن أن تستغرق 60 دقيقة في الإطار القديم.
كيف قمنا بتسريع ما يسمى بالاختبارات "السريعة"
بدأ كل شيء عن طريق الصدفة. كما يحدث عادة. دعمنا ميزة واحدة تلو الأخرى ، واجتازوا الاختبارات في نفس الوقت. زاد عددهم ، والوقت اللازم لإكماله أيضًا. بمجرد أن بدأت الاختبارات بالزحف خارج الحدود الزمنية المخصصة لها ، وبالتالي تم إنهاء عملية تنفيذها بالقوة. الاختبارات غير المكتملة محفوفة بمشكلة مفقودة في الكود.
قمنا بتحليل سرعة الاختبارات وأصبحت مهمة تسريعها بشكل حاد ذات صلة. لذلك بدأت دراسة بعنوان "الاختبارات تعمل ببطء - أصلحها".
فيما يلي ثلاثة من المشاكل الكبيرة التي وجدناها في الاختبارات.
المشكلة 1: jsQuery يساء استخدامها
يتم تخزين جميع البيانات التي لدينا في قاعدة بيانات PostgreSQL. في الغالب في شكل json ، لذلك نستخدم jsQuery بنشاط.
فيما يلي مثال على استعلام قمنا به في قاعدة البيانات للحصول على البيانات اللازمة:
SELECT * FROM firm WHERE json_data @@ 'rubrics.@# > 0' AND json_data @@ 'address_name = *' AND json_data @@ 'contact_groups.#.contacts.#.type = “website”' ORDER BY RANDOM() LIMIT 1
من السهل ملاحظة أن المثال يستخدم json_data عدة مرات متتالية ، على الرغم من أنه سيكون من الصحيح كتابة هذا:
SELECT * FROM firm WHERE json_data @@ 'rubrics.@# > 0 AND address_name = * AND contact_groups.#.contacts.#.type = “website”' ORDER BY RANDOM() LIMIT 1
لم تكن مثل هذه العيوب واضحة للغاية ، لأنه في الاختبارات لا نكتب جميع الاستفسارات بأيدينا ، ولكن بدلاً من ذلك نستخدم QueryBuilders ، التي يؤلفونها هم أنفسهم بعد تحديد الوظائف اللازمة. لم نعتقد أن هذا يمكن أن يؤثر على سرعة تنفيذ الاستعلام. وفقًا لذلك ، يبدو في الرمز شيئًا مثل هذا:
$qb = $this>createQueryBulder() ->selectAllBranchFields() ->fromBranchPartition() ->hasRubric() ->hasAddressName() ->hasWebsite() ->orderByRandom() ->setMaxResults(1);
لا تكرر أخطائنا : إذا كانت هناك عدة شروط في حقل JSONB واحد ، صفها جميعًا في إطار عامل التشغيل الوحيد "". بعد أن قمنا بإعادة التصميم ، قمنا بتسريع وقت تنفيذ كل طلب مرتين. في السابق ، كان الطلب الموصوف يستغرق 7500 مللي ثانية ، ولكنه الآن يستغرق 3500 مللي ثانية.
المشكلة 2: بيانات الاختبار الإضافية
يتم توفير الوصول إلى API الخاص بنا عن طريق المفتاح ، كل مستخدم لديه API الخاص به. سابقًا ، في الاختبارات ، كان من الضروري غالبًا تعديل الإعدادات الرئيسية. وبسبب هذا ، سقطت الاختبارات.
قررنا إنشاء العديد من المفاتيح بالإعدادات اللازمة لكل عملية انحدار لتجنب مشاكل التقاطع. وبما أن إنشاء مفتاح جديد لا يؤثر على وظائف التطبيق بأكمله ، فإن هذا النهج في الاختبارات لن يؤثر على أي شيء. عاشوا في مثل هذه الظروف لمدة عام تقريبًا ، حتى بدأوا في التعامل مع الإنتاجية.
لا يوجد العديد من المفاتيح - 1000 قطعة. لتسريع التطبيق ، نقوم بتخزينها في الذاكرة وتحديثها كل بضع دقائق أو عند الطلب. وهكذا ، بعد حفظ المفتاح التالي ، بدأت الاختبارات عملية المزامنة ، التي لم ننتظر نهايتها - تلقينا استجابة "504" ، والتي تمت كتابتها في السجلات. في الوقت نفسه ، لم يشر التطبيق إلى مشكلة بأي شكل من الأشكال ، واعتقدنا أن كل شيء يعمل بشكل جيد بالنسبة لنا. استمرت عملية اختبار الانحدار نفسها. وفي النهاية اتضح أننا كنا محظوظين دائمًا وتم حفظ مفاتيحنا.
عشنا في جهل حتى تحققنا من السجلات. اتضح أننا أنشأنا المفاتيح ، لكننا لم نحذفها بعد إجراء الاختبارات. وهكذا ، تراكمت لدينا 500،000 منهم.
لا تكرر أخطائنا: إذا قمت بتعديل قاعدة البيانات بطريقة أو بأخرى في الاختبارات ، فتأكد من إعادة قاعدة البيانات إلى حالتها الأصلية. بعد تنظيف قاعدة البيانات ، تسارعت عملية التحديث الرئيسية 500 مرة.
المشكلة 3: أخذ عينات عشوائية
نحن نحب اختبار التطبيق على بيانات مختلفة. لدينا الكثير من البيانات ، ويتم العثور على مشاكل بشكل دوري. على سبيل المثال ، كانت هناك حالة عندما لم يتم تحميل بيانات عن الإعلانات ، ولكن الاختبارات كشفت هذه المشكلة في الوقت المحدد. لهذا السبب في كل طلب من اختباراتنا يمكنك أن ترى ORDER BY RANDOM ()
عندما نظرنا إلى نتائج الاستعلامات ، مع عشوائية وبدون ، مع EXPLAIN ، شهدنا زيادة في الأداء بمقدار 20 مرة. إذا تحدثنا عن المثال أعلاه ، فعندئذٍ بدون عشوائية يعمل 160ms. فكرنا بجدية في ما يجب فعله ، لأننا لا نريد حقًا التخلي عن المنزل العشوائي تمامًا.
على سبيل المثال ، يوجد في نوفوسيبيرسك حوالي 150 ألف شركة ، وعندما حاولنا العثور على شركة لها عنوان وموقع ويب وعنوان ، تلقينا سجلًا عشوائيًا من قاعدة البيانات بالكامل تقريبًا. قررنا تخفيض الاختيار إلى أول 100 شركة تناسب ظروفنا. كانت نتيجة الأفكار حلا وسطا بين الاختيار المستمر للبيانات المختلفة والسرعة:
SELECT * FROM (SELECT * FROM firm_1 WHERE json_data @@ 'rubrics.@# > 0 AND address_name = * AND contact_groups.#.contacts.#.type = "website"' LIMIT 100) random_hack ORDER BY RANDOM() LIMIT 1;
بهذه الطريقة البسيطة ، لم نفقد أي شيء تقريبًا عند تسارع 20x. وقت تنفيذ هذا الطلب هو 180 مللي ثانية.
لا تكرر أخطائنا: هذه اللحظة بالطبع بالكاد يمكن وصفها بالخطأ. إذا كان لديك بالفعل الكثير من الاختبارات ، فكر دائمًا في مدى حاجتك العشوائية في البيانات. ساعدتنا المفاضلة بين سرعة تنفيذ الاستعلام في قاعدة البيانات وتفرد التحديد في تسريع استعلامات SQL بمقدار 20 مرة.
مرة أخرى قائمة قصيرة من الإجراءات:
- إذا حددنا عدة شروط لتحديد البيانات في حقل JSONB ، فيجب إدراجها في عامل تشغيل واحد "".
- إذا أنشأنا بيانات اختبار ، فتأكد من حذفها. حتى لو بدا أن وجودهم لا يؤثر على وظائف التطبيق.
- إذا كنت بحاجة إلى بيانات عشوائية لكل تشغيل ، فإننا نجد حلًا وسطًا بين تفرد العينة وسرعة التنفيذ.
قمنا بتسريع الانحدار ثلاث مرات بفضل التعديلات البسيطة (والبعض ، ربما الواضحة). تمر اختبارات 25K الآن في 10 دقائق. وهذا ليس الحد - فنحن نحسن الشفرة بعد ذلك. من غير المعروف عدد الاكتشافات غير المتوقعة التي تنتظرنا هناك.