تسريع SQLAlchemy لرواد الفضاء المعماريين


هبر ، هذا تقرير صادر عن مهندس البرمجيات أليكسي ستاركوف في مؤتمر موسكو بايثون كونفوكس ++ 2018 في موسكو. فيديو في نهاية المنشور.
مرحبا بالجميع! اسمي أليكسي ستاركوف - هذا أنا ، في أفضل سنواتي ، أعمل في مصنع.
الآن أعمل في مختبرات Qrator. في الأساس ، طوال حياتي ، درست C و C ++ - أنا أحب Alexandrescu ، The Gang of Four ، مبادئ SOLID - هذا كل شيء. مما يجعلني رائد فضاء معماري. لقد كنت أكتب Python في العامين الماضيين لأنني أحبها.

في الواقع ، من هم "رواد الفضاء المعماريون"؟ في المرة الأولى التي التقيت فيها بهذا المصطلح مع جويل سبولسكي ، ربما قرأته. ويصف "رواد الفضاء" بأنهم أناس يريدون بناء بنية مثالية ، يعلقون التجريد ، أكثر من التجريد ، أكثر من التجريد ، الذي أصبح أكثر وأكثر عمومية. في النهاية ، ترتفع هذه المستويات بحيث تصف جميع البرامج الممكنة ، لكنها لا تحل أي مشاكل عملية. في هذه اللحظة ، ينفد "رائد الفضاء" (هذه هي المرة الأخيرة التي يحيط فيها هذا المصطلح بعلامات اقتباس) من الهواء ويموت.

ولدي أيضًا ميول نحو استكشاف الفضاء المعماري ، ولكن في هذا التقرير سأتحدث قليلاً عن مدى عضلي ولم يسمح لي ببناء نظام بالأداء الضروري. الشيء الرئيسي هو كيف تغلبت عليه.

ملخص تقريري: كان / كان.



زيادة آلاف وملايين المرات. عندما قدمت هذه الشريحة ، كان الفكر الوحيد الذي كان لدي هو "كيف؟"



أين يمكنني أن أفسد كثيرا؟ إذا كنت لا تريد أن تفسد مثلي - تابع القراءة.



سأتحدث عن نظام التكوين. نظام التكوين هو أداة داخلية في مختبرات Qrator تقوم بتخزين التكوينات للشبكة المعرفة بالبرمجيات (SDN) - شبكة التصفية الخاصة بنا. وتلتزم بمزامنة التكوين بين المكونات ومراقبة وضعها.



ما هي باختصار؟ لدينا قاعدة بيانات تخزن لقطة لتكويننا للشبكة بأكملها ، ولدينا خادم يعالج الأوامر الواردة إليه ويغير التكوين بطريقة أو بأخرى.

يأتي المسؤولون الفنيون والعملاء لدينا إلى هذا الخادم وعبر وحدة التحكم ، من خلال واجهات برمجة تطبيقات نقطة النهاية ، وواجهات برمجة تطبيقات REST ، و JSON RPC وأوامر إصدار الأشياء الأخرى إلى الخادم ، ونتيجة لذلك يغير تكويننا.

يمكن أن تكون الفرق إما بسيطة جدًا أو أكثر تعقيدًا. بعد ذلك ، لدينا مجموعة معينة من أجهزة الاستقبال التي تشكل SDN الخاص بنا ويدفع الخادم التكوين إلى أجهزة الاستقبال هذه. يبدو هذا بسيطًا جدًا. في الأساس ، سأتحدث عن هذا الجزء.



لأنها هي التي ترتبط بقاعدة البيانات والكيمياء.



ما هي خصوصية هذا النظام؟ إنه صغير للغاية - متوسط. يتم تخزين مئات الآلاف ، حتى الملايين ، من الكيانات في قاعدة البيانات هذه. تكمن الخصوصية في أن الرسم البياني للعلاقات بين الكيانات معقد للغاية. هناك العديد من التسلسلات الهرمية للوراثة بين الكيانات ، وهناك شوائب ، وهناك ببساطة تبعيات بينها. يتم تحديد جميع هذه القيود من خلال منطق الأعمال ويجب أن نلتزم بها.

تبلغ نسبة طلبات الكتابة لقراءة الطلبات حوالي 15: 1. هنا من الواضح: هناك الكثير من الأوامر لتغيير التكوين ومرة ​​واحدة في فترة طويلة معينة من الوقت لدينا دفع التكوين إلى نقاط النهاية.

يتم استخدام MySQL داخليًا - وهو متاح أيضًا في منتجات أخرى لشركتنا ، ولدينا خبرة جادة جدًا في قاعدة البيانات هذه ، وهناك أشخاص يمكنهم العمل معها: بناء مخطط بيانات واستعلامات تصميم وكل شيء آخر. لذلك ، أخذنا MySQL كقاعدة بيانات علائقية عالمية.



ماذا كانت المشكلة بعد تصميم هذا النظام؟ استغرق تنفيذ أمر واحد من ثانية واحدة إلى ثلاثين ثانية ، اعتمادًا على مدى تعقيد الفريق. وعليه ، بلغ التأخير في التنفيذ خمس دقائق. وصل فريق واحد - 30 ثانية ، والثاني وهكذا ، كومة من المتراكمة - تأخير لمدة 5 دقائق.

التأخير في تطبيق التكوين يصل إلى عشر دقائق. تقرر أن هذا لم يكن كافياً بالنسبة لنا وأن التحسين كان ضروريًا.



أولاً ، قبل إجراء أي تحسين ، من الضروري إجراء تحقيق ومعرفة ما هو الأمر في الواقع.



كما اتضح ، فقد افتقرنا إلى أهم عنصر في التحقيق - لم يكن لدينا قياس عن بعد. لذلك ، إذا كنت تصمم نوعًا من النظام ، أولاً ، في مرحلة التصميم ، ضع القياس عن بُعد فيه. حتى لو كان النظام صغيرًا في البداية ، ثم أكثر قليلاً ، ثم أكثر - في النهاية ، يأتي الجميع إلى وضع حيث تحتاج إلى مشاهدة المسارات ، ولكن لا يوجد قياس عن بعد.



ما الذي يمكن فعله بعد ذلك إذا لم يكن لديك قياس عن بعد؟ يمكنك تحليل السجلات. هنا ، تمر النصوص البسيطة إلى حد ما عبر سجلاتنا وتحولها إلى مثل هذا الجدول ، مما يوضح أسرع وأبطأ ومتوسط ​​أوقات تنفيذ الأوامر. بدءًا من هنا ، يمكننا بالفعل معرفة الأماكن التي لدينا فيها الكمامات: أي الفرق تستغرق وقتًا أطول للتنفيذ ، وأيها أسرع.



الشيء الوحيد الذي يجب ملاحظته هو أنه عند تحليل السجلات ، فإننا لا نفكر إلا في وقت تنفيذ هذه الأوامر على الخادم. هذه هي المرحلة الأولى - المرحلة التي تحمل علامة t2. t1 - هذه هي الطريقة التي سيرى بها العميل وقت التنفيذ لفريقنا: الدخول في قائمة الانتظار ، والانتظار ، والتنفيذ على الخادم. ستكون هذه المرة أطول ، لذلك نقوم بتحسين الوقت t2 ، ثم نستخدم الوقت t1 لتحديد ما إذا كنا قد وصلنا إلى الهدف.

t1 هو مقياس الجودة لأدائنا.



وفقًا لذلك ، هذه هي الطريقة التي حددنا بها جميع الفرق - أي أخذنا السجل من الخادم ، ودفعناه من خلال النصوص البرمجية الخاصة بنا ، وبحثنا وحددنا المكونات التي تعمل بشكل أبطأ. تم بناء الخادم بشكل نمطي ، ومكون منفصل مسؤول عن كل أمر ، ويمكننا تعريف المكونات بشكل فردي - ووضع معايير لها. إذن لدينا هنا فئة - لكل مكون إشكالي كتبنا فيه قمنا في code_under_test () ببعض الأنشطة التي تصور الاستخدام القتالي للمكون. وكانت هناك طريقتان: الملف الشخصي () والمقعد (). المكالمات الأولى cProfile ، تظهر عدد المرات التي تم استدعاؤها ، حيث توجد الاختناقات.

تم تشغيل مقاعد البدلاء () عدة مرات واعتبرت مقاييس مختلفة لنا - هذه هي الطريقة التي قيمنا بها الأداء.

ولكن اتضح أن هذه ليست المشكلة!



كانت المشكلة الرئيسية في عدد استعلامات قاعدة البيانات. كان هناك الكثير من الطلبات ، ولفهم سبب وجود الكثير ، دعنا ننظر إلى كيفية تنظيم كل شيء.



أمامنا قطعة من دائرة بسيطة تمثل مستقبلاتنا ، مقدمة على شكل فئة المتلقي. هم متحدون في بعض المجموعة - مجموعة المتلقي. وبالتالي ، هناك بعض المستويات التكوينية - شرائح التكوين ، وهي مجموعة فرعية من التكوينات المسؤولة عن "دور" واحد لهذا المستقبل. على سبيل المثال ، لتوجيه - مستوى التوجيه. يمكن توصيل السهول المزودة بأجهزة الاستقبال بأي ترتيب - أي أن هذه علاقة علاقة بأطراف.

هذه قطعة من الخطوط العريضة التي أعرضها هنا حتى يمكن فهم الأمثلة بشكل أكبر.

ماذا يريد كل رائد فضاء معماري أن يفعل عندما يرى واجهة برمجة التطبيقات لشخص آخر؟ إنه يريد إخفاءها وتجريدها وكتابة واجهته حتى يتمكن من إزالة واجهة برمجة التطبيقات هذه أو بالأحرى إخفاءها.



وفقًا لذلك ، هناك واجهة برمجة تطبيقات "قذرة" للكيمياء ، حيث يوجد ، في الواقع ، مصممو خرائط وفئتنا "الخالصة" - جهاز الاستقبال ، حيث يتم تخزين بعض التكوين وهناك طرق: تحميل () ، حفظ () ، حذف (). وجميع الفئات الأخرى المرتبطة بها. نحصل على رسم بياني لكائنات Python ، متصلة بطريقة ما مع بعضها البعض - لكل منها طريقة load () ، save () ، delete () ، والتي تشير إلى مخطط الخيمياء ، والذي بدوره يستدعي API.



التنفيذ هنا بسيط للغاية. لدينا طريقة تحميل تقوم بعمل استعلام لقاعدة البيانات ولكل كائن مستلم يقوم بإنشاء كائن Python الخاص به. هناك طريقة حفظ تقوم بالعملية المعاكسة - تبدو إذا كان هناك كائن في قاعدة البيانات باستخدام المفتاح الأساسي ، إذا لم يكن كذلك - فهي تنشئ وتضيف ، ثم نقوم بحفظ حالة هذا الكائن. حذف على المفتاح الأساسي يتلقى ويحذف الكائن من قاعدة البيانات.



المشكلة الرئيسية مرئية على الفور - هذا هو رسم الخرائط. أولاً ، نقوم بذلك مرة واحدة من كائن Python إلى المخطط ، ثم المخطط إلى القاعدة. التعيين الإضافي هو مكالمة واحدة أو مكالمتين ، والتي قد لا تكون مخيفة حتى الآن. كانت المشكلة الرئيسية تزامن يدوي. لدينا شيئان من الواجهة "النظيفة" وأحدهما يغير السمة - كيف نرى أن السمة تغيرت في الأخرى؟ مستحيل. من الضروري دمج التغييرات في قاعدة البيانات والحصول على السمة في كائن آخر. بالطبع ، إذا علمنا أن الأشياء موجودة في نفس السياق ، فيمكننا تتبع ذلك بطريقة أو بأخرى. ولكن إذا كان لدينا جلستان في أماكن مختلفة - فقط من خلال القاعدة ، أو حجب القاعدة في الذاكرة ، وهو ما لم نفعله.

هذا التحميل / الحفظ / الحذف هو مخطط آخر يكرر تمامًا الدواخل من الخيمياء ، والتي تمت كتابتها بشكل جيد واختبارها. هذه الأداة عمرها سنوات عديدة ، وهناك الكثير من المساعدة على الإنترنت عليها وتكرارها ليس جيدًا أيضًا.

انظر الرمز في الزاوية اليمنى العليا؟ لذا سأضع علامة على الشرائح التي يتم فيها عمل شيء ما من أجل "النقاء" ، لزيادة مستوى التجريد ، لعلماء الفضاء المعماريين. أي أن الشرائح التي لا تحتوي على هذا الرمز تكون عملية ومملة وغير مثيرة للاهتمام ولا يمكن قراءتها.

ماذا تفعل إذا كان الكثير من الاستفسارات بطيئة. كم؟ في الواقع الكثير. تخيل سلسلة من الميراث: كائن واحد ، لديه أحد الوالدين ، أن أحدهما لديه والد آخر. نقوم بمزامنة الكائن الفرعي - للقيام بذلك ، تحتاج أولاً إلى مزامنة الوالدين. لمزامنة أحد الوالدين ، تحتاج إلى مزامنة أحد الوالدين. حسنًا ، تمت مزامنة الجميع. في الواقع ، اعتمادًا على كيفية إنشاء الرسم البياني ، يمكننا المشي ومزامنة جميع هذه الكائنات مائة مرة - وبالتالي عدد كبير من الطلبات.



ماذا فعلنا؟ أخذنا كل منطق أعمالنا وتمسكنا به في مخطط الخرائط. تم دمج جميع الكائنات الأخرى هنا أيضًا مع مصممي الخرائط ، وتبين أن واجهة برمجة التطبيقات بالكامل ، وهي طبقة تجريد البيانات بالكامل ، متسخة.



هذه هي الطريقة التي تبدو بها في Python - مصمم الخرائط لديه نوع من منطق الأعمال ، هناك وصف تعريفي لهذه اللوحة هناك. يتم سرد الأعمدة والعلاقات. هنا لدينا مثل هذه الفئة.



بالطبع ، من وجهة نظر أي رائد فضاء ، فإن واجهة برمجة التطبيقات القذرة هي عيب. منطق الأعمال في وصف تعريفي للقاعدة. يتم خلط المخططات مع منطق الأعمال. Phew. قبيح.

وصف الدائرة مشوشة. هذه مشكلة في الواقع - إذا لم يكن منطق الأعمال يحتوي على سطرين ، ولكن يحتوي على حجم أكبر ، فعندئذٍ نحتاج في هذا الفصل إلى التمرير أو البحث لفترة طويلة جدًا للوصول إلى أوصاف محددة. قبل ذلك ، كان كل شيء جميلًا: في مكان واحد وصف القاعدة ، التعريفي ، وصف المخططات ، في منطق عمل آخر. ثم هناك فوضى في الدائرة.

ولكن ، من ناحية أخرى ، نحصل على الفور على آليات الخيمياء: وحدة العمل ، والتي تسمح لك بتتبع الأشياء التي تكون قذرة وأي مرحلات يجب تحديثها ؛ نحصل على علاقة تسمح لنا بالتخلص من الأسئلة الإضافية في قاعدة البيانات ، دون التأكد من ملء المجموعات ذات الصلة ؛ وخريطة الهوية التي ساعدتنا أكثر من غيرها. تضمن خريطة الهوية أن كائنين Python سيكونان نفس كائن Python إذا كان لهما نفس المفتاح الأساسي.

وفقًا لذلك ، قمنا على الفور بتقليل التعقيد إلى خطي.



هذه نتائج وسيطة. زاد الأداء على الفور بمقدار 10 مرات ، وانخفض عدد الاستفسارات إلى قاعدة البيانات بنحو 40-80 مرة وارتفع RPS إلى 1-5. حسنًا ، جيد. لكن واجهة برمجة التطبيقات قذرة. ما يجب القيام به



ميكسين. نأخذ منطق الأعمال ، ومرة ​​أخرى نزيله من مخططنا ، ولكن حتى لا يكون هناك رسم خرائط مرة أخرى ، فسوف نرث مخططنا داخل الكيمياء من مزيجنا. لماذا لا العكس؟ لن ينجح هذا في الخيمياء ، ستقسم وتقول: "لديك فصلين مختلفين يشيران إلى جهاز لوحي واحد ، لا يوجد تعدد الأشكال - اذهب من هنا." وهكذا - من الممكن.

وبالتالي ، لدينا وصف تعريفي في مخطط الخرائط ، الموروث من mixin ويتلقى كل منطق الأعمال. مريح للغاية. وبقية الطبقات هي نفسها تمامًا. يبدو - رائع ، كل شيء نظيف. ولكن هناك تحذير واحد - تبقى الاتصالات والمرحلات داخل الكيمياء ، وعندما يكون لدينا ، على سبيل المثال ، الانضمام من خلال جدول ثانوي للوحة وسيطة ، فإن مصمم هذه اللوحة سيكون موجودًا بطريقة أو بأخرى في كود العميل ، وهو ليس جميلًا جدًا.

لم يكن الخيمياء إطارًا جيدًا ومشهورًا إذا لم يعطني الفرصة لمحاربة هذا.



كيف يبدو المزيج. لديه منطق الأعمال ، ومصممي الخرائط بشكل منفصل ، ووصف تعريفي للوحة. تبقى الاتصالات داخل الكيمياء ، ولكن منطق الأعمال منفصل.

كيف يبدو المخطط العام؟



لدينا ملف به مخطط يتم فيه تجميع جميع فئاتنا التعريفية - دعنا نسميها schema.py. ولدينا كيانات في منطق الأعمال بشكل منفصل. وهذه الكيانات موروثة داخل ملف المخطط - نكتب فئة منفصلة لكل كيان ، ونرثها في المخطط. وبالتالي ، فإن منطق الأعمال يكمن في كومة واحدة ، والمخطط في آخر ويمكن تغييرها بشكل مستقل.



كمثال على التحسين ، سننظر في مخطط بسيط من تصنيفين: أجهزة الاستقبال (جدول المتلقي) وشرائح التكوين (جدول المتلقي). ترتبط العديد من شرائح التكوين مع تسمية جهاز الاستقبال. لا يوجد شيء معقد بشكل خاص.

من أجل إخفاء العلاقات داخل الواجهة "القذرة" للكيمياء ، نستخدم العلاقات والمجموعات.



تسمح لنا بإخفاء مصممي الخرائط من كود العميل.



على وجه الخصوص ، هناك مجموعتان مفيدتان للغاية هما Association_proxy و attrib_mapped_collection. نستخدمها معًا. كيف تعمل العلاقة الكلاسيكية في الخيمياء: لدينا علاقة - هذه مجموعة معينة ، قائمة ، مصممي خرائط. يعد مصممو الخرائط كائنات علاقة بعيدة المدى. يتيح لك Attribute_mapped_collection استبدال هذه القائمة بإملاء ، والمفاتيح التي ستكون فيها بعض سمات مصممي الخرائط ، والقيم هي مصممي الخرائط أنفسهم.

هذه هي الخطوة الأولى.



الخطوة الثانية ، نحن نقوم ب union_proxy على هذه العلاقة. يسمح لنا بعدم تمرير مخطط إلى المجموعة ، ولكن لتمرير بعض القيمة التي سيتم استخدامها لاحقًا لتهيئة مخططنا ، ReceiverPlanes.

هنا لدينا لامدا ، حيث نمرر المفتاح والقيمة. يتحول المفتاح إلى اسم شريحة التكوين ، والقيمة إلى قيمة شريحة التكوين. نتيجة لذلك ، في رمز العميل ، يبدو كل شيء على هذا النحو.



لقد وضعنا نوعًا من الإملاء في نوع من القاموس. كل شيء يعمل: لا مصممي خرائط ولا كيمياء ولا قواعد بيانات.

صحيح ، هناك مطبات.



إذا قمنا بتعيين قيم مختلفة ، أو حتى واحدة ، لنفس المفتاح مرتين - يتم استدعاء لامدا لكل عنصر محدد ، يتم إنشاء كائن - مخطط. واعتمادًا على كيفية هيكلة المخطط ، يمكن أن يؤدي هذا إلى عواقب مختلفة ، من "الانتهاكات العادلة للثوابت" إلى العواقب غير المتوقعة. على سبيل المثال ، قمت بحذف كائن ما من المجموعة ، ولكنه لا يزال موجودًا: قمت بحذف عنصر واحد فقط. عندما بدأت لأول مرة ، قتلت الكثير من الوقت في مثل هذه الأشياء.

ومزامنة صغيرة ضمنية. قد يتأخر Association_proxy و attrib_mapped_collection قليلاً: عندما نقوم بإنشاء كائن مخطط ، تتم إضافته إلى قاعدة البيانات ، ولكنه غير موجود بعد في سمة المجموعة. ستظهر هناك فقط عندما تنتهي صلاحية السمة في هذه الجلسة. عند انتهاء صلاحيتها ، ستحدث مزامنة جديدة مع قاعدة البيانات وستصل إلى هناك.

للتغلب على هذا ، استخدمنا مجموعاتنا الخاصة المكتوبة ذاتيا. هذه ليست حتى كيمياء - يمكنك ببساطة إنشاء مجموعتك الخاصة للتغلب على كل هذا.



يوجد كود أكثر ويتم إبراز الجزء الأكثر أهمية. لدينا مجموعة معينة ترث من رسم الخرائط القابلة للتغيير - هذا إملاء ، يمكنك من خلال مفاتيح تغيير القيم. وهناك طريقة _get_plane_obj - للحصول على كائن شريحة التكوين.

هنا نقوم بأشياء بسيطة - نحاول الحصول عليها بالاسم ، بواسطة مفتاح أساسي ، وإذا لم يكن كذلك ، فإننا نقوم بإنشاء هذا الكائن وإعادته.

بعد ذلك ، نعيد تعريف طريقتين فقط: __setitem__ و __getitem__
في __setitem__ ، نضع هذه الكائنات في مجموعتنا ، في علاقة. الشيء الوحيد هو أننا نعين قيمة في النهاية. وبالتالي ، فإننا ننفذ نفس الآلية التي تستخدمها Association_proxy - تمرير القيمة ، والإملاء هناك ، ويتم تعيينها للسمة المقابلة.

__getitem__ يقوم بمعالجة عكسية. يتلقى عن طريق مفتاح بعض الكائنات من التتابع ويعيد صفته. هناك أيضًا مشكلة صغيرة هنا - إذا قمت بتخزين المجموعة في ذاكرة التخزين المؤقت ، فمن الممكن الخروج من المزامنة قليلاً. لأنه عندما تنتهي صلاحية سمة المجموعة في الكيمياء ، يتم استبدال المجموعة بأخرى ، بعد انتهاء صلاحيتها. لذلك ، يمكننا الاحتفاظ بالإشارة إلى المجموعة القديمة ولا نعلم أن القديم قد انتهى وظهرت مجموعة جديدة بالفعل. لذلك ، في الجزء الأخير ، ننتقل مباشرة إلى مثيل الخيمياء ، ومرة ​​أخرى نحصل على المجموعة من خلال __getattr__ ونقوم __getitem__ معها. أي أننا لا نستطيع تخزين مجموعة الطائرات في ذاكرة التخزين المؤقت هنا.



كيف تصفع هذه المجموعة على مزيجنا؟ كالعادة - قم بإعداد سمة مجموعة. المكان الوحيد المثير للاهتمام هو أنه عندما نقوم بتحميل مثيل من قاعدة البيانات ، لا يتم استدعاء طريقة __init__. يتم استبدال جميع السمات السابقة.

يعطي Alchemy مصمم إعادة بناء قياسي ، والذي يسمح لك بوضع علامة على بعض الطرق على أنها تسمى بعد تحميل كائن من قاعدة البيانات. وفي وقت التمهيد ، يتعين علينا تهيئة مجموعتنا. النفس هي فقط تلك الحالة. الاستخدام هو نفسه تمامًا كما في المثال السابق.



ولكن في مخططنا ، لا تزال آذان قاعدة البيانات مرئية - وهذا هو التكوين. ما نوع التكوين؟ هل هو varchar أم هو blob؟ في الواقع ، العميل غير مهتم. . .



. IPAddress varchar'. TypeDecorator, , , -, , -, : process_bind_param process_result_value, .

address IPAddress. , . … , varchar(45), . - IP-, .

, .



— . , , . - , — , . , .



. — , , , «», « », « », «»; — , , sql- , , , .

, . , . , , .



. :
on_before_flush — , sql- , , , . , ? , - . , — . is_modified — , . , , -, — - . , , — , , - , , , - .

, , , , . , , — dirty_instances, .

— before_commit. : flush', flush — flush.

, , session.dirty_instances . flush, flush' .

after_commit, after_soft_rollback — , .

, — install_handler . , .



. , — 30-40 . , - , 200 , RPS. .



. , , . , . 30 — ! ( )

, 30 . — , — .



. ?

, . — sql , SQLAlchemy ore. — ORM, . , alchemy core sql — , core . sql — sql.
, core . — , . dbapi .

, , . , ORM , — , , , , , , — , , — , .

, . , — core . , , - , , … , . Core , .

, .



. __table__, core. — , , , , , . iterable, - , . , .



. 2-4 , 14 RPS 10-15. .



.
, — , .
SQLA ORM — , , , , .
, — SQLA Core. , raw SQL, . , , , — Core . إنه مريح للغاية.

, .

Source: https://habr.com/ru/post/ar430818/


All Articles