Changes between Version 1 and Version 2 of Monitoring
- Timestamp:
- 05/17/26 22:46:46 (9 days ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
Monitoring
v1 v2 1 1 === Мониторинг 2 2 3 == Query Latency Distribution3 == Benchmark Report – Оптимизација на индекси и query-и во PostgreSQL 4 4 5 Просечниот execution time не ја претставува реалната состојба во системот. На пример 990 прашалници имаат време на извршување 1мс, додека 10 имаат 5000мс. Просекот е 50мс, но може да видиме дека тие 10 прашалници се outliers. Токму овие 10 спори прашалници можат да го нарушат корисничкото искуство. 5 Целта на оваа анализа е да се испита влијанието на индексите, query оптимизацијата, scan стратегиите, join операциите и sorting механизмите врз перформансите на PostgreSQL при извршување на комплексни аналитички SQL query-иња. 6 6 7 Пример за брз прашалник: 7 За секој query беа направени мерења пред оптимизација и мерења по оптимизација. Анализата е направена со: EXPLAIN ANALYZE; EXPLAIN (ANALYZE, BUFFERS). 8 9 Q1 - Анализа на player valuations 10 8 11 {{{ 9 SELECT * FROM players WHERE player_id = 10; 12 EXPLAIN (ANALYZE, BUFFERS) 13 SELECT * 14 FROM ( 15 SELECT 16 p.player_id, 17 p.last_name, 18 pv.market_value_in_eur, 19 ROW_NUMBER() OVER ( 20 PARTITION BY p.player_id 21 ORDER BY pv.date DESC 22 ) AS rn, 23 AVG(pv.market_value_in_eur) OVER ( 24 PARTITION BY p.player_id 25 ) AS avg_value 26 FROM players p 27 JOIN player_valuations pv 28 ON p.player_id = pv.player_id 29 WHERE pv.market_value_in_eur > 1000000 30 ) t 31 WHERE rn = 1 32 ORDER BY avg_value DESC 33 LIMIT 10000; 10 34 }}} 11 35 12 Пример за спор прашалник: 36 Пред оптимизацијата, прашалникот ги дава следниве резултати: 273мс време на извршување, Seq Scan, процесирани редици 131462, одстранети редици 339478, Sort Type: External Merge. Главните проблеми се тоа што има Full Sequential Scan на player_valuations и скапо филтрирање. 37 38 Ги додаваме следниве индекси: 39 13 40 {{{ 14 SELECT 15 p.player_name, 16 COUNT(t.transfer_id), 17 AVG(t.transfer_fee), 18 SUM(m.attendance) 19 FROM players p 20 JOIN transfers t ON p.player_id = t.player_id 21 JOIN matches m ON m.home_team_id = p.club_id 22 OR m.away_team_id = p.club_id 23 GROUP BY p.player_name; 41 CREATE INDEX idx_pv_player_date 42 ON player_valuations(player_id, date DESC); 43 44 CREATE INDEX idx_pv_value 45 ON player_valuations(market_value_in_eur); 24 46 }}} 25 47 26 Исто така, имаме два типа на прашалници: еден што има 10000 повици на ден со време на извршување 2мс и друг со 50 повици на ден со 5 секунди време на извршување. Вториот прашалник претставува поголем проблем за нашиот систем бидејќи корисникот не сака да чека slow response.48 По оптимизацијата добиваме 248мс време на извршување, Bitmap Heap Scan и повторно за sorting се користи External Merge. Најголемата промена е тоа што за филтерот market_value_in_eur > 1000000 се користи Bitmap Index Scan, наместо скенирање на цела табела, се читаат само потребните heap pages. Сепак и покрај индексите има многу редици што се процесираат и сортингот останува главен bottleneck. 27 49 28 == Hot Data vs Cold Data Monitoring 50 Q2 – Анализа на трансфери 29 51 30 Како што веќе видовме, системот располага со повеќе табели. Некои од нив се користат повеќе од останатите и може да се каже дека во реален систем 20% од податоците генерираат 80% од оптоварувањето.31 32 Класичен пример може да најдеме во табелата players. Од сите играчи кои се во табелата, најинтересни се познатите играчи - топ играчи. Токму затоа овие играчи ќе се најдат во повеќе прашалници од останати играчи и ќе се создаде row-level contention. Мониторинг резултат од гореспоменатиот проблем е тоа што играчот со id 10 ќе има 1200 queries/min, додека тој со id 187 ќе има 5 queries/min. Истите сценарија можат да се најдат и во други табели.33 34 Од друга страна, имаме и cold data која ретко се користи, нема locks и contention.35 36 == Пример37 38 Во Transfermarkt системот, „deadline day“ претставува екстремен случај на оптоварување, при што во краток временски интервал се извршуваат илјадници трансфер операции. За разлика од нормалниот режим, каде системот е претежно read-oriented, во овој период се јавува нагло зголемување на write операции и конкурентни трансакции.39 40 Во наредните примери ќе видиме и зашто ова е едно од најпредизивикувачките сценарија во системот.41 42 1. Complex Transfer Transaction43 44 Секој трансфер не е едноставен INSERT, туку составена трансакција:45 52 {{{ 46 BEGIN; 47 48 INSERT INTO transfers(player_id, from_club_id, to_club_id, transfer_fee, season) 49 VALUES (25, 3, 8, 12000000, '2025'); 50 51 UPDATE players 52 SET market_value = market_value + 1500000, 53 club_id = 8 54 WHERE player_id = 25; 55 56 UPDATE clubs 57 SET budget = budget - 12000000 58 WHERE club_id = 8; 59 60 UPDATE clubs 61 SET budget = budget + 12000000 62 WHERE club_id = 3; 63 64 COMMIT; 53 EXPLAIN ANALYZE 54 SELECT DISTINCT 55 p.last_name, 56 t.from_club_id, 57 t.to_club_id, 58 TO_CHAR(t.transfer_fee, '999,999,999') AS transfer_fee, 59 cg.club_id 60 FROM transfers t 61 JOIN players p ON p.player_id = t.player_id 62 LEFT JOIN club_games cg ON cg.club_id = t.from_club_id 63 WHERE t.transfer_date >= '2021-01-01' AND t.transfer_fee IS NOT NULL 64 ORDER BY transfer_fee DESC 65 LIMIT 10000; 65 66 }}} 66 67 67 Може да видиме дека имаме 4 write операции во 3 различни табели и сето ова е само за еден играч, додека на deadline day имаме илјадници трансфери. Исто така, системот влегува во contention режим поради тоа што сите трансакции таргетираат исти ентитети (player_id = 25, club_id = 8) и latency и queries/sec се зголемуваат драстично. Уште еден проблем со кој се соочуваме е transaction queueing, а тоа се случува бидејќи трансакцијата која се извршува зад себе остава други трансакции кои чекаат ресурси. Овој проблем е чест и во нормални услови, но на deadline day е многу посериозен. 68 Query-то прави join помеѓу transfers, players, club_games, филтрира трансфери по 2021 година, ги исклучува NULL transfer fees, сортира по transfer fee, враќа top 10,000 резултати. 68 69 69 == Спојување на две табели во една 70 71 Во последниот пример ќе направиме денормализација на системот, односно табелите players и player_valuations ќе ги споиме во една и ќе споредиме што е подобро. 70 ПРЕД ОПТИМИЗАЦИЈА прашалникот има време на извршување од 7.8с, чита 2.5 милиони редици, користи Seq Scan, Incremental Sort и Hash Join + Nested Loop. Главен проблем е што PostgreSQL прави SeqScan на transfers и players - прашалникот враќа голем дел од табелата. Потоа, ги додаваме следниве индекси: 72 71 73 72 {{{ 74 SELECT 75 p.last_name, 76 pv.market_value_in_eur, 77 pv.date 78 FROM players p 79 JOIN player_valuations pv 80 ON p.player_id = pv.player_id 81 WHERE p.player_id = 10 82 ORDER BY pv.date DESC 83 LIMIT 1; 73 CREATE INDEX idx_transfers_date 74 ON transfers(transfer_date); 75 76 CREATE INDEX idx_transfers_player 77 ON transfers(player_id); 78 79 CREATE INDEX idx_transfers_date_player 80 ON transfers(transfer_date, player_id); 84 81 }}} 85 82 86 Ова query има JOIN и ORDER BY + LIMIT. 83 и со мерењето ги добиваме следниве резултати: 3.5с време на извршување, што е значително подобрување, и повторно користење на Seq Scan. Сепак, главното прашање е зашто сеуште користиме Seq Scan. Кога враќаме 20% или повеќе од табелата, PostgreSQL знае дека е поевтино да користи Seq Scan од Index Scan. 84 85 Q3 - Комплексен aggregation query 87 86 88 87 {{{ 89 CREATE TABLE players_with_value AS 90 SELECT 91 p.player_id, 92 p.last_name, 93 p.current_club_id, 94 pv.market_value_in_eur, 95 pv.date 96 FROM players p 97 JOIN player_valuations pv 98 ON p.player_id = pv.player_id; 99 }}} 100 {{{ 101 SELECT last_name, market_value_in_eur 102 FROM players_with_value 103 WHERE player_id = 10 104 ORDER BY date DESC 105 LIMIT 1; 88 EXPLAIN (ANALYZE, BUFFERS) 89 SELECT 90 p.player_id, 91 p.last_name, 92 COUNT(a.game_id) AS appearances_count, 93 MAX(pv.market_value_in_eur) AS max_market_value 94 FROM players p 95 LEFT JOIN appearances a 96 ON p.player_id = a.player_id 97 LEFT JOIN player_valuations pv 98 ON p.player_id = pv.player_id 99 WHERE EXISTS ( 100 SELECT 1 101 FROM transfers t 102 WHERE t.player_id = p.player_id 103 AND t.transfer_fee > 5000000 104 ) 105 GROUP BY 106 p.player_id, 107 p.last_name 108 HAVING COUNT(a.game_id) > 10 109 ORDER BY max_market_value DESC 110 LIMIT 8000; 106 111 }}} 107 112 108 Користејќи ја новокреираната табела извршивме query кој не користи JOIN и има побрз пристап. Сепак, целосна денормализација не е најдоброто решение. За најдобри резултати во нашиот систем ќе искористиме partial denormalization, односно ќе направиме нова табела со моменталната/последната вредност на играчот: 113 Главни проблеми се full scans на appearances и player_valuations, join спојува 9 милиони редици пред агрегацијата и скапа агрегација. 114 115 Ги додаваме следниве индекси: 116 109 117 {{{ 110 CREATE TABLE players_current_value AS 111 SELECT DISTINCT ON (player_id) 112 player_id, 113 market_value, 114 date 115 FROM player_valuations 116 ORDER BY player_id, date DESC; 118 CREATE INDEX idx_transfers_player_fee 119 ON transfers(player_id, transfer_fee); 120 121 CREATE INDEX idx_appearances_player_game 122 ON appearances(player_id, game_id); 123 124 CREATE INDEX idx_player_valuations_player_value 125 ON player_valuations(player_id, market_value_in_eur); 117 126 }}} 118 127 119 Предности што ги добиваме со ова се тоа што нема непотребно скенирање на целата историја доколку корисникот не ја бара, помалку write операции и помал contention. 128 Ако ги споредиме резултатите пред и по оптимизацијата ќе видиме дека времето на извршување се намалило 4119мс -> 3325мс, за transfers access наместо Seq Scan користиме Index Only Scan и за appearances access наместо Seq Scan користиме Partial Index Only Scan. Најголемо подобрување имаме во EXISTS subquery-то поради индексот на transfers, додека appearances не е целосно оптимизирано и сеуште има heap fetches, односно PostgreSQL мора да оди во table pages. Најголем проблем останува join explosion. 129 130 131 132
