Evgenii Legotckoi
Evgenii LegotckoiҚаз. 22, 2019, 1:39 Т.Ж.

Джанго - 049-сабақ. Өндірістік жоба арқылы Джанго өнімділігін оңтайландыру

Жақында мен сайтты оңтайландыруға көп уақыт бөлдім, енді бұл туралы айтқым келеді.
Бұл мақала 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-тегі сұрау статистикасын көру жеткілікті.

EVILEG форумының басты бетін оңтайландырудағы бірінші қадам

Әзірге барлық сұраулар жөндеу режимінде көрсетіледі.

Жағдай көңілсіз, себебі форумның негізгі бетін жүктеуде:

  • 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 нәтижесін қараңыз.

Select_related көмегімен өнімділікті жақсарту

Өтініштер санының жағдайы жақсарды

  • 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'
    )

Ал Джанго Жібекте мен келесі нәтижені аламын

SELECT_related және prefetch_related пайдалану

Нәтижесінде бізде келесілер бар:

  • 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 ретінде жүктелуі мүмкін.
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

Руслан Волшебник
  • Қаз. 24, 2019, 11:37 Т.Ж.

Спасибо. Хорошая статья.

Я нашёл 2 опечатки. Выделил жирным.

prefetch_related
prefetch_related отличается тем, что позволяет подгрузить не только объекты, которые используются в ForeignKey полях модели, но и те объекты, модели...
Должно вроде быть "но и те ".

Дополнительная оптимизация
...То есть у вас может быть ситуация, когда лучше выполнить ещё пару дополнительных запросов, через перегружать join операциями основной запрос.
Тут видимо имелось ввиду чем .

Evgenii Legotckoi
  • Қаз. 25, 2019, 2:54 Т.Ж.

Спасибо, поправил

p
  • Қар. 25, 2019, 9:11 Т.Ж.

Стоило бы упомянуть про Prefetch объекты со специально сформированными querysetами. Про кеширование. Помимо only есть defer. В некоторых случаях в drf можно автоматически делать select/prefetch_related. И запросы можно смотреть в django_debug_toolbar или в shell_plus --print-sql

Evgenii Legotckoi
  • Қар. 25, 2019, 9:21 Т.Ж.

Стоило бы упомянуть про Prefetch объекты со специально сформированными querysetами

Вы про это?

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'
    )

В некоторых случаях в drf можно автоматически делать select/prefetch_related

Для drf можно сделать отдельную статью, я вообще не рассматривал в данной статье drf

И запросы можно смотреть в django_debug_toolbar или в shell_plus --print-sql

В качестве альтернативы

D
  • Наурыз 2, 2021, 2:48 Т.Ж.

Огромное спасибо вам за статью! Для меня стали открытием select_related и prefetch_related

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
Г

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:66ұпай,
  • Бағалау ұпайлары-1
t

C++ - Тест 001. Первая программа и типы данных

  • Нәтиже:33ұпай,
  • Бағалау ұпайлары-10
t

Qt - Тест 001. Сигналы и слоты

  • Нәтиже:52ұпай,
  • Бағалау ұпайлары-4
Соңғы пікірлер
G
GoattRockҚыр. 3, 2024, 1:50 Т.Қ.
Linux жүйесінде файлдарды қалай көшіруге болады Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
d
dblas5Шілде 5, 2024, 11:02 Т.Ж.
QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssrАқп. 8, 2024, 6:43 Т.Қ.
Qt Linux - Сабақ 001. Linux астында Autorun Qt қолданбасы как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий КононенкоАқп. 5, 2024, 1:50 Т.Ж.
Qt WinAPI - Сабақ 007. Qt ішінде ICMP Ping арқылы жұмыс істеу Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
Енді форумда талқылаңыз
Evgenii Legotckoi
Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
F
FynjyШілде 22, 2024, 4:15 Т.Ж.
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
BlinCT
BlinCTМаусым 25, 2024, 1 Т.Ж.
Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
BlinCT
BlinCTМамыр 5, 2024, 5:46 Т.Ж.
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii LegotckoiМамыр 2, 2024, 2:07 Т.Қ.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

Бізді әлеуметтік желілерде бақылаңыз