في الواقع ، لم أكن أنوي رؤية اللون الذي كانت عليه الشجاعة. لقد التقطت مشروع هواية في Go ، وذهبت إلى GitHub لرؤية حالة fasthttp: هل يتطور؟ حسنا ، على الأقل معتمدة؟ نشأ. ذهبت ، بدا حيث يجلس fasthttp في معايير TechEmpower . أنا أنظر: وهناك fasthttp بالكاد يظهر نصف ما ينجح القائد - إلى بعض أكتيكس على بعض الصدأ. يا له من ألم.
وهنا أضع ذراعيّ ، وأضرب رأسي على الأرض (ثلاث مرات) وأصرخ: "سبحان الله ، حقًا ، الصدأ إله حقيقي ، كم كنت أعمى من قبل!". ولكن إما أن المقابض لم تنجح ، أو أن الجبين يندم ... وبدلاً من ذلك ، حصلت على رمز الاختبارات المكتوبة في Go واختبارات actix-web في Rust. لفرزها.
بعد بضع ساعات اكتشفت:
- لماذا يحتل إطار actix-web Rust المرتبة الأولى في جميع اختبارات TechEmpower ،
- كيف تبدأ جافا سكريبت.
الآن سوف أخبرك بكل شيء بشكل صحيح.
ما هو مقياس إطار عمل TechEmpower؟
إذا كان إطار الويب يوضح ما إذا كان سيذهب إلى ، أو ، على سبيل المثال ، التفكير في يهمس للأصدقاء "أنا سريع" ، فسوف يقع بالتأكيد في TechEmpower Framework Benchmark. مكان شعبي للذهاب لقياس الأداء.
يحتوي الموقع على تصميم خاص: تنتشر علامات تبويب المرشحات والجولات والظروف والنتائج لأنواع مختلفة من الاختبارات على الصفحة بيد سخية. سخية وكاسحة بحيث لا تلاحظها. لكن الأمر يستحق النقر على علامات التبويب ، والمعلومات التي تقف وراءها مفيدة.
أسهل طريقة هي الحصول على نتائج اختبار النص غير المرغوب فيه ، "Hello World!" لخوادم الويب. عادة ما يقدم مؤلفو الإطار رابطًا له: من المفترض أن نبقى في المئات الأولى. القضية صحيحة ومفيدة. بشكل عام ، إن التخلي عن النص الشرير مفيد للكثيرين ، ويذهب القادة في مجموعة ضيقة.
في مكان قريب ، في علامات التبويب ذاتها ، هي نتائج اختبارات الأنواع الأخرى (السيناريوهات). هناك سبعة منهم ، ويمكن الاطلاع على مزيد من التفاصيل هنا . لا تختبر هذه البرامج النصية فقط كيفية معالجة إطار العمل / النظام الأساسي لمعالجة طلب http بسيط ، ولكن أيضًا اختبار مع عميل قاعدة البيانات أو محرك القالب أو متسلسل JSON.
هناك بيانات الاختبار في بيئة افتراضية ، على الأجهزة المادية. بالإضافة إلى الرسوم البيانية ، هناك بيانات جدولية. بشكل عام ، هناك الكثير من الأشياء المثيرة للاهتمام ، الأمر يستحق التنقيب ، وليس مجرد النظر إلى موضع النظام الأساسي "your".
أول ما يتبادر إلى ذهني بعد اجتياز نتائج الاختبار: "لماذا يختلف كل شيء بشكل كبير عن النص العادي؟!". في النص العادي ، ينتقل الزعماء إلى مجموعة ضيقة ، لكن عندما يتعلق الأمر بالعمل مع قاعدة البيانات ، يؤدي موقع actix-web بهامش كبير. في الوقت نفسه ، فإنه يظهر وقت معالجة طلب مستقر. الشيطان.
آخر الشذوذ: حل جافا سكريبت قوية بشكل لا يصدق. يطلق عليه ex4x. اتضح أن كوده كان أقل بقليل من كتابته بالكامل في جافا. المستخدمة من قبل جافا وقت التشغيل ، JDBC. تتم ترجمة شفرة JavaScript إلى bytecode والغراء في مكتبات Java. أخذوا حرفيا ذلك - وربط البرنامج النصي بجافا. حيل وجوه شاحبة ليس لها حدود.
كيف ننظر إلى الكود وما في الداخل
رمز لجميع الاختبارات على جيثب. كل شيء في مستودع واحد ، وهو مريح للغاية. يمكنك استنساخ ومشاهدة ، يمكنك مشاهدة مباشرة على جيثب. يشتمل الاختبار على أكثر من 300 مجموعة مختلفة من الإطار مع المتسللين ومحركات القوالب وعميل قاعدة البيانات. في لغات البرمجة المختلفة ، مع نهج مختلف للتنمية. عمليات التنفيذ بلغة واحدة قريبة ، ويمكن مقارنتها بالتنفيذ بلغات أخرى. يتم الحفاظ على الكود بواسطة المجتمع ، إنه ليس من عمل شخص أو فريق.
الرمز القياسي هو مكان رائع لتوسيع آفاقك. من المثير للاهتمام تحليل كيف يحل الأشخاص المختلفون نفس المشكلات. لا يوجد الكثير من التعليمات البرمجية ، والمكتبات والحلول المستخدمة سهلة التمييز. لا أشعر بالأسف على الإطلاق لأنني وصلت إلى هناك. لقد تعلمت الكثير. أولا وقبل كل شيء عن الصدأ.
قبل الصدأ ، كان لدي فكرة غامضة للغاية. من المؤكد أن أي مقالة عن C و C ++ و D ، وخاصة Go ، تحتوي على اثنين من المعلقين الذين يشرحون بالتفصيل وبقلق شديد أن الغرور والهراء والغباء مكتوبة في شيء آخر ، طالما أن هناك GASCOGNE الصدأ. في بعض الأحيان يتم ترحيلهم لدرجة أنهم يقدمون أمثلة على الكود أكثر من شخص غير مستعد أو قليل قبول مدفوعة إلى ذهول: "لماذا ، لماذا ، لماذا كل هذه الرموز؟!"
لذلك ، كان فتح الكود مخيفًا.
نظرت. اتضح أنه يمكن قراءة البرامج الموجودة في Rust. علاوة على ذلك ، تتم قراءة الكود جيدًا لدرجة أنني قمت بتثبيت Rust ، وحاول تجميع الاختبار والعبث معه قليلاً.
لقد تخلت عن هذا العمل تقريبًا ، لأن التجميع يستمر لفترة طويلة. وقت طويل جدا. لو كنت D 'Artagnan ، أو حتى مجرد choleric ، كنت قد هرعت إلى Gascony ، وسوف تتبع الآلاف من الشياطين مكتئب. لكنني فعلت ذلك. شربت الشاي مرة أخرى. يبدو أنه حتى كوب واحد: على جهاز الكمبيوتر المحمول ، استغرق التجميع الأول حوالي 20 دقيقة ، ولكن ، كل شيء يسير أكثر متعة. ربما حتى صناديق التحديث الكبيرة القادمة.
ولكن أليس كذلك الصدأ نفسه؟
لا. ليست لغة برمجة.
بالطبع ، الصدأ هي لغة رائعة. قوية ومرنة ، وإن كانت خارجة عن العادة والفعل. لكن اللغة نفسها لن تكتب رمز سريع. اللغة هي إحدى الأدوات ، أحد القرارات التي اتخذها المبرمج.
كما قلت - يتم التخلي عن النص العادي بسرعة من قبل الكثيرين. إن أداء actix-web و fasthttp وعشرات الأطر الأخرى عند معالجة طلب بسيط يمكن مقارنته تمامًا ، أي أن اللغات الأخرى لديها القدرة التقنية على التنافس مع Rust.
Actix-web نفسها ، بالطبع ، هي "اللوم": منتج سريع وعملي وممتاز. التسلسل مناسب ، ومحرك القوالب جيد - كما أنه يساعد كثيرًا.
والجدير بالذكر أن نتائج اختبارات العمل مع قاعدة البيانات تختلف.
بعد البحث قليلاً في الكود ، سلطت الضوء على ثلاثة اختلافات رئيسية (على ما يبدو لي) ساعدت اختبارات أكتيكس على الانفصال عن المنافسين في الاختبارات الاصطناعية:
- خط أنابيب tokio-postgres تشغيل الأنابيب ؛
- استخدام اتصال واحد مع اختبار Rust بدلاً من تجمع اتصال مع اختبار مكتوب في Go ؛
- تحديث مقاييس actix بأمر واحد يتم إرساله عبر استعلام بسيط بدلاً من إرسال أوامر UPDATE متعددة.
أي نوع من وضع ناقل؟
فيما يلي مقتطف من وثائق tokio-postgres (المستخدمة في اختبار مكتبة عملاء PostgreSQL) التي تشرح معنى مطوريها:
Sequential Pipelined | Client | PostgreSQL | | Client | PostgreSQL | |----------------|-----------------| |----------------|-----------------| | send query 1 | | | send query 1 | | | | process query 1 | | send query 2 | process query 1 | | receive rows 1 | | | send query 3 | process query 2 | | send query 2 | | | receive rows 1 | process query 3 | | | process query 2 | | receive rows 2 | | | receive rows 2 | | | receive rows 3 | | | send query 3 | | | | process query 3 | | receive rows 3 | |
لا ينتظر العميل في وضع خطوط الأنابيب (خط أنابيب) استجابة PostgreSQL ، ولكنه يرسل الاستعلام التالي أثناء قيام PostgreSQL بمعالجة الاستعلام السابق. يمكن ملاحظة أنه بهذه الطريقة يمكنك معالجة نفس تسلسل استعلامات قاعدة البيانات بشكل أسرع بكثير.
إذا كان الاتصال في وضع المواسير مزدوج الاتجاه (يوفر إمكانية الحصول على نتائج بالتوازي مع الإرسال) ، فقد يتم تقليل هذا الوقت قليلاً. يبدو أن هناك بالفعل نسخة تجريبية من toko-postgres حيث يتم فتح اتصال مزدوج.
نظرًا لأن عميل PostgreSQL يرسل عدة رسائل (تحليل ، ربط ، تنفيذ ، ومزامنة) إلى كل استعلام SQL يتم إرساله للتنفيذ ، ويتلقى ردًا عليه ، فإن وضع خط الأنابيب سيكون أكثر فعالية حتى عند معالجة الاستعلامات الفردية.
ولماذا لا يوجد في Go؟
لأن الذهاب يستخدم عادة تجمعات اتصال قاعدة البيانات. لا يُقصد بالوصلات أن تستخدم بالتوازي.
إذا قمت بتشغيل نفس استعلامات SQL من خلال مجموعة ، بدلاً من اتصال واحد ، فمن الناحية النظرية ، يمكنك الحصول على وقت تنفيذ أقصر مع عميل تسلسلي عادي مقارنة بالعمل عند اتصال واحد ، سواء كان ذلك ثلاثة أضعاف:
| Connection | Connection 2 | Connection 3 | PostgreSQL | |----------------|----------------|----------------|-----------------| | send query 1 | | | | | | send query 2 | | process query 1 | | receive rows 1 | | send query 3 | process query 2 | | | receive rows 2 | | process query 3 | | | receive rows 3 | |
يبدو أن جلد الغنم (وضع ناقل) لا يستحق كل هذا العناء.
فقط تحت الحمل الكبير ، يمكن أن يكون عدد الاتصالات بخادم PostgreSQL مشكلة.
وما علاقة عدد الاتصالات به؟
النقطة المهمة هنا هي كيف يستجيب خادم PostgreSQL لزيادة عدد الاتصالات.
تعرض المجموعة اليسرى من الأعمدة صعود وهبوط أداء PostgreSQL اعتمادًا على عدد الاتصالات المفتوحة:

( مقتبس من منشور بيركونا )
يمكن ملاحظة أنه مع زيادة عدد الاتصالات المفتوحة ، يتراجع أداء خادم PostgreSQL بسرعة.
بالإضافة إلى ذلك ، فتح اتصال مباشر ليس "مجانيًا". مباشرة بعد فتح العميل يرسل معلومات الخدمة ، "يوافق" مع خادم PostgreSQL على كيفية معالجة الطلبات.
لذلك ، في الممارسة العملية ، يجب أن تحد من عدد الاتصالات النشطة إلى PostgreSQL ، وغالبًا ما تمر بها من خلال pgbouncer أو بعض الأوديسة الأخرى.
إذن لماذا كانت شبكة actix أسرع؟
أولاً ، شبكة اكتيكس نفسها سريعة للغاية. هو الذي يحدد "السقف" ، وهو أعلى قليلاً من الآخر. المكتبات الأخرى المستخدمة (serde، yarde) هي أيضًا مفيدة جدًا. ولكن يبدو لي أنه في اختبارات العمل مع PostgreSQL ، كان من الممكن أن تؤتي ثمارها لأن خادم actix-web يبدأ بسلسلة واحدة على كور المعالج. يفتح كل موضوع اتصالًا واحدًا فقط بـ PostgreSQL.
كلما كانت الاتصالات النشطة أقل ، يعمل PostgreSQL الأسرع (انظر الرسوم البيانية أعلاه).
يسمح لك العميل الذي يعمل في وضع خط أنابيب (tokio-postgres) باستخدام اتصال واحد مع PostgreSQL بفعالية من أجل المعالجة المتوازية لاستفسارات المستخدم. تفريغ معالجات طلب HTTP أوامر SQL الخاصة بهم في قائمة انتظار واحدة وتصطف في آخر لتلقي النتائج. النتائج هز المرح ، والتأخير ضئيل ، والجميع سعداء. الأداء الكلي أعلى من نظام به تجمع اتصال.
لذلك تحتاج إلى التخلي عن التجمع ، وكتابة عميل خط أنابيب PostgreSQL ، وسوف تأتي السعادة والسرعة المذهلة على الفور؟
ربما. ولكن ليس كل ذلك مرة واحدة.
عندما من غير المرجح حفظ وضع الناقل وبالتأكيد لن يتم حفظه
المخطط المستخدم في الكود القياسي لن يعمل مع معاملات PostgreSQL.
في المؤشر ، ليست هناك حاجة للمعاملات ويتم كتابة الكود مع مراعاة أنه لن تكون هناك معاملات. في الممارسة العملية ، فإنها تحدث.
إذا فتح رمز الواجهة الخلفية معاملة PostgreSQL (على سبيل المثال ، لإجراء تغيير في جدولين مختلفين الذري) ، فسيتم تنفيذ جميع الأوامر المرسلة عبر هذا الاتصال داخل هذه المعاملة.
نظرًا لاستخدام الاتصال مع PostgreSQL بشكل متوازٍ ، فكل شيء يقع فيه. يتم خلط الأوامر التي يجب تنفيذها في معاملة كما صممها المطور مع أوامر sql التي بدأتها معالجات طلب HTTP المتوازية. سنتلقى فقدان البيانات العشوائية ومشاكل في سلامتها.
مرحبا المعاملة - وداعا الاستخدام الموازي للاتصال واحد. يجب عليك التأكد من عدم استخدام الاتصال بواسطة معالجات طلب http الأخرى. ستحتاج إما إلى إيقاف معالجة طلبات HTTP الواردة قبل إغلاق المعاملة ، أو استخدام تجمع للمعاملات ، وفتح عدة اتصالات بخادم قاعدة البيانات. هناك العديد من تطبيقات البلياردو لصدأ ، وليس واحدة. علاوة على ذلك ، فهي موجودة في Rust بشكل منفصل عن تطبيق عميل قاعدة البيانات. يمكنك الاختيار حسب الذوق أو اللون أو الرائحة أو بشكل عشوائي. الذهاب لا يعمل بهذه الطريقة. قوة الأدوية ، نعم.
نقطة مهمة: في الاختبار ، رمز نظرت إليه ، المعاملات لا تفتح. هذا السؤال هو ببساطة لا يستحق كل هذا العناء. تم تحسين رمز الاختبار لمهمة محددة وظروف تشغيل تطبيق محددة للغاية. ربما تم اتخاذ قرار استخدام اتصال واحد لكل دفق خادم بوعي واتضح أنه فعال للغاية.
هل هناك أي شيء آخر مثير للاهتمام في الكود المرجعي؟
نعم.
يتم توضيح سيناريو قياس الأداء بتفصيل كبير. بالإضافة إلى المعايير التي يجب أن تفي بها الكود المشارك في الاختبارات. واحد منهم هو أن جميع الاستعلامات إلى خادم قاعدة البيانات يجب أن تنفذ بالتتابع.
يبدو جزء التعليمات البرمجية التالي (مختصر قليلاً) أنه لا يفي بالمعايير:
let mut worlds = Vec::with_capacity(num);
كل شيء يبدو وكأنه إطلاق نموذجي للعمليات الموازية. ولكن ، نظرًا لاستخدام اتصال PostgreSQL ، يتم إرسال الاستعلامات إلى خادم قاعدة البيانات بالتتابع. واحدا تلو الآخر. كما هو مطلوب. لا جريمة.
لماذا هذا حسنًا ، أولاً ، في الكود (تم تقديمه في مكتب التحرير ، والذي كان يعمل في الجولة الثامنة عشرة) لم يتم استخدام المزامنة / الانتظار حتى الآن ، فقد ظهر في راست لاحقًا. ومن خلال العقود الآجلة ، من الأسهل إرسال استعلامات SQL "بالتوازي" - كما في الكود أعلاه. يسمح لك هذا بالحصول على بعض تحسينات الأداء الإضافية: بينما يقبل PostgreSQL ويعالج أول استعلام SQL ، يتم تغذية الباقي به. لا ينتظر خادم الويب نتيجة كل منهما ، ولكنه ينتقل إلى مهام أخرى ويعود إلى معالجة طلب http فقط عند اكتمال جميع استعلامات SQL.
بالنسبة إلى PostgreSQL ، فإن المكافأة هي أن نفس نوع الاستعلام في نفس السياق (اتصال) يذهب في صف واحد. تزداد احتمالية عدم إعادة بناء خطة الاستعلام.
اتضح أن مزايا وضع خط الأنابيب (انظر الشكل من وثائق tokoio postgres) يتم استغلالها بالكامل حتى عند معالجة طلب http واحد.
ماذا بعد؟
باستخدام بروتوكول استعلام بسيط للتحديثات الدفعية
يسمح بروتوكول الاتصال بين العميل وخادم PostgreSQL بطرق بديلة لتنفيذ أوامر SQL. يتضمن بروتوكول (الاستعلامات الموسعة) المعتادة إرسال عدة رسائل للعميل: تحليل وربط وتنفيذ وتزامن. البديل هو بروتوكول Simple Query ، والذي بموجبه رسالة واحدة كافية لتنفيذ أمر والحصول على النتائج - Query.
الفرق الرئيسي بين البروتوكول المعتاد هو نقل معلمات الطلب: يتم إرسالها بشكل منفصل عن الأمر نفسه. إنه أكثر أمانًا. يفترض البروتوكول المبسط أنه سيتم تحويل جميع معلمات استعلام SQL إلى سلسلة وإدراجها في نص الاستعلام.
كان أحد الحلول المثيرة للاهتمام المستخدمة في مقاييس actix-web هو تحديث العديد من إدخالات الجدول بأمر واحد يتم إرساله عبر بروتوكول Simple Query.
وفقًا للمعيار ، عند معالجة طلب المستخدم ، يجب على خادم الويب تحديث عدة سجلات في الجدول ، وكتابة أرقام عشوائية. من الواضح أن تحديث السجلات بالتتابع مع استعلامات متسلسلة يستغرق وقتًا أطول من استعلام واحد يقوم بتحديث جميع السجلات مرة واحدة.
يبدو الطلب الذي تم إنشاؤه في رمز الاختبار كالتالي:
UPDATE world SET randomnumber = temp.randomnumber FROM (VALUES (1, 2), (2, 3) ORDER BY 1) AS temp(id, randomnumber) WHERE temp.id = world.id
حيث (1, 2), (2, 3)
هي أزواج معرف الخط / القيمة الجديدة للحقل العشوائي.
عدد السجلات التي تم تحديثها متغير ، وإعداد الطلب (PREPARE) مقدمًا غير منطقي. نظرًا لأن بيانات التحديث عددية ، ويمكن الوثوق بالمصدر (رمز الاختبار نفسه) ، فلا يوجد خطر من حقن SQL ، حيث يتم تضمين البيانات ببساطة في نص SQL ويتم إرسال كل شيء باستخدام بروتوكول Simple Query.
يشاع بسيط الاستعلام حولها. التقيت توصية: "العمل فقط على بروتوكول Simple Query ، وسيكون كل شيء سريعًا وجيدًا." أدركها بقدر كبير من الشكوك. يتيح لك Simple Query تقليل عدد الرسائل المرسلة إلى خادم PostgreSQL عن طريق نقل معالجة معلمات الاستعلام إلى جانب العميل. يمكنك مشاهدة مكاسب الاستعلامات التي تم إنشاؤها ديناميكيًا مع عدد متغير من المعلمات. بالنسبة للنوع نفسه من استعلامات SQL (الأكثر شيوعًا) ، فإن الكسب غير واضح. حسنًا ، ومدى أمان معالجة معلمات الاستعلام ، في حالة تطبيق الاستعلام البسيط ، فإنه يحدد تنفيذ مكتبة العميل.
كما كتبت أعلاه ، في هذه الحالة ، يتم إنشاء نص استعلام SQL بشكل ديناميكي ، البيانات رقمية ويتم إنشاؤها بواسطة الخادم نفسه. مزيج مثالي للاستعلام البسيط. ولكن حتى في هذه الحالة ، فإن الأمر يستحق اختبار الخيارات الأخرى. تعتمد البدائل على نظام PostgreSQL الأساسي والعميل: pgx (العميل لـ Go) يجعل من الممكن إرسال حزمة من الأوامر ، JDBC - لتنفيذ أمر واحد عدة مرات متتالية مع معلمات مختلفة. يمكن تشغيل كلا الحلين بنفس السرعة أو حتى أسرع.
فلماذا يؤدي الصدأ؟
القائد ، بالطبع ، ليس صدأ. الاختبارات القائمة على actix-web تقود - إنه هو الذي يحدد "سقف" الأداء. هناك ، على سبيل المثال ، الصواريخ والحديد ، التي تشغل مواقع متواضعة. ولكن في الوقت الحالي ، فإن actix-web هو الذي يحدد إمكانية استخدام Rust في تطوير الويب. بالنسبة لي ، فإن الإمكانات مرتفعة للغاية.
خادم "سري" آخر غير واضح ولكنه مهم يستند إلى actix-web ، والذي سمح ليحتل المرتبة الأولى في جميع مقاييس TechEmpower - في كيفية عمله مع PostgreSQL:
- يتم فتح اتصال واحد فقط مع PostgreSQL لكل تيار خادم ويب. يستخدم هذا الاتصال وضع خط الأنابيب ، والذي يسمح باستخدامه بفعالية للمعالجة المتوازية لطلبات المستخدم.
- كلما قل عدد الاتصالات النشطة ، كانت استجابة PostgreSQL أسرع. تزداد سرعة معالجة طلبات المستخدم. في الوقت نفسه ، يعمل النظام بالكامل تحت الحمل أكثر ثباتًا (التأخيرات في معالجة الطلبات الواردة أقل وتزداد ببطء).
عندما تكون السرعة مهمة ، من المحتمل أن يكون هذا الخيار أسرع من استخدام المضاعفات (مثل pgbouncer و odyssey). وبالتأكيد كان أسرع في المعايير.
من المثير للاهتمام للغاية كيف ستؤثر المزامنة / الانتظار ، التي ظهرت في Rust ، والدراما الحديثة مع actix-web على شعبية Rust في تطوير الويب. من المثير للاهتمام أيضًا كيف ستتغير نتائج الاختبار بعد معالجتها في حالة التزامن / الانتظار.