| | 1 | = Мерење на перформанси |
| | 2 | |
| | 3 | Извршена е анализа на перформансите на базата film_rental во PostgreSQL. Се споредуваат неоптимизирани табели и оптимизирани табели: |
| | 4 | * rental_copy - непартиционирана табела, користена како почетна состојба за споредба; |
| | 5 | * rental - партиционирана табела според rental_date, со дополнителни индекси; |
| | 6 | * payment_copy - непартиционирана табела за плаќања; |
| | 7 | * payment - партиционирана табела според payment_date, со индекс за датум и вработен. |
| | 8 | За оваа анализа беше користено pgbench. Секој тест се извршува со повеќе паралелни клиенти, при што се мерат просечна латентност и TPS, односно број на трансакции во секунда. Во сите тестови бројот на неуспешни трансакции беше 0. |
| | 9 | |
| | 10 | |
| | 11 | Пример за извршување на benchmark тест: |
| | 12 | |
| | 13 | pgbench -n -U admin -d film_rental -c 10 -j 2 -T 60 -f /tmp/movie_recommendation_rental_copy.sql |
| | 14 | |
| | 15 | Каде: |
| | 16 | -n означува дека pgbench не ја иницијализира стандардната pgbench шема; |
| | 17 | -U admin е корисникот со кој се поврзува кон базата; |
| | 18 | -d film_rental е базата врз која се извршува тестот; |
| | 19 | -c 10 означува 10 паралелни клиенти; |
| | 20 | -j 2 означува 2 работни нишки; |
| | 21 | -T 60 означува времетраење од 60 секунди; |
| | 22 | -f ја задава SQL датотеката што се извршува. |
| | 23 | |
| | 24 | Истата команда се повторуваше со различен број клиенти, на пример 10, 20, 30 и 50, и со SQL датотеки за неоптимизираната и оптимизираната верзија. |
| | 25 | |
| | 26 | == Детални резултати |
| | 27 | |
| | 28 | === Q1 - Movie Recommendation |
| | 29 | Прашалникот прави препорака на филмови за конкретен корисник. Се анализираат претходните изнајмувања, омилените категории, рејтинзите и популарноста на филмовите во 2024 година. |
| | 30 | |
| | 31 | {{{ |
| | 32 | WITH customer_history AS ( |
| | 33 | SELECT DISTINCT |
| | 34 | f.film_id, |
| | 35 | f.rating, |
| | 36 | fc.category_id |
| | 37 | FROM rental_copy r |
| | 38 | JOIN inventory i ON r.inventory_id = i.inventory_id |
| | 39 | JOIN film f ON i.film_id = f.film_id |
| | 40 | JOIN film_category fc ON f.film_id = fc.film_id |
| | 41 | WHERE r.customer_id = 101 |
| | 42 | ), |
| | 43 | preferred_categories AS ( |
| | 44 | SELECT |
| | 45 | category_id, |
| | 46 | COUNT(*) AS category_score |
| | 47 | FROM customer_history |
| | 48 | GROUP BY category_id |
| | 49 | ), |
| | 50 | candidate_films AS ( |
| | 51 | SELECT |
| | 52 | f.film_id, |
| | 53 | f.title, |
| | 54 | f.rating, |
| | 55 | fc.category_id, |
| | 56 | pc.category_score |
| | 57 | FROM film f |
| | 58 | JOIN film_category fc ON f.film_id = fc.film_id |
| | 59 | JOIN preferred_categories pc ON fc.category_id = pc.category_id |
| | 60 | WHERE NOT EXISTS ( |
| | 61 | SELECT 1 |
| | 62 | FROM customer_history ch |
| | 63 | WHERE ch.film_id = f.film_id |
| | 64 | ) |
| | 65 | ), |
| | 66 | film_popularity AS ( |
| | 67 | SELECT |
| | 68 | i.film_id, |
| | 69 | COUNT(r.rental_id) AS rental_count |
| | 70 | FROM rental_copy r |
| | 71 | JOIN inventory i ON r.inventory_id = i.inventory_id |
| | 72 | WHERE r.rental_date >= DATE '2024-01-01' |
| | 73 | AND r.rental_date < DATE '2025-01-01' |
| | 74 | GROUP BY i.film_id |
| | 75 | ) |
| | 76 | SELECT |
| | 77 | cf.film_id, |
| | 78 | cf.title, |
| | 79 | cf.rating, |
| | 80 | c.name AS category, |
| | 81 | cf.category_score, |
| | 82 | COALESCE(fp.rental_count, 0) AS yearly_rentals, |
| | 83 | (cf.category_score * 10 + COALESCE(fp.rental_count, 0)) AS recommendation_score |
| | 84 | FROM candidate_films cf |
| | 85 | JOIN category c ON cf.category_id = c.category_id |
| | 86 | LEFT JOIN film_popularity fp ON cf.film_id = fp.film_id |
| | 87 | WHERE cf.rating IN ( |
| | 88 | SELECT rating |
| | 89 | FROM customer_history |
| | 90 | ) |
| | 91 | ORDER BY recommendation_score DESC, cf.title |
| | 92 | LIMIT 10; |
| | 93 | }}} |
| | 94 | |
| | 95 | За оптимизираната верзија се користи истата логика, но наместо rental_copy се користи партиционираната табела rental. |
| | 96 | |
| | 97 | || '''Clients''' || '''Threads''' || '''rental_copy latency''' || '''rental_copy TPS''' || '''rental latency''' || '''rental TPS''' || |
| | 98 | || 10 || 2 || 2235.603 ms || 4.473 || 1083.428 ms || 9.230 || |
| | 99 | || 20 || 4 || 4036.198 ms || 4.955 || 2208.459 ms || 9.056 || |
| | 100 | || 50 || 4 || 9487.494 ms || 5.270 || 5247.761 ms || 9.528 || |
| | 101 | |
| | 102 | Q1 покажува значително подобрување кај оптимизираната табела. Кај 10 клиенти латентноста е намалена за околу 51.5%, а TPS е зголемен за околу 106.3%. Кај 50 клиенти латентноста е намалена за околу 44.7%, а TPS е зголемен за околу 80.8%. Ова се случува бидејќи прашалникот користи customer_id, inventory_id и rental_date. Индексите помагаат при пребарување на историјата на корисникот, а партиционирањето помага при делот од прашалникот кој ја пресметува популарноста за 2024 година. |
| | 103 | |
| | 104 | === Q2 - Popular Films 2024 |
| | 105 | Прашалникот ги пресметува најизнајмуваните филмови во 2024 година. Се користи филтер по датум, спојување со inventory и film, групирање по филм и сортирање според бројот на изнајмувања. |
| | 106 | {{{ |
| | 107 | SELECT |
| | 108 | f.film_id, |
| | 109 | f.title, |
| | 110 | f.rating, |
| | 111 | COUNT(r.rental_id) AS rental_count |
| | 112 | FROM rental_copy r |
| | 113 | JOIN inventory i ON r.inventory_id = i.inventory_id |
| | 114 | JOIN film f ON i.film_id = f.film_id |
| | 115 | WHERE r.rental_date >= DATE '2024-01-01' |
| | 116 | AND r.rental_date < DATE '2025-01-01' |
| | 117 | GROUP BY f.film_id, f.title, f.rating |
| | 118 | ORDER BY rental_count DESC |
| | 119 | LIMIT 20; |
| | 120 | }}} |
| | 121 | |
| | 122 | За оптимизираната верзија се користи истата логика, но наместо rental_copy се користи партиционираната табела rental. |
| | 123 | |
| | 124 | || '''Clients''' || '''rental_copy latency''' || '''rental_copy TPS''' || '''rental latency''' || '''rental TPS''' || '''Промена''' || |
| | 125 | || 10 || 2222.438 ms || 4.500 || 1628.696 ms || 6.140 || ~26.7% побрзо || |
| | 126 | || 20 || 4205.239 ms || 4.756 || 3828.958 ms || 5.223 || ~8.9% побрзо || |
| | 127 | || 30 || 6545.865 ms || 4.583 || 6648.265 ms || 4.512 || ~1.6% побавно || |
| | 128 | || 50 || 11077.453 ms || 4.514 || 11290.484 ms || 4.429 || ~1.9% побавно || |
| | 129 | |
| | 130 | Q2 има мешани резултати. Кај 10 и 20 клиенти партиционираната табела е побрза, но кај 30 и 50 клиенти резултатот е речиси ист или малку полош. |
| | 131 | Ова покажува дека партиционирањето не секогаш гарантира подобрување. Кај овој прашалник, освен читање од rental, има и GROUP BY, ORDER BY и LIMIT. При поголем број паралелни клиенти главен трошок може да станат агрегацијата и сортирањето, а не само читањето на редови од табелата. |
| | 132 | |
| | 133 | |
| | 134 | Q3 - Monthly Revenue |
| | 135 | Прашалникот пресметува месечен приход по продавница за 2024 година. Се пресметуваат број на плаќања, вкупен приход, просечна, минимална и максимална уплата. |
| | 136 | |
| | 137 | {{{ |
| | 138 | SELECT |
| | 139 | date_trunc('month', p.payment_date) AS month, |
| | 140 | s.store_id, |
| | 141 | COUNT(p.payment_id) AS payments_count, |
| | 142 | SUM(p.amount) AS total_revenue, |
| | 143 | AVG(p.amount) AS avg_payment, |
| | 144 | MIN(p.amount) AS min_payment, |
| | 145 | MAX(p.amount) AS max_payment |
| | 146 | FROM payment_copy p |
| | 147 | JOIN staff s ON p.staff_id = s.staff_id |
| | 148 | WHERE p.payment_date >= DATE '2024-01-01' |
| | 149 | AND p.payment_date < DATE '2025-01-01' |
| | 150 | GROUP BY date_trunc('month', p.payment_date), s.store_id |
| | 151 | ORDER BY month, s.store_id; |
| | 152 | }}} |
| | 153 | |
| | 154 | За оптимизираната верзија се користи истата логика, но наместо payment_copy се користи партиционираната табела payment. |
| | 155 | |
| | 156 | || '''Clients''' || '''payment_copy latency''' || '''payment_copy TPS''' || '''payment latency''' || '''payment TPS''' || '''Промена''' || |
| | 157 | || 10 || 2627.300 ms || 3.806 || 1055.953 ms || 9.470 || ~59.8% побрзо || |
| | 158 | || 30 || 8545.741 ms || 3.511 || 3325.437 ms || 9.021 || ~61.1% побрзо || |
| | 159 | || 50 || 16138.221 ms || 3.098 || 5321.565 ms || 9.396 || ~67.0% побрзо || |
| | 160 | |
| | 161 | Q3 покажува најстабилно подобрување кај аналитичките прашалници. Прашалникот пресметува месечен приход по продавница за 2024 година, а табелата payment е партиционирана според payment_date. Бидејќи условот користи временски интервал за 2024 година, PostgreSQL може да ја чита партицијата за 2024 година наместо целата табела. Затоа латентноста е намалена за околу 60-67%, а TPS е зголемен за околу 149-203%. |
| | 162 | |
| | 163 | === Q4 - INSERT rental |
| | 164 | |
| | 165 | Овој тест мери внесување нови редови во табелата за изнајмувања. За benchmark редовите се користи посебен датум 2026-03-07 12:00:00, за да се разликуваат од оригиналните податоци. |
| | 166 | |
| | 167 | {{{ |
| | 168 | INSERT INTO rental_copy (rental_date, inventory_id, customer_id, staff_id) |
| | 169 | VALUES ( |
| | 170 | TIMESTAMP '2026-03-07 12:00:00', |
| | 171 | floor(random() * 50000 + 1)::int, |
| | 172 | floor(random() * 1000000 + 1)::int, |
| | 173 | floor(random() * 50 + 1)::int |
| | 174 | ); |
| | 175 | }}} |
| | 176 | |
| | 177 | За оптимизираната верзија се користи истата логика, но внесувањето се прави во партиционираната табела rental. |
| | 178 | |
| | 179 | || '''Clients''' || '''rental_copy latency''' || '''rental_copy TPS''' || '''rental latency''' || '''rental TPS''' || '''Промена''' || |
| | 180 | || 10 || 3.121 ms || 3204.533 || 2.855 ms || 3502.623 || ~8.5% побрзо || |
| | 181 | || 30 || 2.949 ms || 10173.818 || 3.147 ms || 9533.949 || ~6.7% побавно || |
| | 182 | || 50 || 6.756 ms || 7400.777 || 3.088 ms || 16190.222 || ~54.3% побрзо || |
| | 183 | |
| | 184 | INSERT операциите имаат мешани резултати. Кај 10 и 50 клиенти партиционираната табела е побрза, но кај 30 клиенти непартиционираната табела има малку подобар резултат. |
| | 185 | Причината е тоа што при внесување PostgreSQL мора да одреди во која партиција припаѓа новиот ред и мора да ги ажурира индексите. Тоа додава мал overhead, но при поголемо конкурентно оптоварување партиционираната структура може да помогне затоа што записите се насочуваат кон конкретна партиција. |
| | 186 | |
| | 187 | === Q5 - UPDATE rental |
| | 188 | Овој тест мери ажурирање на изнајмувања. Се избира еден ред со benchmark датумот и со return_date IS NULL, па потоа се поставува return_date = now(). |
| | 189 | {{{ |
| | 190 | UPDATE rental_copy |
| | 191 | SET return_date = now() |
| | 192 | WHERE rental_id = ( |
| | 193 | SELECT rental_id |
| | 194 | FROM rental_copy |
| | 195 | WHERE rental_date = TIMESTAMP '2026-03-07 12:00:00' |
| | 196 | AND return_date IS NULL |
| | 197 | LIMIT 1 |
| | 198 | ); |
| | 199 | }}} |
| | 200 | |
| | 201 | За оптимизираната верзија се користи истата логика, но ажурирањето се прави врз партиционираната табела rental. |
| | 202 | |
| | 203 | || '''Clients''' || '''rental_copy latency''' || '''rental_copy TPS''' || '''rental latency''' || '''rental TPS''' || '''Промена''' || |
| | 204 | || 10 || 590.026 ms || 16.948 || 7.044 ms || 1419.709 || ~98.8% побрзо || |
| | 205 | || 30 || 1483.097 ms || 20.228 || 12.318 ms || 2435.437 || ~99.2% побрзо || |
| | 206 | || 50 || 2646.029 ms || 18.896 || 16.689 ms || 2996.003 || ~99.4% побрзо || |
| | 207 | |
| | 208 | UPDATE операцијата покажува најголемо подобрување. Условот користи конкретен rental_date, па кај партиционираната табела PostgreSQL може да пребарува само во релевантната партиција. Кај rental_copy системот мора да пребарува низ голема непартиционирана табела. Затоа латентноста е многу поголема, а TPS е многу помал. |
| | 209 | |
| | 210 | == Вкупно подобрување |
| | 211 | Партиционираните табели и индексите покажуваат значително подобрување кај прашалници кои се временски ограничени и кај операции кои можат да се насочат кон конкретна партиција. |
| | 212 | Најголеми придобивки: |
| | 213 | |
| | 214 | * Q3 - Monthly Revenue: околу 64.5% просечно намалување на латентноста. |
| | 215 | * Q5 - UPDATE rental: околу 99.2% просечно намалување на латентноста. |
| | 216 | * Q1 - Movie Recommendation: околу 45.8% просечно намалување на латентноста. |
| | 217 | |
| | 218 | Мешани резултати: |
| | 219 | * Q2 - Popular Films има подобрување кај 10 и 20 клиенти, но мало влошување кај 30 и 50 клиенти. |
| | 220 | * Q4 - INSERT има подобрување кај 10 и 50 клиенти, но мало влошување кај 30 клиенти. |
| | 221 | |
| | 222 | Ова покажува дека оптимизацијата мора да се мери. Партиционирањето не е автоматско решение за сите прашалници. Најмногу помага кога WHERE условот ја користи колоната по која е направено партиционирањето. |
| | 223 | |
| | 224 | == Операции на читање со мешани резултати |
| | 225 | |
| | 226 | * Q1 - Movie Recommendation: јасно подобрување кај сите нивоа на конкурентност. |
| | 227 | * Q2 - Popular Films: подобро кај помал број клиенти, но речиси исто или малку полошо кај поголем број клиенти. |
| | 228 | * Q3 - Monthly Revenue: најдобар пример каде партиционирањето директно го намалува бројот на редови што треба да се читаат. |
| | 229 | |
| | 230 | Кај читањата кои содржат големи агрегации и сортирања, како Q2, главниот проблем не е секогаш табелата од која се чита. Понекогаш главниот трошок е обработката по читањето, односно GROUP BY и ORDER BY. |
| | 231 | |
| | 232 | == Операции на запишување |
| | 233 | |
| | 234 | * INSERT: просечно подобрување, но со варијации. |
| | 235 | * UPDATE: многу големо подобрување поради филтрирање по rental_date. |
| | 236 | |
| | 237 | Кај INSERT, партиционирањето може да додаде overhead затоа што секој ред мора да се насочи во соодветна партиција. Кај UPDATE, партиционирањето е многу корисно кога условот ја користи партициската колона, бидејќи пребарувањето се ограничува на мала партиција наместо на целата табела. |
| | 238 | |
| | 239 | == Заклучок |
| | 240 | За ова специфично работно оптоварување: |
| | 241 | |
| | 242 | * Партиционирањето е многу корисно за временски извештаи, особено кај payment и rental. |
| | 243 | * Индексите значително помагаат кај прашалници со WHERE услови и JOIN операции врз често користени колони. |
| | 244 | * Q3 и Q5 покажуваат најголемо подобрување, затоа што директно ја користат колоната по која е направено партиционирањето. |
| | 245 | * Q2 покажува дека партиционирањето не го решава секој проблем, бидејќи агрегацијата и сортирањето можат да станат главен bottleneck. |
| | 246 | * INSERT операциите треба да се мерат внимателно, бидејќи партиционирањето и индексите можат да додадат мал overhead. |
| | 247 | |
| | 248 | Најсоодветен пристап за оваа база е да се задржи партиционирањето по датум за големите трансакциски табели rental и payment, да се користат индекси само за најчестите филтри и спојувања, и редовно да се проверуваат резултатите со EXPLAIN ANALYZE и pgbench. |
| | 249 | |
| | 250 | |
| | 251 | |
| | 252 | |
| | 253 | |
| | 254 | |
| | 255 | |
| | 256 | |
| | 257 | |
| | 258 | |
| | 259 | |
| | 260 | |
| | 261 | |
| | 262 | |
| | 263 | |
| | 264 | |
| | 265 | |
| | 266 | |
| | 267 | |
| | 268 | |
| | 269 | |
| | 270 | |
| | 271 | |