Changes between Initial Version and Version 1 of AdvancedTopics


Ignore:
Timestamp:
06/13/26 19:50:54 (7 days ago)
Author:
231069
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedTopics

    v1 v1  
     1= Фаза 5: Напредна тема - Партиционирање =
     2
     3
     4
     5
     6[[BR]]
     7== 1. Опис  ==
     8Партиционирана е централната табела '''Zapisnik''' (~10.000.000 записи) по '''RANGE''' стратегија врз колоната `datum`, со гранулација по '''година''' (партиции 2021–2026 + `DEFAULT`). Партиционирањето повлече измени и врз двете зависни табели што референцираат `Zapisnik` - '''Stavka_Zapisnik''' и '''Uplata''' - за да се зачува интегритетот, како и дополнување на процедурите што вметнуваат во нив.
     9
     10
     11[[BR]]
     12== 2. Избор на табела и стратегија ==
     13`Zapisnik` е најголемата и најфреквентно пребарувана табела во системот. Записите се додаваат постојано, се групираат природно по `datum`, а голем дел од погледите и прегледите филтрираат по временски период. Со партиционирање по `datum`:
     14* при филтрирање по период, планерот пристапува само до релевантната година (партиција), наместо до сите ~10М редови;
     15* архивирање на стари записници станува отстранување на цела партиција, наместо `DELETE` врз милиони редови.
     16
     17
     18[[BR]]
     19== 3. Измени во шемата ==
     20
     21=== 3.1 Примарен клуч ===
     22Оригиналниот PK беше само `id_na_zapisnik`. Бидејќи PostgreSQL бара партициониониот клуч да биде дел од PK, тој е направен '''композитен''': `(id_na_zapisnik, datum)`.
     23
     24=== 3.2 Надворешни клучеви ===
     25Поради композитниот PK, веќе нема уникатност само на `id_na_zapisnik`, па старите FK од `Stavka_Zapisnik` и `Uplata` не важат. За да се зачува интегритетот:
     26* во `Stavka_Zapisnik` и во `Uplata` е додадена нова колона `datum_zapisnik`;
     27* Foreign keys се преправени во '''композитни''': `(id, datum_zapisnik) → Zapisnik(id_na_zapisnik, datum)`.
     28
     29=== 3.3 Погледи ===
     30Сите погледи (`v_site_zapisnici`, `v_zapisnici_detalno`, `v_neplateni_kazni`, `v_povtoreni_prekrsoci`, `v_policajci_prosek_zapisnici`, `v_istorija_gragjanin`) '''не бараат никаква измена''' — продолжуваат да работат, бидејќи пристапот до партиционираната табела е ист како до обична.
     31
     32=== 3.4 Процедури ===
     33Процедурите `kreiraj_zapisnik_so_prekrsok` и `dodadi_stavka_zapisnik` се '''дополнети''' да ја пополнуваат `datum_zapisnik` при вметнување во `Uplata`/`Stavka_Zapisnik`, инаку композитниот FK би бил прекршен.
     34
     35=== 3.5 Тригери ===
     36Тригерот `azuriraj_iznos_uplata` врз `Stavka_Zapisnik` е исклучен само за време на масовното пополнување на `datum_zapisnik`, за да не се активира 20М пати.
     37
     38
     39[[BR]]
     40== 4. Имплементација ==
     41
     42=== Чекор 1 - Додавање и пополнување на datum_zapisnik во децата ===
     43Се прави '''додека старата `Zapisnik` сè уште постои''' (за да се извлече датумот). Тригерите се исклучуваат за време на пополнувањето.
     44{{{
     45#!sql
     46ALTER TABLE Stavka_Zapisnik DISABLE TRIGGER USER;
     47ALTER TABLE Stavka_Zapisnik ADD COLUMN datum_zapisnik date;
     48UPDATE Stavka_Zapisnik sz
     49SET datum_zapisnik = z.datum
     50FROM Zapisnik z
     51WHERE sz.id_na_zapisnik = z.id_na_zapisnik;
     52ALTER TABLE Stavka_Zapisnik ENABLE TRIGGER USER;
     53
     54ALTER TABLE Uplata ADD COLUMN datum_zapisnik date;
     55UPDATE Uplata u
     56SET datum_zapisnik = z.datum
     57FROM Zapisnik z
     58WHERE u.id_zapisnik = z.id_na_zapisnik;
     59}}}
     60
     61=== Чекор 2 - Отстранување на старите FK ===
     62Не можат да опстанат покрај новиот композитен PK.
     63{{{
     64#!sql
     65ALTER TABLE Stavka_Zapisnik DROP CONSTRAINT IF EXISTS stavka_zapisnik_id_na_zapisnik_fkey;
     66ALTER TABLE Uplata          DROP CONSTRAINT IF EXISTS uplata_id_zapisnik_fkey;
     67}}}
     68
     69=== Чекор 3 - Преименување и креирање на партиционираната табела ===
     70Старата `Zapisnik_old` се задржува како baseline копија. Новата се создава со композитен PK и `PARTITION BY RANGE (datum)`.
     71{{{
     72#!sql
     73ALTER TABLE Zapisnik RENAME TO Zapisnik_old;
     74
     75CREATE TABLE Zapisnik (
     76    id_na_zapisnik     int,
     77    vreme              time,
     78    datum              date NOT NULL,
     79    lokacija           varchar(100),
     80    Potpis             boolean DEFAULT false,
     81    id_slucaj          int,
     82    EMBG_Prekrsuvach   char(13),
     83    Vozilo_Broj_Sasija varchar(17),
     84    EMBG_Policaec      char(13),
     85    PRIMARY KEY (id_na_zapisnik, datum),
     86    FOREIGN KEY (id_slucaj)          REFERENCES Slucaj(id_slucaj)      ON UPDATE CASCADE ON DELETE RESTRICT,
     87    FOREIGN KEY (EMBG_Prekrsuvach)   REFERENCES Gragjanin(EMBG)        ON UPDATE CASCADE ON DELETE RESTRICT,
     88    FOREIGN KEY (Vozilo_Broj_Sasija) REFERENCES Vozilo(broj_na_sasija) ON UPDATE CASCADE ON DELETE RESTRICT,
     89    FOREIGN KEY (EMBG_Policaec)      REFERENCES Policaec(EMBG_P)       ON UPDATE CASCADE ON DELETE RESTRICT
     90) PARTITION BY RANGE (datum);
     91}}}
     92
     93=== Чекор 4 - Креирање на партициите по година ===
     94{{{
     95#!sql
     96CREATE TABLE Zapisnik_2021 PARTITION OF Zapisnik FOR VALUES FROM ('2021-01-01') TO ('2022-01-01');
     97CREATE TABLE Zapisnik_2022 PARTITION OF Zapisnik FOR VALUES FROM ('2022-01-01') TO ('2023-01-01');
     98CREATE TABLE Zapisnik_2023 PARTITION OF Zapisnik FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
     99CREATE TABLE Zapisnik_2024 PARTITION OF Zapisnik FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
     100CREATE TABLE Zapisnik_2025 PARTITION OF Zapisnik FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');
     101CREATE TABLE Zapisnik_2026 PARTITION OF Zapisnik FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');
     102CREATE TABLE Zapisnik_default PARTITION OF Zapisnik DEFAULT;
     103}}}
     104
     105=== Чекор 5 - Миграција на податоците ===
     106PostgreSQL автоматски го распоредува секој од ~10М редови во соодветната партиција. `ANALYZE` за свежа статистика.
     107{{{
     108#!sql
     109INSERT INTO Zapisnik (id_na_zapisnik, vreme, datum, lokacija, Potpis,
     110                      id_slucaj, EMBG_Prekrsuvach, Vozilo_Broj_Sasija, EMBG_Policaec)
     111SELECT id_na_zapisnik, vreme, datum, lokacija, Potpis,
     112       id_slucaj, EMBG_Prekrsuvach, Vozilo_Broj_Sasija, EMBG_Policaec
     113FROM Zapisnik_old;
     114
     115
     116}}}
     117
     118=== Чекор 6 - Враќање на интегритетот (композитни FK) ===
     119{{{
     120#!sql
     121ALTER TABLE Stavka_Zapisnik
     122    ADD CONSTRAINT stavka_zapisnik_zapisnik_fk
     123    FOREIGN KEY (id_na_zapisnik, datum_zapisnik)
     124    REFERENCES Zapisnik (id_na_zapisnik, datum)
     125    ON UPDATE CASCADE ON DELETE CASCADE;
     126
     127ALTER TABLE Uplata
     128    ADD CONSTRAINT uplata_zapisnik_fk
     129    FOREIGN KEY (id_zapisnik, datum_zapisnik)
     130    REFERENCES Zapisnik (id_na_zapisnik, datum)
     131    ON UPDATE CASCADE ON DELETE RESTRICT;
     132}}}
     133
     134=== Чекор 7 - Дополнување на процедурите ===
     135За процедурите да продолжат да работат со композитниот FK, при вметнување се предава и `datum_zapisnik`. Пример од `kreiraj_zapisnik_so_prekrsok`:
     136{{{
     137#!sql
     138INSERT INTO Uplata (iznos, status, Uplatil_Gragjanin, id_zapisnik, datum_zapisnik)
     139VALUES (0, 'Neplateno', p_embg_prekrsuvac, v_id_zapisnik, p_datum);
     140
     141INSERT INTO Stavka_Zapisnik (reden_broj, id_na_zapisnik, id_na_prekrsok, datum_zapisnik)
     142VALUES (1, v_id_zapisnik, p_id_prekrsok, p_datum);
     143}}}
     144Истото важи и за `dodadi_stavka_zapisnik` — прво се чита `datum` на записникот, па се запишува во `datum_zapisnik`.
     145
     146
     147[[BR]]
     148== 5. Резултати и споредба ==
     149
     150=== 5.1 План на извршување ===
     151Истото барање е извршено врз непартиционираната (`Zapisnik_old`) и врз партиционираната табела, со `EXPLAIN (ANALYZE, BUFFERS)`:
     152{{{
     153#!sql
     154EXPLAIN (ANALYZE, BUFFERS)
     155SELECT * FROM Zapisnik
     156WHERE datum BETWEEN '2024-03-01' AND '2024-03-31';
     157}}}
     158'''Пред партиционирањето''' — планерот изведе `Parallel Seq Scan` врз целата табела `Zapisnik_old` (време ~2325 ms):
     159
     160[[Image(before.png, width=1000)]]
     161
     162'''По партиционирањето''' — благодарение на '''partition pruning''', планерот пристапи '''само''' до партицијата `Zapisnik_2024` (`Seq Scan`, време ~449 ms), отфрлајќи ги сите останати партиции:
     163
     164[[Image(after.png, width=1000)]]
     165
     166=== 5.3 Споредба на перформанси ===
     167||='''Аспект'''=||='''Пред (Zapisnik_old)'''=||='''Потоа (партиционирана)'''=||
     168|| Тип на пристап || Parallel Seq Scan врз цела табела || Seq Scan само на Zapisnik_2024 ||
     169|| Скенирани партиции || цела табела || само Zapisnik_2024 (pruning) ||
     170|| Цена (cost) || 1000.00 – 362222.83 || 0.00 – 60456.30 ||
     171|| Време || 2325.078 ms || 449.092 ms ||
     172|| Вратени редови || ~168.330 || ~168.330 ||
     173
     174Резултатот е ист (~168.330 редови за месецот), но '''количината на работа е драстично помала''': времето падна од ~2325 ms на ~449 ms (≈ '''5 пати побрзо'''), а проценетата цена од ~362.000 на ~60.000 (≈ '''6 пати помала'''). Дополнително, baseline-от мораше да вклучи паралелизам за да ја совлада целата табела, додека партиционираната верзија беше побрза и со едноставен `Seq Scan` врз една партиција.
     175
     176
     177[[BR]]
     178== 6. Одржување ==
     179Архивирање на цела година и додавање нова партиција, без `DELETE`:
     180{{{
     181#!sql
     182ALTER TABLE Zapisnik DETACH PARTITION Zapisnik_2021;
     183
     184CREATE TABLE Zapisnik_2027 PARTITION OF Zapisnik
     185    FOR VALUES FROM ('2027-01-01') TO ('2028-01-01');
     186}}}
     187
     188
     189[[BR]]
     190== 7. Прилози ==
     191* [attachment:particioniranje.sql Скрипта за партиционирање] — целосна имплементација.
     192* before.png, after.png — слики од плановите на извршување (вметнати во секција 5.2).