Changes between Initial Version and Version 1 of AdvancedTopics


Ignore:
Timestamp:
09/16/26 09:45:43 (2 weeks ago)
Author:
231081
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedTopics

    v1 v1  
     1= Напредни теми =
     2
     3Во рамки на проектот се имплементирани две напредни теми:
     4
     51. Геопросторно пребарување на најблиски продавници
     62. Партиционирање на историјата на нарачки според година
     7
     8= 1. Геопросторно пребарување =
     9
     10== 1.1 Цел ==
     11
     12Целта на оваа функционалност е за дадена адреса на корисник да се пронајдат продавниците што се наоѓаат во зададен радиус. Продавниците се подредуваат според растојанието од корисникот.
     13
     14Бидејќи PostGIS екстензијата не е достапна на серверот, решението е имплементирано со вградените можности на PostgreSQL:
     15
     16* географска ширина и должина;
     17* вградениот тип `point`;
     18* GiST просторен индекс;
     19* ограничувачки правоаголник, односно bounding box;
     20* Haversine формула за пресметување растојание.
     21
     22== 1.2 Географски координати ==
     23
     24Во табелата `public.address` се додадени колоните `latitude` и `longitude`.
     25
     26{{{#!sql
     27ALTER TABLE public.address
     28ADD COLUMN latitude DOUBLE PRECISION
     29    CHECK (latitude BETWEEN -90 AND 90),
     30ADD COLUMN longitude DOUBLE PRECISION
     31    CHECK (longitude BETWEEN -180 AND 180);
     32}}}
     33
     34Ограничувањата `CHECK` спречуваат внесување невалидни координати.
     35
     36Бидејќи адресите во податочното множество се генерирани и не секогаш претставуваат реални адреси, за демонстрацијата се користат приближни координати на ниво на град. Изворот на координатите се означува како `CITY_APPROXIMATION`.
     37
     38== 1.3 Поглед со локации на продавници ==
     39
     40Погледот `advanced_gis.store_locations` ги поврзува продавниците со нивните адреси и координати.
     41
     42{{{#!sql
     43CREATE OR REPLACE VIEW advanced_gis.store_locations AS
     44SELECT
     45    si.store_instance_id,
     46    s.store_id,
     47    s.store_name,
     48    si.address_id,
     49    a.street,
     50    a.street_number,
     51    a.latitude,
     52    a.longitude,
     53    point(a.longitude, a.latitude) AS map_point,
     54    'CITY_APPROXIMATION'::VARCHAR(30) AS coordinate_source
     55FROM public.storeinstance si
     56JOIN public.store s
     57    ON s.store_id = si.store_id
     58JOIN public.address a
     59    ON a.address_id = si.address_id
     60WHERE a.latitude IS NOT NULL
     61  AND a.longitude IS NOT NULL;
     62}}}
     63
     64Колоната `map_point` ја претставува локацијата како PostgreSQL точка во формат:
     65
     66{{{
     67(longitude, latitude)
     68}}}
     69
     70== 1.4 Пресметување растојание со Haversine формула ==
     71
     72Функцијата `advanced_gis.distance_meters` го пресметува воздушното растојание меѓу две географски точки.
     73
     74Haversine формулата го зема предвид сферичниот облик на Земјата. Како радиус на Земјата се користи вредноста `6.371.000` метри.
     75
     76Функцијата прима:
     77
     78* географска ширина на првата точка;
     79* географска должина на првата точка;
     80* географска ширина на втората точка;
     81* географска должина на втората точка.
     82
     83Како резултат враќа растојание во метри.
     84
     85== 1.5 Пронаоѓање најблиски продавници ==
     86
     87Функцијата `advanced_gis.find_nearest_stores` прима идентификатор на адреса на корисник и максимален радиус во метри.
     88
     89{{{#!sql
     90SELECT *
     91FROM advanced_gis.find_nearest_stores(5745513, 5000);
     92}}}
     93
     94Функцијата работи во две фази:
     95
     961. Со bounding box брзо ги избира можните продавници во околината.
     972. За избраните кандидати го пресметува точното растојание со функцијата `distance_meters`.
     98
     99На крај се задржуваат само продавниците чие растојание е помало или еднакво на зададениот радиус, а резултатите се подредуваат од најблиска кон најдалечна продавница.
     100
     101== 1.6 GiST просторен индекс ==
     102
     103За побрзо пребарување според координати е креиран парцијален GiST индекс.
     104
     105{{{#!sql
     106CREATE INDEX IF NOT EXISTS address_coordinates_gist_idx
     107ON public.address
     108USING GIST (
     109    (point(longitude, latitude))
     110)
     111WHERE latitude IS NOT NULL
     112  AND longitude IS NOT NULL;
     113}}}
     114
     115Индексот ги содржи само адресите што имаат координати. Тој се користи за побрзо просторно филтрирање на кандидатите пред пресметувањето на точното растојание.
     116
     117== 1.7 Ограничување на решението ==
     118
     119Имплементацијата не користи PostGIS. Таа претставува геопросторно решение изработено со основните типови и индекси на PostgreSQL.
     120
     121PostGIS не беше достапен на серверот, а корисникот на базата нема администраторски права за негово инсталирање. Поради тоа се користат `point`, GiST индекс и Haversine формула.
     122
     123= 2. Партиционирање на нарачките =
     124
     125== 2.1 Цел ==
     126
     127Табелата `public."Order"` содржи `10.000.013` нарачки со датуми од `2021-01-01` до `2026-05-10`.
     128
     129Целта на партиционирањето е големата логичка табела да се подели на помали физички табели според годината на нарачката.
     130
     131Оригиналната табела `public."Order"` не се менува. За демонстрацијата е креирана посебна шема `advanced_partitioning`.
     132
     133== 2.2 Родителска партиционирана табела ==
     134
     135{{{#!sql
     136CREATE SCHEMA IF NOT EXISTS advanced_partitioning;
     137
     138CREATE TABLE advanced_partitioning.order_history (
     139    LIKE public."Order"
     140)
     141PARTITION BY RANGE (order_date);
     142}}}
     143
     144`PARTITION BY RANGE (order_date)` означува дека редовите се распределуваат во партиции според опсегот на `order_date`.
     145
     146== 2.3 Годишни партиции ==
     147
     148За секоја година е креирана посебна партиција.
     149
     150{{{#!sql
     151CREATE TABLE advanced_partitioning.order_history_2021
     152PARTITION OF advanced_partitioning.order_history
     153FOR VALUES FROM ('2021-01-01') TO ('2022-01-01');
     154
     155CREATE TABLE advanced_partitioning.order_history_2022
     156PARTITION OF advanced_partitioning.order_history
     157FOR VALUES FROM ('2022-01-01') TO ('2023-01-01');
     158
     159CREATE TABLE advanced_partitioning.order_history_2023
     160PARTITION OF advanced_partitioning.order_history
     161FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
     162
     163CREATE TABLE advanced_partitioning.order_history_2024
     164PARTITION OF advanced_partitioning.order_history
     165FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
     166
     167CREATE TABLE advanced_partitioning.order_history_2025
     168PARTITION OF advanced_partitioning.order_history
     169FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');
     170
     171CREATE TABLE advanced_partitioning.order_history_2026
     172PARTITION OF advanced_partitioning.order_history
     173FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');
     174}}}
     175
     176Долната граница е вклучена, додека горната граница не е вклучена. На пример, партицијата за 2025 година ги чува датумите:
     177
     178{{{
     1792025-01-01 <= order_date < 2026-01-01
     180}}}
     181
     182Креирана е и `DEFAULT` партиција за датуми што не припаѓаат во дефинираните годишни опсези.
     183
     184{{{#!sql
     185CREATE TABLE advanced_partitioning.order_history_default
     186PARTITION OF advanced_partitioning.order_history
     187DEFAULT;
     188}}}
     189
     190== 2.4 Внесување примерок од реалните податоци ==
     191
     192За тестирање е земен приближно 1% примерок од оригиналната табела.
     193
     194{{{#!sql
     195INSERT INTO advanced_partitioning.order_history
     196SELECT *
     197FROM public."Order"
     198TABLESAMPLE SYSTEM (1)
     199REPEATABLE (2026);
     200}}}
     201
     202Вметнати се вкупно `102.606` реални нарачки. PostgreSQL автоматски го насочува секој ред во соодветната партиција според `order_date`.
     203
     204Добиена е следната распределба:
     205
     206|| Партиција || Број на нарачки ||
     207|| 2021 || 19.122 ||
     208|| 2022 || 19.251 ||
     209|| 2023 || 19.015 ||
     210|| 2024 || 19.330 ||
     211|| 2025 || 19.273 ||
     212|| 2026 || 6.615 ||
     213
     214== 2.5 Индекс на датумот ==
     215
     216На родителската табела е дефиниран индекс врз `order_date`.
     217
     218{{{#!sql
     219CREATE INDEX IF NOT EXISTS order_history_order_date_idx
     220ON advanced_partitioning.order_history (order_date);
     221
     222ANALYZE advanced_partitioning.order_history;
     223}}}
     224
     225PostgreSQL автоматски креира соодветни индекси на поединечните партиции.
     226
     227== 2.6 Partition pruning ==
     228
     229Ефикасноста на партиционирањето е проверена со следниот план за извршување:
     230
     231{{{#!sql
     232EXPLAIN (ANALYZE, BUFFERS)
     233SELECT COUNT(*)
     234FROM advanced_partitioning.order_history
     235WHERE order_date >= DATE '2025-01-01'
     236  AND order_date < DATE '2026-01-01';
     237}}}
     238
     239Во планот за извршување се добива:
     240
     241{{{
     242Index Only Scan using order_history_2025_order_date_idx
     243on order_history_2025
     244Heap Fetches: 0
     245Execution Time: 3.486 ms
     246}}}
     247
     248Иако барањето се извршува над родителската табела `order_history`, PostgreSQL пристапува само до `order_history_2025`.
     249
     250Оваа оптимизација се нарекува `partition pruning`. Од условот за датум PostgreSQL определува дека потребните податоци можат да се наоѓаат само во партицијата за 2025 година и ги исклучува останатите партиции од пребарувањето.
     251
     252Потоа се применува `Index Only Scan`, со што податоците се читаат директно од индексот, без дополнително читање од табелата.
     253
     254= Заклучок =
     255
     256Со геопросторното пребарување се овозможува пронаоѓање продавници во зададен радиус преку координати, просторен индекс и Haversine формула.
     257
     258Со партиционирањето на нарачките по година се намалува бројот на податоци што треба да се пребаруваат. Комбинацијата од partition pruning и индексирање овозможува поефикасно извршување на временски ограничени барања над голема историја на нарачки.