= Мерење на перформанси Извршена е анализа на перформансите на базата film_rental во PostgreSQL. Се споредуваат неоптимизирани табели и оптимизирани табели: * rental_copy - непартиционирана табела, користена како почетна состојба за споредба; * rental - партиционирана табела според rental_date, со дополнителни индекси; * payment_copy - непартиционирана табела за плаќања; * payment - партиционирана табела според payment_date, со индекс за датум и вработен. За оваа анализа беше користено pgbench. Секој тест се извршува со повеќе паралелни клиенти, при што се мерат просечна латентност и TPS, односно број на трансакции во секунда. Во сите тестови бројот на неуспешни трансакции беше 0. Пример за извршување на benchmark тест: {{{ pgbench -n -U admin -d film_rental -c 10 -j 2 -T 60 -f /tmp/movie_recommendation_rental_copy.sql }}} Каде: -n означува дека pgbench не ја иницијализира стандардната pgbench шема; -U admin е корисникот со кој се поврзува кон базата; -d film_rental е базата врз која се извршува тестот; -c 10 означува 10 паралелни клиенти; -j 2 означува 2 работни нишки; -T 60 означува времетраење од 60 секунди; -f ја задава SQL датотеката што се извршува. Истата команда се повторуваше со различен број клиенти, на пример 10, 20, 30 и 50, и со SQL датотеки за неоптимизираната и оптимизираната верзија. == Детални резултати === Q1 - Movie Recommendation Прашалникот прави препорака на филмови за конкретен корисник. Се анализираат претходните изнајмувања, омилените категории, рејтинзите и популарноста на филмовите во 2024 година. {{{ WITH customer_history AS ( SELECT DISTINCT f.film_id, f.rating, fc.category_id FROM rental_copy r JOIN inventory i ON r.inventory_id = i.inventory_id JOIN film f ON i.film_id = f.film_id JOIN film_category fc ON f.film_id = fc.film_id WHERE r.customer_id = 101 ), preferred_categories AS ( SELECT category_id, COUNT(*) AS category_score FROM customer_history GROUP BY category_id ), candidate_films AS ( SELECT f.film_id, f.title, f.rating, fc.category_id, pc.category_score FROM film f JOIN film_category fc ON f.film_id = fc.film_id JOIN preferred_categories pc ON fc.category_id = pc.category_id WHERE NOT EXISTS ( SELECT 1 FROM customer_history ch WHERE ch.film_id = f.film_id ) ), film_popularity AS ( SELECT i.film_id, COUNT(r.rental_id) AS rental_count FROM rental_copy r JOIN inventory i ON r.inventory_id = i.inventory_id WHERE r.rental_date >= DATE '2024-01-01' AND r.rental_date < DATE '2025-01-01' GROUP BY i.film_id ) SELECT cf.film_id, cf.title, cf.rating, c.name AS category, cf.category_score, COALESCE(fp.rental_count, 0) AS yearly_rentals, (cf.category_score * 10 + COALESCE(fp.rental_count, 0)) AS recommendation_score FROM candidate_films cf JOIN category c ON cf.category_id = c.category_id LEFT JOIN film_popularity fp ON cf.film_id = fp.film_id WHERE cf.rating IN ( SELECT rating FROM customer_history ) ORDER BY recommendation_score DESC, cf.title LIMIT 10; }}} За оптимизираната верзија се користи истата логика, но наместо rental_copy се користи партиционираната табела rental. || '''Clients''' || '''Threads''' || '''rental_copy latency''' || '''rental_copy TPS''' || '''rental latency''' || '''rental TPS''' || || 10 || 2 || 2235.603 ms || 4.473 || 1083.428 ms || 9.230 || || 20 || 4 || 4036.198 ms || 4.955 || 2208.459 ms || 9.056 || || 50 || 4 || 9487.494 ms || 5.270 || 5247.761 ms || 9.528 || Q1 покажува значително подобрување кај оптимизираната табела. Кај 10 клиенти латентноста е намалена за околу 51.5%, а TPS е зголемен за околу 106.3%. Кај 50 клиенти латентноста е намалена за околу 44.7%, а TPS е зголемен за околу 80.8%. Ова се случува бидејќи прашалникот користи customer_id, inventory_id и rental_date. Индексите помагаат при пребарување на историјата на корисникот, а партиционирањето помага при делот од прашалникот кој ја пресметува популарноста за 2024 година. === Q2 - Popular Films 2024 Прашалникот ги пресметува најизнајмуваните филмови во 2024 година. Се користи филтер по датум, спојување со inventory и film, групирање по филм и сортирање според бројот на изнајмувања. {{{ SELECT f.film_id, f.title, f.rating, COUNT(r.rental_id) AS rental_count FROM rental_copy r JOIN inventory i ON r.inventory_id = i.inventory_id JOIN film f ON i.film_id = f.film_id WHERE r.rental_date >= DATE '2024-01-01' AND r.rental_date < DATE '2025-01-01' GROUP BY f.film_id, f.title, f.rating ORDER BY rental_count DESC LIMIT 20; }}} За оптимизираната верзија се користи истата логика, но наместо rental_copy се користи партиционираната табела rental. || '''Clients''' || '''rental_copy latency''' || '''rental_copy TPS''' || '''rental latency''' || '''rental TPS''' || '''Промена''' || || 10 || 2222.438 ms || 4.500 || 1628.696 ms || 6.140 || ~26.7% побрзо || || 20 || 4205.239 ms || 4.756 || 3828.958 ms || 5.223 || ~8.9% побрзо || || 30 || 6545.865 ms || 4.583 || 6648.265 ms || 4.512 || ~1.6% побавно || || 50 || 11077.453 ms || 4.514 || 11290.484 ms || 4.429 || ~1.9% побавно || Q2 има мешани резултати. Кај 10 и 20 клиенти партиционираната табела е побрза, но кај 30 и 50 клиенти резултатот е речиси ист или малку полош. Ова покажува дека партиционирањето не секогаш гарантира подобрување. Кај овој прашалник, освен читање од rental, има и GROUP BY, ORDER BY и LIMIT. При поголем број паралелни клиенти главен трошок може да станат агрегацијата и сортирањето, а не само читањето на редови од табелата. === Q3 - Monthly Revenue Прашалникот пресметува месечен приход по продавница за 2024 година. Се пресметуваат број на плаќања, вкупен приход, просечна, минимална и максимална уплата. {{{ SELECT date_trunc('month', p.payment_date) AS month, s.store_id, COUNT(p.payment_id) AS payments_count, SUM(p.amount) AS total_revenue, AVG(p.amount) AS avg_payment, MIN(p.amount) AS min_payment, MAX(p.amount) AS max_payment FROM payment_copy p JOIN staff s ON p.staff_id = s.staff_id WHERE p.payment_date >= DATE '2024-01-01' AND p.payment_date < DATE '2025-01-01' GROUP BY date_trunc('month', p.payment_date), s.store_id ORDER BY month, s.store_id; }}} За оптимизираната верзија се користи истата логика, но наместо payment_copy се користи партиционираната табела payment. || '''Clients''' || '''payment_copy latency''' || '''payment_copy TPS''' || '''payment latency''' || '''payment TPS''' || '''Промена''' || || 10 || 2627.300 ms || 3.806 || 1055.953 ms || 9.470 || ~59.8% побрзо || || 30 || 8545.741 ms || 3.511 || 3325.437 ms || 9.021 || ~61.1% побрзо || || 50 || 16138.221 ms || 3.098 || 5321.565 ms || 9.396 || ~67.0% побрзо || Q3 покажува најстабилно подобрување кај аналитичките прашалници. Прашалникот пресметува месечен приход по продавница за 2024 година, а табелата payment е партиционирана според payment_date. Бидејќи условот користи временски интервал за 2024 година, PostgreSQL може да ја чита партицијата за 2024 година наместо целата табела. Затоа латентноста е намалена за околу 60-67%, а TPS е зголемен за околу 149-203%. === Q4 - INSERT rental Овој тест мери внесување нови редови во табелата за изнајмувања. За benchmark редовите се користи посебен датум 2026-03-07 12:00:00, за да се разликуваат од оригиналните податоци. {{{ INSERT INTO rental_copy (rental_date, inventory_id, customer_id, staff_id) VALUES ( TIMESTAMP '2026-03-07 12:00:00', floor(random() * 50000 + 1)::int, floor(random() * 1000000 + 1)::int, floor(random() * 50 + 1)::int ); }}} За оптимизираната верзија се користи истата логика, но внесувањето се прави во партиционираната табела rental. || '''Clients''' || '''rental_copy latency''' || '''rental_copy TPS''' || '''rental latency''' || '''rental TPS''' || '''Промена''' || || 10 || 3.121 ms || 3204.533 || 2.855 ms || 3502.623 || ~8.5% побрзо || || 30 || 2.949 ms || 10173.818 || 3.147 ms || 9533.949 || ~6.7% побавно || || 50 || 6.756 ms || 7400.777 || 3.088 ms || 16190.222 || ~54.3% побрзо || INSERT операциите имаат мешани резултати. Кај 10 и 50 клиенти партиционираната табела е побрза, но кај 30 клиенти непартиционираната табела има малку подобар резултат. Причината е тоа што при внесување PostgreSQL мора да одреди во која партиција припаѓа новиот ред и мора да ги ажурира индексите. Тоа додава мал overhead, но при поголемо конкурентно оптоварување партиционираната структура може да помогне затоа што записите се насочуваат кон конкретна партиција. === Q5 - UPDATE rental Овој тест мери ажурирање на изнајмувања. Се избира еден ред со benchmark датумот и со return_date IS NULL, па потоа се поставува return_date = now(). {{{ UPDATE rental_copy SET return_date = now() WHERE rental_id = ( SELECT rental_id FROM rental_copy WHERE rental_date = TIMESTAMP '2026-03-07 12:00:00' AND return_date IS NULL LIMIT 1 ); }}} За оптимизираната верзија се користи истата логика, но ажурирањето се прави врз партиционираната табела rental. || '''Clients''' || '''rental_copy latency''' || '''rental_copy TPS''' || '''rental latency''' || '''rental TPS''' || '''Промена''' || || 10 || 590.026 ms || 16.948 || 7.044 ms || 1419.709 || ~98.8% побрзо || || 30 || 1483.097 ms || 20.228 || 12.318 ms || 2435.437 || ~99.2% побрзо || || 50 || 2646.029 ms || 18.896 || 16.689 ms || 2996.003 || ~99.4% побрзо || UPDATE операцијата покажува најголемо подобрување. Условот користи конкретен rental_date, па кај партиционираната табела PostgreSQL може да пребарува само во релевантната партиција. Кај rental_copy системот мора да пребарува низ голема непартиционирана табела. Затоа латентноста е многу поголема, а TPS е многу помал. == Вкупно подобрување Партиционираните табели и индексите покажуваат значително подобрување кај прашалници кои се временски ограничени и кај операции кои можат да се насочат кон конкретна партиција. Најголеми придобивки: * Q3 - Monthly Revenue: околу 64.5% просечно намалување на латентноста. * Q5 - UPDATE rental: околу 99.2% просечно намалување на латентноста. * Q1 - Movie Recommendation: околу 45.8% просечно намалување на латентноста. Мешани резултати: * Q2 - Popular Films има подобрување кај 10 и 20 клиенти, но мало влошување кај 30 и 50 клиенти. * Q4 - INSERT има подобрување кај 10 и 50 клиенти, но мало влошување кај 30 клиенти. Ова покажува дека оптимизацијата мора да се мери. Партиционирањето не е автоматско решение за сите прашалници. Најмногу помага кога WHERE условот ја користи колоната по која е направено партиционирањето. == Операции на читање со мешани резултати * Q1 - Movie Recommendation: јасно подобрување кај сите нивоа на конкурентност. * Q2 - Popular Films: подобро кај помал број клиенти, но речиси исто или малку полошо кај поголем број клиенти. * Q3 - Monthly Revenue: најдобар пример каде партиционирањето директно го намалува бројот на редови што треба да се читаат. Кај читањата кои содржат големи агрегации и сортирања, како Q2, главниот проблем не е секогаш табелата од која се чита. Понекогаш главниот трошок е обработката по читањето, односно GROUP BY и ORDER BY. == Операции на запишување * INSERT: просечно подобрување, но со варијации. * UPDATE: многу големо подобрување поради филтрирање по rental_date. Кај INSERT, партиционирањето може да додаде overhead затоа што секој ред мора да се насочи во соодветна партиција. Кај UPDATE, партиционирањето е многу корисно кога условот ја користи партициската колона, бидејќи пребарувањето се ограничува на мала партиција наместо на целата табела. == Заклучок За ова специфично работно оптоварување: * Партиционирањето е многу корисно за временски извештаи, особено кај payment и rental. * Индексите значително помагаат кај прашалници со WHERE услови и JOIN операции врз често користени колони. * Q3 и Q5 покажуваат најголемо подобрување, затоа што директно ја користат колоната по која е направено партиционирањето. * Q2 покажува дека партиционирањето не го решава секој проблем, бидејќи агрегацијата и сортирањето можат да станат главен bottleneck. * INSERT операциите треба да се мерат внимателно, бидејќи партиционирањето и индексите можат да додадат мал overhead. Најсоодветен пристап за оваа база е да се задржи партиционирањето по датум за големите трансакциски табели rental и payment, да се користат индекси само за најчестите филтри и спојувања, и редовно да се проверуваат резултатите со EXPLAIN ANALYZE и pgbench.