Жақында мен сайтты оңтайландыруға көп уақыт бөлдім, енді бұл туралы айтқым келеді.
Бұл мақала QuerySet жүйесінде
select_related
және
prefetch_related
әдістерінің қолданылуын және олардың айырмашылықтарын түсіндіреді. Мен сондай-ақ Джанго неге баяу деп саналатынын және неге ол әлі де емес екенін түсіндіруге тырысамын. Әрине, Django Flask-қа қарағанда әлдеқайда баяу, бірақ көптеген жобаларда мәселе Джангоның өзінде емес, дерекқор сұрауын оңтайландырудың жоқтығында.
Сондықтан EVILEG веб-сайтының форум бетін оңтайландырайық. Бұл бізге Джанго Silk батареясы көмектеседі, ол дерекқорға сұраныстардың санын өлшеуге, сондай-ақ олардың ұзақтығын өлшеуге қызмет етеді.
Django Silk орнатыңыз және конфигурациялаңыз
Django Silk орнатыңыз
pip install django-silk
Оны INSTALLED_APPS қолданбасына қосыңыз
INSTALLED_APPS = [ ... 'silk' ]
сонымен қатар ORTA WARE қосыңыз
MIDDLEWARE = [ ... 'silk.middleware.SilkyMiddleware', ... ]
Сондай-ақ, сұрау статистикасын көру үшін django-silk сайтынан URL мекенжайларын қосу керек.
from django.urls import path, include urlpatterns = [ path('silk/', include('silk.urls', namespace='silk')) ]
Ал соңғы қадам - джанго-жібек миграциясын қолдану.
python manage.py migrate
Джанго Silk туралы ескертпелер
Өндіріс серверінде Django Silk қолданбаңыз. Кем дегенде осы мақалада көрсетілген параметрлермен. Егер сізде сайтта жақсы трафик болса, мысалы, күніне 1400 адам, онда осы параметрлермен Django Silk сіздің барлық ресурстарыңызды жейді. Сондықтан тек әзірлеу серверінде тәжірибе жасаңыз.
Оңтайландыру
Алдымен форумның негізгі бетінде дерекқор сұрауларымен жұмыстың қаншалықты нашар екенін көрейік. Түсінікті болу үшін бұл беттің қалай көрінетінін көрейік.
Ол үшін бізді қызықтыратын бетті жүктеп алып, Django Silk-тегі сұрау статистикасын көру жеткілікті.
Әзірге барлық сұраулар жөндеу режимінде көрсетіледі.
Жағдай көңілсіз, себебі форумның негізгі бетін жүктеуде:
- 325 сұрау дерекқорға
- жұмсалды 155 мс
- 568 мс толық бет
Бұл өте көп уақытты қажет ететін тапсырма, әсіресе әрбір сұрау үшін дерекқорға қосылу орнатылады, содан кейін барлық қажетті деректер де нысандарға жүктелуі керек.
Ресурсты тұтыну өте үлкен. Менің ойымша, бұл көптеген адамдар Джанго баяу деп ойлайтын себептердің бірі деп ойлаймын, бірақ олар дерекқор сұрауларын қалай реттеу және оңтайландыру керектігін түсінбеді.
Оңтайландыру
Түпнұсқа QuerySet негізгі форум бетінде қалай көрінетінін көрейік.
def get_queryset(self): return ForumTopic.objects.all()
1 493 / 5 000
Аударма нәтижелері
Көріп отырғаныңыздай, күрделі ештеңе жоқ. Бұл әдетте Django ORM пайдаланудың ең басында жазылатын сұраулар. Содан кейін ғана сұрақтар Django өнімділігін қалай оңтайландыруға болады.
Себебі бұл бетті көрсету үшін қажетті бастапқы сұраулар жиыны дерекқордан ForumTopic нысандарын ғана алады, бірақ ForumTopic деректер үлгісінің ForeignKey өрістеріне қосылған басқа нысандарды қабылдамайды. Сондықтан, Django қажет болған кезде барлық өте үлкен нысандарды автоматты түрде жүктеуге мәжбүр. Бірақ бағдарламашы әрбір жеке бет үшін не қажет екенін біледі және бір сұрау арқылы алдын ала алу қажет барлық ауқымды нысандарды Django-ға айта алады. Мұны select_related арқылы жасайық.
таңдау_байланысты
Бұл әдіс бір сұрауда басқа кестелерден қосымша объектілерді жинауға мүмкіндік береді. Бұл сізге көптеген сұрауларды біріктіруге және таңдауды жылдамдатуға, сондай-ақ дерекқорға қосылудың үстеме шығындарын азайтуға мүмкіндік береді, өйткені қосылымдар саны қазірдің өзінде өте азаяды.
selected_related арқылы бір сұрауда кейбір деректерді таңдап көрейік. Мен ForumTopic үлгісі үшін келесі өрістерді байланысты ретінде таңдауға болатынын білемін:
- мақала - форум мақаласы
- жауап - жауап, форумда жауап берілді деп белгіленген жазба
- бөлім – сұрақ қойылған бөлім
- пайдаланушы - осы сұрақты қойған пайдаланушы
Бастапқы дерекқор сұрауын келесідей өзгертуге болады:
def get_queryset(self, **kwargs): return ForumTopic.objects.all().select_related('article', 'answer', 'section', 'user')
Содан кейін Django Silk нәтижесін қараңыз.
Өтініштер санының жағдайы жақсарды
- 256 сұрау дерекқорға
- уақыт құны 131 мс
- 444мс толық бет
Келесі суретте 4 біріктіру әрекеті бар жаңа сұрауы бар жол көрсетілген.
Көріп отырғаныңыздай, бұл сұраудың ұзақтығы 19,225 мс болды.
Қазірдің өзінде жақсы нәтиже. Бірақ бұл шек емес екенін анық білемін. Мәселе мынада, форумның басты бетінің құрылымы өте күрделі және ол әр тақырыптағы жазбалар санын, соңғы жазбаны, шешімнің жауабына сілтемені, сондай-ақ пайдаланушы профиліне сұрауды көрсетеді. . жауаптар үшін. Ал енді кезек алдын ала_байланысты әдісіне келді.
алдын ала алуға_байланысты
prefetch_related үлгісінің ForeignKey өрістерінде пайдаланылатын нысандарды ғана емес, сонымен қатар үлгілерінде ForeignKey өрісі бар нысандарды да жүктеуге мүмкіндік беретіндігімен ерекшеленеді. мәліметтер базасының негізгі сұрау деректеріне қатысады. Яғни, хабарларды бөлек сұрауы бар тақырыпқа жүктей аласыз. Бұл жағдайда келесі өрістерді жүктегім келеді.
- пікірлер - бұл тақырыптағы жазбалар, ForumPost үлгісі
- comments__user - хабарлама қалдырған пайдаланушының сыртқы кілті
- жауап___ата-ана - ForumTopic сыртқы кілті - тақырып рұқсатымен белгіленген жауап. Теориялық тұрғыдан бұл нысанды select_related арқылы таңдауға болатын еді, бірақ сұрау құрылымы өте күрделі болды, бұл select_related тиімді пайдалануға мүмкіндік бермеді. Иә, иә, бұл әдісті қолдану ақылға қонымды болуы керек. Өнімділік, әрине, жақсарады, бірақ кейде кейбір деректерді бөлек сұрауда жинаған дұрыс.
Содан кейін дерекқор сұрауы келесідей болады:
def get_queryset(self, **kwargs): return ForumTopic.objects.all().select_related('article', 'answer', 'section', 'user').prefetch_related( 'comments', 'comments__user', 'answer___parent' )
Ал Джанго Жібекте мен келесі нәтижені аламын
Нәтижесінде бізде келесілер бар:
- 6 сұрау дерекқорға
- жұмсалды 26 мс
- 148 мс толық бет
Бұл қол жеткізуге болатын керемет нәтиже ғана. Сонымен қатар, пайдаланушы беттің өте жылдам жүктелетінін сезеді.
Бірақ бұл бәрі емес, 4 біріктіру операциясы бар сұрау әлі де 17-20 мс аймағында екенін ескеріңіз. Біз бұл туралы бірдеңе істей аламыз ба? Әрине, мүмкін, және ол үшін тек әдісін қолдану қажет болады.
ғана
only әдісі бізге бетті көрсету үшін қажет бағандарды ғана таңдауға мүмкіндік береді. Бірақ бұл жағдайда барлық қажетті бағандарды ескеру қажет болады, әйтпесе әрбір жетіспейтін бағанды Джанго бөлек сұрауда алады.
Сондықтан мен келесі дерекқор сұрауын жаздым
def get_queryset(self, **kwargs): return ForumTopic.objects.all().select_related('article', 'answer', 'section', 'user').prefetch_related( 'comments', 'comments__user', 'answer___parent' ).only( 'user__first_name', 'user__last_name', 'section__title', 'section__title_ru', 'article__title', 'article__title_ru' )
Және келесі нәтижеге қол жеткізді
- 6 сұрау дерекқорға
- жұмсалған 20 мс
- 136 мс толық бет
Әрине, мен мүмкін болатын ең жақсы нәтижелерді беремін, өйткені өлшемдерде әрқашан кейбір белгілер бар, бірақ скриншотты талдаудан сұрау ұзақтығы 17-19 мс -ден * дейін қысқарғанын көруге болады. 11-13мс *. Тек қажетті көлемдер мен тұтынуды іріктеуден басқа, мысалы, деректер қорынан мәтіндік деректердің өте үлкен массивтері алынса, олар беттерді көрсету кезінде пайдаланылмайды.
Енді select_related және prefetch_related сұрауларымен біраз ойнап көрейік.
Қосымша оңтайландыру
Осы уақытқа дейін оқу арқылы сіз select_related пайдалану дерекқор сұрауларын оңтайландырудың тамаша тәсілі екенін көрдіңіз деп ойлаймын. Бірақ бір БІРАҚ бар. Менің парақшамда қолданылатын Paginator класын пайдалану кезінде кейбір мәселелер туындауы мүмкін. Бірақ шын мәнінде, Paginator үшін беттердің дұрыс санын есептеу үшін санау сұрауын орындау қажет. Ал егер сұраныс өте күрделі болса, онда есептеуге арналған сұраныстың ұзақтығы айтарлықтай үлкен және қалыпты сұраныстың орындалуына сәйкес болуы мүмкін. Сондықтан, жылдам және тиімді негізгі сұрауды жазу маңызды шарт болуы мүмкін және барлық басқа нысандар prefetch_related арқылы жақсырақ жүктеледі. Яғни, сізде негізгі сұраныспен қосылу операцияларын шамадан тыс жүктеу арқылы бірнеше қосымша сұрауларды орындаған дұрыс болатын жағдай болуы мүмкін.
Мен бұл бетке ORM-де осындай сұраныс жаздым
def get_queryset(self, **kwargs): return ForumTopic.objects.all().select_related('answer').prefetch_related( Prefetch('article', queryset=Article.objects.all().only('title', 'title_ru')), Prefetch('section', queryset=ForumSection.objects.all().only('slug', 'title', 'title_ru')), Prefetch('user', queryset=User.objects.all().only('username', 'first_name', 'last_name')), Prefetch('comments', queryset=ForumPost.objects.all().select_related('user').only( 'user__username', 'user__first_name', 'user__last_name', '_parent_id' )), Prefetch('answer___parent', queryset=ForumTopic.objects.all().only('id')) ).only( 'title', 'user_id', 'section_id', 'article_id', 'answer___parent_id', 'pub_date', 'lastmod', 'attachment' )
Осылайша мен келесі өнімділік нәтижесін алдым
- 8 сұрау дерекқорға
- жұмсалды 14 мс
- 141 мс толық бет
Әрине, бұл жағдайда аса үлкен пайда жоқ деп айта аламыз. Сонымен қатар, жүктеп алудың жалпы жылдамдығы тіпті аздап (5 мс) төмендеді және дерекқорға тағы 2 сұрау болды, бірақ сонымен бірге мен сұраныс өнімділігін 42% өсірдім, бұл қазірдің өзінде. тұрарлық нәрсе. Осылайша, егер сіздің сайтыңызда беттеу үшін пайдаланылатын өте ұзын сұраулар болса және қосылу саны көп болса, select_related параметрін prefetch_related дегенге қайта жазған жөн. Бұл сіздің Django сайтыңызды әлдеқайда жылдам жасауға көмектеседі.
Қорытынды
- Негізгі сұраумен бір уақытта басқа кестелерден қатысты өрістерді таңдау үшін select_related пайдаланыңыз.
- Негізгі сұраулар жиынында ForeignKey бар барлық басқа үлгі нысандарын бір сұрауда қосымша жүктеу үшін prefetch_related пайдаланыңыз.
- Алу үшін бағандар санын шектеу үшін тек пайдаланыңыз, бұл да сұрауларды жылдамдатады және жад шығынын азайтады.
- Егер сіз Пагинатор қолданбасын пайдалансаңыз, негізгі сұрау өте ауыр санақ сұрауын жасамайтынына көз жеткізіңіз, әйтпесе кейбір select_related сұраулары prefetch_related ретінде жүктелуі мүмкін.
Спасибо. Хорошая статья.
Я нашёл 2 опечатки. Выделил жирным.
prefetch_related
prefetch_related отличается тем, что позволяет подгрузить не только объекты, которые используются в ForeignKey полях модели, но и те объекты, модели...
Должно вроде быть "но и те ".
Дополнительная оптимизация
...То есть у вас может быть ситуация, когда лучше выполнить ещё пару дополнительных запросов, через перегружать join операциями основной запрос.
Тут видимо имелось ввиду чем .
Спасибо, поправил
Стоило бы упомянуть про Prefetch объекты со специально сформированными querysetами. Про кеширование. Помимо only есть defer. В некоторых случаях в drf можно автоматически делать select/prefetch_related. И запросы можно смотреть в django_debug_toolbar или в shell_plus --print-sql
Вы про это?
Для drf можно сделать отдельную статью, я вообще не рассматривал в данной статье drf
В качестве альтернативы
Огромное спасибо вам за статью! Для меня стали открытием select_related и prefetch_related