Changes between Initial Version and Version 1 of Optimization


Ignore:
Timestamp:
06/12/25 07:59:22 (2 weeks ago)
Author:
211012
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Optimization

    v1 v1  
     1Профилирање и оптимизација на прашалници
     2
     3Во базите на податоци претставуваат критична компонента за успешното функционирање на апликациите. Како што расте волуменот на податоците и комплексноста на прашалницте, перформансите на базата стануваат тесно грло кое има влијание врз генералните перформанси на апликацијата, оперативната ефикасност и најважно, врз кориснчкото искуство.
     4Еден од најчестите проблеми во администрацијата на базите се неефикасните SQL прашалници, особено кога се работи за комплексни аналитички прашалници што обработуваат големи количини на податоци и притоа прават комплексни пресметки и агрегации, и ова дополнително ги комплицира работите доколку ваквите аналитички прашалници се извршуваат врз оперативната база на податоци. Во продолжение е прикажан пример, како едноствна промена во начинот на калкулирање на еден сегмент од овој прашлник, доведе до драстично подобрување на времето на извршување.
     5
     6== Преглед на првичниот проблем
     7
     8Како пример за овој дел од проектот го земавме погледот за извесна статистичка анализа на резултатите на студентите по задача, `v_statistiki_vremenski_po_student_i_zadacha`. Иако навидум овој поглед сам по себе изгледа доста комплексен, проблем всушност прозилезе дека е во `v_activity_with_interval_and_payload_next`, каде што во потпрашалници се прави пресметка за времето на првата активност пред моментално разгледуваната и првата активност по моментално разгледуваната. Времето на извршување на прашалникот вклучувајќи ги вакви операции во просек надминуваше 3 минути врз табела со околу 500.000 записи.
     9
     10{{{#!sql
     11
     12(SELECT ait3.payload AS payload_next
     13
     14     FROM activity_in_task ait3
     15
     16     WHERE ait3.person_id = pppppp.person_id AND ait3.task_in_test_instance_id = pppppp.task_in_test_instance_id AND
     17         ait3.when_occured = pppppp.when_occured_next) AS payload_next
     18
     19(SELECT min(ait2.when_occured) AS min
     20
     21           FROM activity_in_task ait2
     22
     23           WHERE ait2.person_id = ait.person_id AND ait2.task_in_test_instance_id = ait.task_in_test_instance_id AND
     24               ait2.when_occured > ait.when_occured) AS when_occured_next
     25}}}
     26
     27
     28
     29== Како дојдовме до ваков заклучок?
     30
     31Детална анализа на Execution Plan
     32
     33Execution планот (план на извршување) е најмоќната алатка за да го разбереме "начинот на размислување" на оптимизаторот, и да најдеме потенцијални точки за подобрување. Со негова анализа може да се утврди:
     34- кои индекси (не) се користат,
     35- дали се прават целосни скенирања на табела (table scans),
     36- дали се прават непотребни сортирања или спојувања,
     37- колкави се проценетите и реалните броеви на редови што се процесираат.
     38
     39Основни концепти за читање и разбирање на Execution Plans
     401. Структура на планот
     41Execution планот е во форма на хиерархиска структура (дрво) каде што:
     42
     43Корен е конечниот резултат на прашачникот
     44Листови се основните табели или индекси
     45Внатрешни јазли се операциите (JOIN, WHERE, GROUP BY, итн.)
     46Текот на податоците е оздола нагоре
     47
     482. Клучни метрики во планот
     49Cost (Цена на извршување):
     50cost=0.43..156789.45
     51
     52Првиот број (0.43) е startup cost - цена до враќање на првиот ред
     53Вториот број (156789.45) е total cost - цена по враќање на сите редови
     54Цената е релативна единица, не се мери во време
     55
     56Rows (Редови):
     57rows=1000
     58
     59Проценет број на редови што ќе се вратат од оваа операција
     60Базиран на статистики на планерот
     61
     62Width (Ширина):
     63width=64
     64
     65Просечна големина на редот во бајти
     66
     67Actual Time (Реално време):
     68actual time=180000.123..180456.789
     69
     70Реалното време во милисекунди
     71Првиот број е време до првиот ред
     72Вториот број е вкупно време
     73
     74Loops (Циклуси):
     75loops=1000
     76
     77Колку пати се извршила операцијата
     78Вкупното време = actual time × loops
     79
     803. Типови операции во планот
     81Scan операции:
     82
     83`Seq Scan` - Секвенцијално скенирање (ја чита цела табела)
     84`Index Scan` - Користи индекс за пронаоѓање на потребните редици
     85`Index Only Scan` - Сите потребни податоци се во индексот
     86`Bitmap Heap Scan` - Комбинира повеќе индекси
     87
     88`Join` операции:
     89
     90`Nested Loop` - За секој ред од левата табела, пребарува во десната (многу спора операција)
     91`Hash Join` - Создава hash табела и спојува по клучот
     92`Merge Join` - Спојува два сортирани сетови
     93
     94`Aggregate` операции:
     95
     96`Aggregate` - GROUP BY, COUNT, SUM, итн.
     97`WindowAgg` - Window функции (LEAD, LAG, ROW_NUMBER, итн.)
     98`HashAggregate` - GROUP BY со hash табела
     99
     100== Анализа на нашиот конкретен случај
     101
     102За визуелизација на execution plan-от ја користевме следната алатка [https://explain.dalibo.com/]
     103
     104За да имаме преглед и врз меморијата што се користи, ја извршивме следната команда за да генерираме execution plan
     105
     106{{{#!sql
     107
     108explain (analyze, buffers)
     109select *
     110from v_statistiki_vremenski_po_student_i_zadacha;
     111
     112}}}
     113
     114Очигледно беше дека операцијата `NestedLoopJoin` во рамки на која понатаму се прави `IndexScan` е онаа операција која е причина за лошите перформанси на прашалникот. На сликата подолу е видливо дека на овие операции практично отпаѓа целото време на извршување. Дополнително, можеме да воочиме дека естимацијата направена во однос на редови кои треба да се вратат е непрезицна и дека за секој ред од табелата ова скенирање се повторува.
     115
     116=== Аплицирање на промената во прашалникот
     117
     118{{{#!sql
     119
     120    lead(ait.when_occured) over (
     121              partition by ait.person_id, ait.task_in_test_instance_id
     122              order by ait.when_occured
     123              ) AS when_occured_next,
     124
     125    lead(ait.payload) over (
     126              partition by ait.person_id, ait.task_in_test_instance_id
     127              order by ait.when_occured
     128              ) as payload_next
     129
     130}}}
     131
     132Главната оптимизација се состоеше во замена на корелираните потпрашалници со window функции во погледот `v_activity_with_interval_and_payload_next`.
     133
     134По аплицирање на оптимизацијата промената беше веднаш видлива и во планот на извршување. Претходно скапата операција на NestedLoop која траеше околу 3 минути, сега е заменета со WindowAgg која трае вкупно 144 ms. По ова веднаш беше видливо и подобрување во вкупното време на извршување на прашалникот кое драстично се намали на ~200ms.
     135
     136== Зошто Window функциите се толку поефикасни?
     137
     1381. Еднократно скенирање на табелата: Window функциите поминуваат само еднаш низ податоците, додека корелираните потпрашалници го скенираат истиот сет на податоци повторно за секој ред
     1392. Нема повторливи пребарувања: Корелираните потпрашалници се извршуваат N пати (каде што N е бројот на редови), што резултира со O(N²) сложеност
     1403. Подобра оптимизација: Современите планери (PostgreSQL, SQL Server, Oracle) користат специјализирани алгоритми за оптимизација на window функции
     1414. Намален броја на I/O операции: Наместо повеќе извршувања на потпрашалници со потенцијални пристапи до диск, сè се процесира во РАМ во една ефикасна операција
     1425. Подобро искористување на индексите: Планерот може лесно да ги оптимизира window функциите, особено кога има индекси на колоните во `PARTITION BY` и `ORDER BY`, што најчесто ќе биде случај бидејќи тоа се колони на надворешни клучеви и посекако се користат за спојувања во обични прашалници.