Changes between Initial Version and Version 1 of Phase4_Profiling_Optimization


Ignore:
Timestamp:
05/08/26 03:09:07 (3 weeks ago)
Author:
226052
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Phase4_Profiling_Optimization

    v1 v1  
     1= Профилирање и оптимизација на извршувањето на прашалниците
     2
     3Профилирањето на прашалници претставува анализа на начинот на кој PostgreSQL го извршува одреден SQL прашалник. Целта е да се открие дали прашалникот користи неефикасни операции, како секвенцијално читање на големи табели, непотребно голем број join операции или сортирање на голем број редови. За оваа анализа во PostgreSQL се користи EXPLAIN ANALYZE, кој го прикажува планот на извршување и реалното време потребно за извршување на прашалникот.
     4
     5{{{
     6EXPLAIN ANALYZE
     7WITH customer_history AS (
     8    SELECT DISTINCT
     9        f.film_id,
     10        f.rating,
     11        fc.category_id
     12    FROM rental r
     13    JOIN inventory i ON r.inventory_id = i.inventory_id
     14    JOIN film f ON i.film_id = f.film_id
     15    JOIN film_category fc ON f.film_id = fc.film_id
     16    WHERE r.customer_id = 101
     17),
     18preferred_categories AS (
     19    SELECT
     20        category_id,
     21        COUNT(*) AS category_score
     22    FROM customer_history
     23    GROUP BY category_id
     24),
     25candidate_films AS (
     26    SELECT
     27        f.film_id,
     28        f.title,
     29        f.rating,
     30        fc.category_id,
     31        pc.category_score
     32    FROM film f
     33    JOIN film_category fc ON f.film_id = fc.film_id
     34    JOIN preferred_categories pc ON fc.category_id = pc.category_id
     35    WHERE NOT EXISTS (
     36        SELECT 1
     37        FROM customer_history ch
     38        WHERE ch.film_id = f.film_id
     39    )
     40),
     41film_popularity AS (
     42    SELECT
     43        i.film_id,
     44        COUNT(r.rental_id) AS rental_count
     45    FROM rental r
     46    JOIN inventory i ON r.inventory_id = i.inventory_id
     47    WHERE r.rental_date >= DATE '2024-01-01'
     48      AND r.rental_date < DATE '2025-01-01'
     49    GROUP BY i.film_id
     50)
     51SELECT
     52    cf.film_id,
     53    cf.title,
     54    cf.rating,
     55    c.name AS category,
     56    cf.category_score,
     57    COALESCE(fp.rental_count, 0) AS yearly_rentals,
     58    (cf.category_score * 10 + COALESCE(fp.rental_count, 0)) AS recommendation_score
     59FROM candidate_films cf
     60JOIN category c ON cf.category_id = c.category_id
     61LEFT JOIN film_popularity fp ON cf.film_id = fp.film_id
     62WHERE cf.rating IN (
     63    SELECT rating
     64    FROM customer_history
     65)
     66ORDER BY recommendation_score DESC, cf.title
     67LIMIT 10;
     68}}}
     69
     70Овој прашалник претставува пример за препорака на филмови за клиент. Прво се анализира историјата на изнајмувања на клиентот, односно кои филмови, категории и ratings ги има гледано. Потоа се избираат кандидати од истите категории, се исклучуваат филмовите што клиентот веќе ги изнајмил, и се додава популарноста на филмовите според бројот на изнајмувања во 2024 година. На крај се пресметува recommendation_score и се враќаат најдобрите 10 препораки.
     71
     72{{{
     73CTE customer_history
     74-> Parallel Seq Scan on rental r_1
     75   Filter: (customer_id = 101)
     76   Rows Removed by Filter: 3333329
     77film_popularity
     78-> Parallel Seq Scan on rental r
     79   Filter: ((rental_date >= '2024-01-01') AND (rental_date < '2025-01-01'))
     80Rows Removed by Filter: 2666129
     81Buffers: shared hit=207891
     82Execution Time: 865.763 ms
     83}}}
     84
     85При првото извршување, прашалникот се изврши без дополнителни индекси. Времето на извршување беше околу 866 ms. Во планот се забележува Parallel Seq Scan врз табелата rental, и тоа на две места: при пронаоѓање на историјата на клиентот и при пресметување на популарноста на филмовите во 2024 година. Ова значи дека PostgreSQL мора да прочита голем дел од табелата rental, која има над 10 милиони редови.
     86За оптимизација се додаваат индекси врз колоните што најмногу се користат во WHERE условите и JOIN операциите. Индексот idx_rental_customer_inventory директно помага при пронаоѓање на изнајмувањата за конкретен клиент. Индексот idx_inventory_inventory_film помага при поврзување на rental со inventory и film. Индексите врз rental_date, film_category и rating се додадени како можни оптимизации, но PostgreSQL не мора секогаш да ги користи ако процени дека друг план е поефикасен.
     87
     88За оптимизација се додаваат индекси врз колоните што најмногу се користат во WHERE условите и JOIN операциите. Индексот idx_rental_customer_inventory директно помага при пронаоѓање на изнајмувањата за конкретен клиент. Индексот idx_inventory_inventory_film помага при поврзување на rental со inventory и film. Индексите врз rental_date, film_category и rating се додадени како можни оптимизации, но PostgreSQL не мора секогаш да ги користи ако процени дека друг план е поефикасен.
     89
     90{{{
     91CREATE INDEX idx_rental_customer_inventory ON rental(customer_id, inventory_id);
     92CREATE INDEX idx_rental_date_inventory ON rental(rental_date, inventory_id);
     93CREATE INDEX idx_inventory_inventory_film ON inventory(inventory_id, film_id);
     94CREATE INDEX idx_film_category_category_film ON film_category(category_id, film_id);
     95CREATE INDEX idx_film_rating ON film(rating);
     96}}}
     97
     98По додавање на индексите, истиот прашалник повторно се извршува со EXPLAIN ANALYZE.
     99{{{
     100CTE customer_history
     101-> Index Only Scan using idx_rental_customer_inventory on rental r_1
     102   Index Cond: (customer_id = 101)
     103   Heap Fetches: 1
     104-> Index Only Scan using idx_inventory_inventory_film on inventory i_1
     105   Index Cond: (inventory_id = r_1.inventory_id)
     106film_popularity
     107-> Parallel Seq Scan on rental r
     108   Filter: ((rental_date >= '2024-01-01') AND (rental_date < '2025-01-01'))
     109   Rows Removed by Filter: 2666129
     110Buffers: shared hit=104500 read=15
     111Execution Time: 566.908 ms
     112}}}
     113
     114Времето на извршување се намали од околу 866 ms на околу 567 ms.
     115
     116Најголемата промена се забележува во делот customer_history. Пред оптимизацијата PostgreSQL користеше Parallel Seq Scan врз табелата rental и мораше да провери голем број редови за да ги најде изнајмувањата на клиентот 101. По додавање на индексот idx_rental_customer_inventory, планот користи Index Only Scan, со што овој дел од прашалникот се намали од околу 256 ms на околу 4.4 ms.
     117Сепак, во делот film_popularity PostgreSQL сè уште користи Parallel Seq Scan врз rental за филтрирање на изнајмувањата во 2024 година. Причината е што условот за 2024 година враќа околу 2 милиони редови, па PostgreSQL проценува дека секвенцијално скенирање е поефикасно од користење индекс. Ова покажува дека додавање индекс не значи дека индексот секогаш ќе биде искористен.
     118Иако додавањето индекси го подобри делот од прашалникот каде што се бара историјата на конкретен клиент, во планот на извршување сè уште се забележува Parallel Seq Scan врз табелата rental при пресметување на популарноста на филмовите во 2024 година. Ова се случува затоа што условот:
     119{{{
     120WHERE r.rental_date >= DATE '2024-01-01'
     121  AND r.rental_date < DATE '2025-01-01'
     122}}}
     123
     124работи врз табела со над 10 милиони редови.
     125
     126Бидејќи прашалникот филтрира според датум, табелата rental може да се оптимизира со партиционирање според колоната rental_date. Со range partitioning, податоците се делат во посебни партиции по години. На тој начин, кога прашалникот бара податоци само за 2024 година, PostgreSQL може да ја чита само партицијата за 2024 година, наместо целата табела.
     127
     128{{{
     129ALTER TABLE rental RENAME TO rental_old;
     130CREATE TABLE rental (
     131    rental_id BIGINT GENERATED ALWAYS AS IDENTITY,
     132    rental_date TIMESTAMP NOT NULL,
     133    inventory_id BIGINT NOT NULL,
     134    customer_id BIGINT NOT NULL,
     135    return_date TIMESTAMP,
     136    staff_id BIGINT NOT NULL,
     137    last_update TIMESTAMP DEFAULT now() NOT NULL,
     138    PRIMARY KEY (rental_id, rental_date)
     139)
     140PARTITION BY RANGE (rental_date);
     141
     142CREATE TABLE rental_2020 PARTITION OF rental
     143FOR VALUES FROM ('2020-01-01') TO ('2021-01-01');
     144
     145CREATE TABLE rental_2021 PARTITION OF rental
     146FOR VALUES FROM ('2021-01-01') TO ('2022-01-01');
     147
     148CREATE TABLE rental_2022 PARTITION OF rental
     149FOR VALUES FROM ('2022-01-01') TO ('2023-01-01');
     150
     151CREATE TABLE rental_2023 PARTITION OF rental
     152FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
     153
     154CREATE TABLE rental_2024 PARTITION OF rental
     155FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
     156
     157CREATE TABLE rental_2025 PARTITION OF rental
     158FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');
     159
     160CREATE TABLE rental_2026 PARTITION OF rental
     161FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');
     162Потоа податоците се префрлаат од старата табела rental_old во новата партиционирана табела rental.
     163INSERT INTO rental (rental_id, rental_date, inventory_id, customer_id, return_date, staff_id, last_update)
     164OVERRIDING SYSTEM VALUE
     165SELECT rental_id, rental_date, inventory_id, customer_id, return_date, staff_id, last_update
     166FROM rental_old;
     167}}}
     168
     169По партиционирањето, истиот прашалник повторно се извршува.
     170{{{
     171CTE customer_history
     172-> Append
     173   -> Index Only Scan using rental_2020_customer_id_inventory_id_idx on rental_2020
     174      Index Cond: (customer_id = 101)
     175   -> Index Only Scan using rental_2021_customer_id_inventory_id_idx on rental_2021
     176      Index Cond: (customer_id = 101)
     177   -> Index Only Scan using rental_2022_customer_id_inventory_id_idx on rental_2022
     178      Index Cond: (customer_id = 101)
     179   -> Index Only Scan using rental_2023_customer_id_inventory_id_idx on rental_2023
     180      Index Cond: (customer_id = 101)
     181   -> Index Only Scan using rental_2024_customer_id_inventory_id_idx on rental_2024
     182      Index Cond: (customer_id = 101)
     183
     184film_popularity
     185-> Parallel Seq Scan on rental_2024 r
     186   Filter: ((rental_date >= '2024-01-01') AND (rental_date < '2025-01-01'))
     187
     188Buffers: shared hit=22020 read=15
     189Execution Time: 347.399 ms
     190}}}
     191Времето на извршување се намали на околу 347 ms. Во планот се забележува дека при пресметување на популарноста за 2024 година PostgreSQL чита само од партицијата rental_2024 (Parallel Seq Scan on rental_2024).
     192Ова значи дека PostgreSQL применил partition pruning во делот од прашалникот каде што има услов за 2024 година и не ги чита партициите за другите години за тој дел од планот. Дополнително, делот customer_history користи Index Only Scan врз партициите, што овозможува брзо пронаоѓање на изнајмувањата за конкретниот клиент.
     193Во табелата е прикажана споредба на времето на извршување на истиот прашалник во три различни состојби: без дополнителна оптимизација, по додавање индекси и по партиционирање на табелата rental според rental_date.
     194
     195|| Фаза || Време на извршување ||
     196|| Без дополнителни индекси || 865.763 ms ||
     197|| Со индекси || 566.908 ms ||
     198|| Со партиционирање и индекси || 347.399 ms ||
     199
     200Од резултатите се гледа дека секоја оптимизација го намалува времето на извршување. Индексите најмногу помогнаа во делот каде што се бара историјата на конкретен клиент, додека партиционирањето помогна во делот каде што се анализираат изнајмувањата за 2024 година.
     201Партиционирањето е најкорисно кога прашалниците често филтрираат според колоната што е partition key. Во овој пример, rental_date е погодна колона за partition key бидејќи прашалникот ја анализира популарноста на филмовите за конкретна година. Наместо да се обработува целата табела rental, PostgreSQL може да ја обработи само релевантната партиција.
     202
     203
     204