Changes between Version 1 and Version 2 of QueryOptimization


Ignore:
Timestamp:
09/21/26 16:40:41 (8 days ago)
Author:
231139
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • QueryOptimization

    v1 v2  
    1 = Querry optimisation
     1= Индексирање и оптимизација на прашалници =
     2
     3Во оваа фаза се оптимизирани десет прашалници што ги претставуваат најважните начини на користење на Telecom и GIS погледите. Тестовите се извршени директно врз основните табели за јасно да се измери влијанието на конкретниот индекс. Наведениот поглед ја користи истата пристапна шема, но не бил директен предмет на мерењето.
     4
     5За секој прашалник прво е измерено времето без опционалниот индекс. Потоа планот е проверен со:
     6
     7{{{
     8EXPLAIN (ANALYZE, BUFFERS)
     9SELECT ...;
     10}}}
     11
     12`Seq Scan` значи дека PostgreSQL чита голем дел или цела табела. `Rows Removed by Filter` покажува колку непотребни редови биле прочитани. По оптимизацијата, `Index Scan`, `Index Only Scan` или `Bitmap Index Scan` покажува дека базата пристапува само до потребните записи. `Index Cond` покажува кои услови навистина го користат индексот.
     13
     14Кај составните B-tree индекси прва е колоната со точен услов, а втора е колоната со временски опсег. `INCLUDE` колоните не служат за пребарување, туку овозможуваат потребните вредности да се прочитаат директно од индексот. За точки, полигони и растојанија се користат GiST индекси.
     15
     16[attachment:"Telecom_GIS_Before_After_Index_Benchmark.sql"]
     17
     18== 1. Претплати по корисничка сметка ==
     19
     20Поврзан поглед: `v_customer_subscription_overview`
     21
     22{{{
     23SELECT count(*), max(subscription_id), max(plan_id), max(status)
     24FROM public.subscriptions
     25WHERE account_id = :account_id;
     26}}}
     27
     28=== Пред оптимизацијата
     29
     30Времето било **0.347 ms**. Без индекс, планот користи `Seq Scan` и го применува `account_id = ...` како Filter. Така се проверуваат и претплатите од другите сметки.
     31
     32=== Избран индекс
     33
     34{{{
     35CREATE INDEX idx_bench_subscriptions_account
     36ON public.subscriptions (account_id, subscription_id)
     37INCLUDE (plan_id, contract_id, subscription_number, status);
     38}}}
     39
     40`account_id` е прв бидејќи е условот за пребарување. `subscription_id` е втор за организирање на претплатите на сметката. Другите колони се во INCLUDE бидејќи ги користи погледот, но не се филтри.
     41
     42=== По оптимизацијата
     43
     44Планот може да користи `Index Only Scan` со `Index Cond: account_id = ...`. Времето се намалило на **0.051 ms**, односно прашалникот е **6.80 пати побрз**.
     45
     46[attachment:"01 idx_bench_subscriptions_account.sql"]
     47[attachment:"01 Customer subscription overview.sql"]
     48
     49== 2. Историја на повици по претплата и време ==
     50
     51Поврзан поглед: `v_customer_call_history`
     52
     53{{{
     54SELECT count(*), max(event_start_time), sum(duration_seconds)
     55FROM public.usage_cdr_calls
     56WHERE subscription_id = :subscription_id
     57  AND event_start_time >= TIMESTAMPTZ '2026-05-20 00:00:00+02'
     58  AND event_start_time <  TIMESTAMPTZ '2026-08-21 00:00:00+02';
     59}}}
     60
     61=== Пред оптимизацијата
     62
     63Времето било **0.103 ms**. PostgreSQL ги проверува повиците и дополнително ги филтрира според претплатата и периодот.
     64
     65=== Избран индекс
     66
     67{{{
     68CREATE INDEX idx_bench_calls_subscription_time
     69ON public.usage_cdr_calls (subscription_id, event_start_time DESC);
     70}}}
     71
     72`subscription_id` е прв бидејќи се бара една претплата. `event_start_time` е втор бидејќи потоа се ограничува временскиот период. По креирањето, двата услови може да се појават во `Index Cond`.
     73
     74=== По оптимизацијата
     75
     76Времето се намалило на **0.093 ms**, односно подобрување од **9.7%**. Разликата е мала поради малиот резултат, но индексот станува поважен со растењето на CDR табелата.
     77
     78[attachment:"02 idx_bench_calls_subscription_time.sql"]
     79[attachment:"02 Customer call history.sql"]
     80
     81== 3. Интернет сесии по претплата и време ==
     82
     83Поврзан поглед: `v_customer_data_usage_history`
     84
     85{{{
     86SELECT count(*), max(session_start),
     87       sum(data_used_mb), sum(charge_amount)
     88FROM public.usage_cdr_data
     89WHERE subscription_id = :subscription_id
     90  AND session_start >= TIMESTAMPTZ '2026-05-20 00:00:00+02'
     91  AND session_start <  TIMESTAMPTZ '2026-08-21 00:00:00+02';
     92}}}
     93
     94=== Пред оптимизацијата
     95
     96Времето било **0.152 ms**. Планот без индекс ги филтрира интернет сесиите по читањето на табелата.
     97
     98=== Избран индекс
     99
     100{{{
     101CREATE INDEX idx_bench_data_subscription_time
     102ON public.usage_cdr_data (subscription_id, session_start DESC);
     103}}}
     104
     105Редоследот го следи прашалникот: точна претплата, а потоа временски опсег. Со индексот, двата услови може заедно да се користат во `Index Cond`.
     106
     107=== По оптимизацијата
     108
     109Времето се намалило на **0.141 ms**, односно подобрување од **7.2%**.
     110
     111[attachment:"04 idx_bench_data_subscription_time.sql"]
     112[attachment:"04 Customer data usage history.sql"]
     113
     114== 4. Дневна потрошувачка по претплата и датум ==
     115
     116Поврзан поглед: `v_customer_daily_usage_summary`
     117
     118{{{
     119SELECT count(*), sum(total_call_seconds), sum(total_sms_count),
     120       sum(total_data_mb), sum(total_charge_amount)
     121FROM public.usage_aggregates_daily
     122WHERE subscription_id = :subscription_id
     123  AND usage_date BETWEEN DATE '2026-05-20' AND DATE '2026-08-20';
     124}}}
     125
     126=== Пред оптимизацијата
     127
     128Времето било **0.180 ms**. Без индекс се проверуваат и дневни записи што не припаѓаат на избраната претплата или период.
     129
     130=== Избран индекс
     131
     132{{{
     133CREATE INDEX idx_bench_daily_subscription_date
     134ON public.usage_aggregates_daily (subscription_id, usage_date DESC);
     135}}}
     136
     137`subscription_id` е колоната со еднаквост, а `usage_date` е временскиот опсег. Индексот го намалува бројот на редови што стигнуваат до агрегатните функции.
     138
     139=== По оптимизацијата
     140
     141Времето се намалило на **0.162 ms**, односно подобрување од **10.0%**.
     142
     143[attachment:"05 idx_bench_daily_subscription_date.sql"]
     144[attachment:"05 Customer daily usage summary.sql"]
     145
     146== 5. CRM тикети по корисник и датум ==
     147
     148Поврзан поглед: `v_customer_support_ticket_timeline`
     149
     150{{{
     151SELECT count(*), max(created_at), max(priority), max(status)
     152FROM public.crm_tickets
     153WHERE customer_id = :customer_id
     154  AND created_at >= TIMESTAMPTZ '2026-01-01 00:00:00+01'
     155  AND created_at <  TIMESTAMPTZ '2026-08-21 00:00:00+02';
     156}}}
     157
     158=== Пред оптимизацијата
     159
     160Времето било **0.473 ms**. Планот чита повеќе тикети и потоа ги отстранува тие што не припаѓаат на избраниот корисник и период.
     161
     162=== Избран индекс
     163
     164{{{
     165CREATE INDEX idx_bench_tickets_customer_date
     166ON public.crm_tickets (customer_id, created_at DESC)
     167INCLUDE (account_id, subscription_id,
     168         assigned_employee_id, status, priority);
     169}}}
     170
     171`customer_id` и `created_at` се индексни клучеви бидејќи се користат во WHERE. Другите колони се во INCLUDE бидејќи се потребни за timeline погледот. Така е можен `Index Only Scan`.
     172
     173=== По оптимизацијата
     174
     175Времето се намалило на **0.079 ms**, што е подобрување од **83.3%** и забрзување од **5.99 пати**.
     176
     177[attachment:"14 idx_bench_tickets_customer_date.sql"]
     178[attachment:"12 Customer support ticket timeline.sql"]
     179
     180== 6. Мрежни прекини по локација и време ==
     181
     182Поврзан поглед: `v_network_outage_operations`
     183
     184{{{
     185SELECT count(*), max(start_time), max(status)
     186FROM public.outages
     187WHERE site_id = :site_id
     188  AND start_time >= TIMESTAMPTZ '2025-01-01 00:00:00+01'
     189  AND start_time <  TIMESTAMPTZ '2026-08-21 00:00:00+02';
     190}}}
     191
     192=== Пред оптимизацијата
     193
     194Времето било **0.071 ms**. Условите за локација и време се применуваат врз веќе прочитаните прекини.
     195
     196=== Избран индекс
     197
     198{{{
     199CREATE INDEX idx_bench_outages_site_date
     200ON public.outages (site_id, start_time DESC)
     201INCLUDE (alarm_id, status, outage_type, end_time);
     202}}}
     203
     204`site_id` е прв затоа што се анализира една локација. `start_time` е втор за периодот. INCLUDE колоните ги содржат главните податоци од оперативниот поглед.
     205
     206=== По оптимизацијата
     207
     208Времето се намалило на **0.059 ms**, односно подобрување од **16.9%**.
     209
     210[attachment:"17 idx_bench_outages_site_date.sql"]
     211[attachment:"15 Network outage operations.sql"]
     212
     213== 7. Сообраќај на повици по сектор и време ==
     214
     215Поврзан поглед: `v_sector_traffic_map`
     216
     217{{{
     218SELECT count(*), max(event_start_time),
     219       sum(duration_seconds), sum(charge_amount)
     220FROM public.usage_cdr_calls
     221WHERE sector_id = :sector_id
     222  AND event_start_time >= TIMESTAMPTZ '2026-05-20 00:00:00+02'
     223  AND event_start_time <  TIMESTAMPTZ '2026-08-21 00:00:00+02';
     224}}}
     225
     226=== Пред оптимизацијата
     227
     228Времето било **50.042 ms**. EXPLAIN ANALYZE покажува `Seq Scan` врз големата CDR табела. Се читаат многу повици, а потоа се отстрануваат тие што не припаѓаат на избраниот сектор или период.
     229
     230=== Избран индекс
     231
     232{{{
     233CREATE INDEX idx_bench_calls_sector_time
     234ON public.usage_cdr_calls (sector_id, event_start_time)
     235INCLUDE (duration_seconds, charge_amount);
     236}}}
     237
     238`sector_id` е прв поради точниот услов, а `event_start_time` е втор поради временскиот опсег. Вредностите за SUM се во INCLUDE. По додавањето, планот може да користи `Index Only Scan` и да ги чита само потребните повици.
     239
     240=== По оптимизацијата
     241
     242Времето се намалило на **0.627 ms**. Прашалникот е **79.81 пати побрз**, односно времето е намалено за **98.7%**. Ова е најголемото Telecom подобрување.
     243
     244[attachment:"22 idx_bench_calls_sector_time.sql"]
     245[attachment:"06 Sector traffic map.sql"]
     246
     247== 8. Кориснички адреси покриени со GIS полигон ==
     248
     249Поврзан поглед: `v_customer_coverage_detail`
     250
     251{{{
     252SELECT count(*)
     253FROM public.customer_addresses address
     254WHERE address.is_primary
     255  AND address.location IS NOT NULL
     256  AND EXISTS (
     257      SELECT 1
     258      FROM public.coverage_zones coverage
     259      WHERE coverage.coverage_area IS NOT NULL
     260        AND coverage.coverage_area && address.location
     261        AND ST_Covers(coverage.coverage_area, address.location)
     262  );
     263}}}
     264
     265=== Пред оптимизацијата
     266
     267Времето било **152.839 ms**. Без просторен индекс се проверуваат многу комбинации од адресни точки и полигони.
     268
     269=== Избран индекс
     270
     271{{{
     272CREATE INDEX idx_coverage_zones_area_gist
     273ON public.coverage_zones
     274USING gist (coverage_area);
     275}}}
     276
     277GiST е избран затоа што прашалникот работи со полигони. Операторот `&&` преку индексот прво избира можни полигони, а `ST_Covers` ја прави точната проверка само врз тие кандидати.
     278
     279=== По оптимизацијата
     280
     281Времето се намалило на **149.056 ms**, односно подобрување од **2.5%**. Добивката е мала бидејќи ST_Covers сè уште мора да провери голем број адреси.
     282
     283[attachment:"04 Coverage area spatial index.sql"]
     284[attachment:"04 Customer coverage detail.sql"]
     285
     286== 9. Најблиска активна мрежна локација ==
     287
     288Поврзан поглед и функција: `v_network_site_points` и `find_nearest_network_sites`
     289
     290Прашалникот ја наоѓа најблиската активна локација за 2 000 адреси. Растојанието се пресметува во метри со geography.
     291
     292=== Пред оптимизацијата
     293
     294Времето било **208.732 ms**. Без соодветен индекс PostgreSQL ги споредува и сортира мрежните локации за секоја адреса.
     295
     296=== Избран индекс
     297
     298{{{
     299CREATE INDEX idx_network_sites_location_geography_gist
     300ON public.network_sites
     301USING gist ((location::geography));
     302}}}
     303
     304Индексот е врз `location::geography` бидејќи прашалникот го користи истиот израз. GiST го поддржува операторот `<->`, со кој директно се бара најблиската локација.
     305
     306=== По оптимизацијата
     307
     308Планот може да користи `Index Scan` подреден по растојание. Времето се намалило на **52.625 ms**, што е подобрување од **74.8%** и забрзување од **3.97 пати**.
     309
     310[attachment:"03 Network site geography index.sql"]
     311[attachment:"01 Network site points.sql"]
     312[attachment:"06 Find nearest network sites.sql"]
     313
     314== 10. Кориснички адреси во радиус од 2 km ==
     315
     316{{{
     317SELECT count(*), max(address.address_id)
     318FROM public.customer_addresses address
     319JOIN public.network_sites site ON site.site_id = :site_id
     320WHERE address.location IS NOT NULL
     321  AND address.location && ST_Envelope(
     322      ST_Buffer(site.location::geography, 2000)::geometry
     323  )
     324  AND ST_DWithin(
     325      address.location::geography,
     326      site.location::geography,
     327      2000
     328  );
     329}}}
     330
     331=== Пред оптимизацијата
     332
     333Времето било **39.591 ms**. Без индекс PostgreSQL ја чита customer_addresses и ја проверува оддалеченоста за голем број адреси.
     334
     335=== Избран индекс
     336
     337{{{
     338CREATE INDEX idx_customer_addresses_location_gist
     339ON public.customer_addresses
     340USING gist (location);
     341}}}
     342
     343GiST индексот го користи условот `location && ...` за да ги избере точките во приближната област. `ST_DWithin` потоа го проверува точното растојание само за тие кандидати. Во планот ова може да се појави како `Bitmap Index Scan`.
     344
     345=== По оптимизацијата
     346
     347Времето се намалило на **1.149 ms**. Прашалникот е **34.46 пати побрз**, односно времето е намалено за **97.1%**. Ова е најголемото GIS подобрување.
     348
     349[attachment:"01 Customer address spatial index.sql"]
     350
     351== Вкупен резултат ==
     352
     353За десетте прашалници, збирот на медијаните се намалил од **452.530 ms** на **204.042 ms**. Вкупно се заштедени **248.488 ms**, што е намалување од **54.9%** и забрзување од **2.22 пати**.
     354
     355Најголемо подобрување има кај сообраќајот по сектор и GIS пребарувањето на адреси во радиус од 2 km. Во двата случаи EXPLAIN ANALYZE покажува дека проблемот бил читање голем број непотребни редови, а индексот овозможува директен пристап до релевантното множество.
     356
     357Индексите се избрани според реалните услови во прашалниците. Колоните во `Index Cond` се клучеви на индексот, колоните потребни само за резултатот се ставаат во INCLUDE, а за просторни операции се користи GiST. Така секој задржан индекс има конкретна намена и измерено подобрување.
     358