Changes between Initial Version and Version 1 of SmallDatabasePerformance


Ignore:
Timestamp:
06/27/26 12:57:22 (35 hours ago)
Author:
211101
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • SmallDatabasePerformance

    v1 v1  
     1Анализата е направена со `EXPLAIN (ANALYZE, BUFFERS)`, при што се споредува планот за извршување пред и по додавање на дополнителните индекси. Во почетната состојба базата ги содржи само индексите кои PostgreSQL автоматски ги креира за `PRIMARY KEY` и `UNIQUE` ограничувања. Овие индекси не се третираат како дополнителна оптимизација, туку како дел од основната релациска структура на базата.
     2
     3Во моментот на тестирањето, базата ја има следната количина на податоци:
     4
     5||= Табела =||= Број на редови =||
     6|| `user` || 3 ||
     7|| `transaction` || 12 ||
     8|| `transaction_account` || 5 ||
     9|| `transaction_breakdown` || 11 ||
     10|| `tag` || 11 ||
     11|| `tag_assigned_to_transaction` || 16 ||
     12
     13Поради малата количина на податоци, очекувано е PostgreSQL во повеќе случаи да избере `Seq Scan`, бидејќи целосното читање на мала табела е поевтино од пристап преку индекс. Затоа, во анализата не се гледа само времето на извршување, туку и дали индексот навистина се користи во execution plan-от.
     14
     15==== Предложени дополнителни индекси ====
     16
     17Следните индекси се предложени затоа што колоните често се користат во `JOIN` и `WHERE` услови во аналитичките извештаи:
     18
     19{{{#!sql
     20CREATE INDEX IF NOT EXISTS idx_transaction_date
     21ON "transaction"(date);
     22
     23CREATE INDEX IF NOT EXISTS idx_transaction_account_user_id
     24ON transaction_account(user_id);
     25
     26CREATE INDEX IF NOT EXISTS idx_transaction_breakdown_transaction_id
     27ON transaction_breakdown(transaction_id);
     28
     29CREATE INDEX IF NOT EXISTS idx_transaction_breakdown_account_id
     30ON transaction_breakdown(transaction_account_id);
     31
     32CREATE INDEX IF NOT EXISTS idx_tag_assigned_transaction_id
     33ON tag_assigned_to_transaction(transaction_id);
     34
     35CREATE INDEX IF NOT EXISTS idx_tag_assigned_tag_id
     36ON tag_assigned_to_transaction(tag_id);
     37}}}
     38
     39Индексот `idx_transaction_date` е наменет за извештаи кои филтрираат трансакции според временски период. Индексот `idx_transaction_account_user_id` е наменет за извештаи кои ги ограничуваат резултатите на конкретен корисник. Индексите на `transaction_breakdown` и `tag_assigned_to_transaction` се наменети за побрзо поврзување на трансакциите со сметки и тагови.
     40
     41==== Сценарио 1: Трендови на трошење според тагови за корисник ====
     42
     43'''Цел:'''
     44Овој извештај ја прикажува потрошувачката по тагови за конкретен корисник во последните шест месеци. Прашалникот е комплексен затоа што ги поврзува табелите `tag_assigned_to_transaction`, `transaction`, `transaction_breakdown`, `transaction_account` и `tag`, а дополнително врши филтрирање по корисник и датум, групирање по таг и месец, и сумирање на потрошената сума.
     45
     46'''Анализиран SQL:'''
     47
     48{{{#!sql
     49EXPLAIN (ANALYZE, BUFFERS)
     50SELECT
     51tg.tag_name,
     52DATE_TRUNC('month', t.date) AS month,
     53SUM(tb.spent_amount) AS total_spent
     54FROM tag_assigned_to_transaction tat
     55JOIN "transaction" t
     56ON tat.transaction_id = t.transaction_id
     57JOIN transaction_breakdown tb
     58ON t.transaction_id = tb.transaction_id
     59JOIN transaction_account ta
     60ON tb.transaction_account_id = ta.transaction_account_id
     61JOIN tag tg
     62ON tat.tag_id = tg.tag_id
     63WHERE
     64ta.user_id = 1
     65AND t.date >= DATE_TRUNC('month', CURRENT_DATE - INTERVAL '6 MONTHS')
     66GROUP BY
     67tg.tag_name, month
     68ORDER BY
     69tg.tag_name, month;
     70}}}
     71
     72'''Релевантни индекси за ова сценарио:'''
     73
     74{{{#!sql
     75CREATE INDEX IF NOT EXISTS idx_transaction_date
     76ON "transaction"(date);
     77
     78CREATE INDEX IF NOT EXISTS idx_transaction_account_user_id
     79ON transaction_account(user_id);
     80
     81CREATE INDEX IF NOT EXISTS idx_transaction_breakdown_transaction_id
     82ON transaction_breakdown(transaction_id);
     83
     84CREATE INDEX IF NOT EXISTS idx_transaction_breakdown_account_id
     85ON transaction_breakdown(transaction_account_id);
     86
     87CREATE INDEX IF NOT EXISTS idx_tag_assigned_transaction_id
     88ON tag_assigned_to_transaction(transaction_id);
     89
     90CREATE INDEX IF NOT EXISTS idx_tag_assigned_tag_id
     91ON tag_assigned_to_transaction(tag_id);
     92}}}
     93
     94'''План пред додавање на индексите:'''
     95
     96{{{#!sql
     97GroupAggregate  (cost=6.54..6.77 rows=9 width=53) (actual time=0.176..0.189 rows=7 loops=1)
     98Group Key: tg.tag_name, (date_trunc('month'::text, t.date))
     99Buffers: shared hit=5
     100->  Sort  (cost=6.54..6.57 rows=9 width=25) (actual time=0.162..0.167 rows=16 loops=1)
     101Sort Key: tg.tag_name, (date_trunc('month'::text, t.date))
     102Sort Method: quicksort  Memory: 25kB
     103->  Hash Join  (cost=4.99..6.40 rows=9 width=25) (actual time=0.119..0.142 rows=16 loops=1)
     104Hash Cond: (tat.tag_id = tg.tag_id)
     105->  Hash Join  (cost=3.74..5.10 rows=9 width=16) (actual time=0.086..0.099 rows=16 loops=1)
     106Hash Cond: (tat.transaction_id = t.transaction_id)
     107->  Hash Join  (cost=2.35..3.67 rows=10 width=16) (actual time=0.057..0.066 rows=16 loops=1)
     108Hash Cond: (tat.transaction_id = tb.transaction_id)
     109->  Seq Scan on tag_assigned_to_transaction tat
     110->  Hash
     111->  Hash Join
     112Hash Cond: (tb.transaction_account_id = ta.transaction_account_id)
     113->  Seq Scan on transaction_breakdown tb
     114->  Hash
     115->  Seq Scan on transaction_account ta
     116Filter: (user_id = 1)
     117Rows Removed by Filter: 2
     118->  Hash
     119->  Seq Scan on transaction t
     120Filter: (date >= date_trunc('month'::text, (CURRENT_DATE - '6 mons'::interval)))
     121->  Hash
     122->  Seq Scan on tag tg
     123Planning Time: 0.733 ms
     124Execution Time: 0.296 ms
     125}}}
     126
     127'''План по додавање на индексите:'''
     128
     129{{{#!sql
     130GroupAggregate  (cost=6.54..6.77 rows=9 width=53) (actual time=0.154..0.166 rows=7 loops=1)
     131Group Key: tg.tag_name, (date_trunc('month'::text, t.date))
     132Buffers: shared hit=5
     133->  Sort  (cost=6.54..6.57 rows=9 width=25) (actual time=0.142..0.146 rows=16 loops=1)
     134Sort Key: tg.tag_name, (date_trunc('month'::text, t.date))
     135Sort Method: quicksort  Memory: 25kB
     136->  Hash Join  (cost=4.99..6.40 rows=9 width=25) (actual time=0.100..0.122 rows=16 loops=1)
     137Hash Cond: (tat.tag_id = tg.tag_id)
     138->  Hash Join  (cost=3.74..5.10 rows=9 width=16) (actual time=0.068..0.081 rows=16 loops=1)
     139Hash Cond: (tat.transaction_id = t.transaction_id)
     140->  Hash Join  (cost=2.35..3.67 rows=10 width=16) (actual time=0.040..0.049 rows=16 loops=1)
     141Hash Cond: (tat.transaction_id = tb.transaction_id)
     142->  Seq Scan on tag_assigned_to_transaction tat
     143->  Hash
     144->  Hash Join
     145Hash Cond: (tb.transaction_account_id = ta.transaction_account_id)
     146->  Seq Scan on transaction_breakdown tb
     147->  Hash
     148->  Seq Scan on transaction_account ta
     149Filter: (user_id = 1)
     150Rows Removed by Filter: 2
     151->  Hash
     152->  Seq Scan on transaction t
     153Filter: (date >= date_trunc('month'::text, (CURRENT_DATE - '6 mons'::interval)))
     154->  Hash
     155->  Seq Scan on tag tg
     156Planning Time: 0.921 ms
     157Execution Time: 0.242 ms
     158}}}
     159
     160'''Споредба:'''
     161
     162||= Метрика =||= Пред индекси =||= По индекси =||
     163|| Planning Time || 0.733 ms || 0.921 ms ||
     164|| Execution Time || 0.296 ms || 0.242 ms ||
     165|| Тип на скенирање || `Seq Scan` || `Seq Scan` ||
     166|| Дали новите индекси се користат || Не || Не ||
     167
     168'''Заклучок:'''
     169Времето на извршување се намали од 0.296 ms на 0.242 ms, што претставува намалување од приближно 18.2%. Сепак, execution plan-от и по додавање на индексите користи `Seq Scan` над сите релевантни табели, а не `Index Scan` или `Bitmap Index Scan`. Затоа подобрувањето не може да се припише директно на индексите. Најверојатната причина е малата количина на податоци и фактот што сите блокови се веќе во shared buffers. Индексите остануваат логични за идно зголемување на бројот на трансакции, тагови и breakdown записи.
     170
     171==== Сценарио 2: Сметки со највисоко вкупно трошење во изминатата година ====
     172
     173'''Цел:'''
     174Овој извештај ги прикажува сметките со најголема вкупна потрошувачка во последната година. Прашалникот користи `JOIN` помеѓу `transaction_breakdown`, `transaction` и `transaction_account`, филтрирање по датум, `SUM` агрегација, `GROUP BY`, `ORDER BY` и `LIMIT`.
     175
     176'''Анализиран SQL:'''
     177
     178{{{#!sql
     179EXPLAIN (ANALYZE, BUFFERS)
     180SELECT
     181ta.account_name,
     182COALESCE(SUM(tb.spent_amount), 0) AS total_spent
     183FROM transaction_breakdown tb
     184JOIN "transaction" t
     185ON tb.transaction_id = t.transaction_id
     186JOIN transaction_account ta
     187ON tb.transaction_account_id = ta.transaction_account_id
     188WHERE
     189t.date >= NOW() - INTERVAL '1 YEAR'
     190GROUP BY
     191ta.account_name
     192ORDER BY
     193total_spent DESC
     194LIMIT 10;
     195}}}
     196
     197'''Релевантни индекси за ова сценарио:'''
     198
     199{{{#!sql
     200CREATE INDEX IF NOT EXISTS idx_transaction_date
     201ON "transaction"(date);
     202
     203CREATE INDEX IF NOT EXISTS idx_transaction_breakdown_transaction_id
     204ON transaction_breakdown(transaction_id);
     205
     206CREATE INDEX IF NOT EXISTS idx_transaction_breakdown_account_id
     207ON transaction_breakdown(transaction_account_id);
     208}}}
     209
     210'''План пред додавање на индексите:'''
     211
     212{{{#!sql
     213Limit  (cost=3.85..3.86 rows=5 width=40) (actual time=0.110..0.114 rows=3 loops=1)
     214Buffers: shared hit=3
     215->  Sort  (cost=3.85..3.86 rows=5 width=40) (actual time=0.108..0.111 rows=3 loops=1)
     216Sort Key: (COALESCE(sum(tb.spent_amount), '0'::numeric)) DESC
     217Sort Method: quicksort  Memory: 25kB
     218->  HashAggregate  (cost=3.73..3.79 rows=5 width=40) (actual time=0.097..0.101 rows=3 loops=1)
     219Group Key: ta.account_name
     220->  Hash Join  (cost=2.47..3.67 rows=11 width=12) (actual time=0.059..0.069 rows=11 loops=1)
     221Hash Cond: (tb.transaction_account_id = ta.transaction_account_id)
     222->  Hash Join  (cost=1.36..2.51 rows=11 width=8) (actual time=0.032..0.038 rows=11 loops=1)
     223Hash Cond: (tb.transaction_id = t.transaction_id)
     224->  Seq Scan on transaction_breakdown tb
     225->  Hash
     226->  Seq Scan on transaction t
     227Filter: (date >= (now() - '1 year'::interval))
     228->  Hash
     229->  Seq Scan on transaction_account ta
     230Planning Time: 0.403 ms
     231Execution Time: 0.191 ms
     232}}}
     233
     234'''План по додавање на индексите:'''
     235
     236{{{#!sql
     237Limit  (cost=3.85..3.86 rows=5 width=40) (actual time=0.100..0.104 rows=3 loops=1)
     238Buffers: shared hit=3
     239->  Sort  (cost=3.85..3.86 rows=5 width=40) (actual time=0.098..0.101 rows=3 loops=1)
     240Sort Key: (COALESCE(sum(tb.spent_amount), '0'::numeric)) DESC
     241Sort Method: quicksort  Memory: 25kB
     242->  HashAggregate  (cost=3.73..3.79 rows=5 width=40) (actual time=0.086..0.090 rows=3 loops=1)
     243Group Key: ta.account_name
     244->  Hash Join  (cost=2.47..3.67 rows=11 width=12) (actual time=0.060..0.070 rows=11 loops=1)
     245Hash Cond: (tb.transaction_account_id = ta.transaction_account_id)
     246->  Hash Join  (cost=1.36..2.51 rows=11 width=8) (actual time=0.030..0.036 rows=11 loops=1)
     247Hash Cond: (tb.transaction_id = t.transaction_id)
     248->  Seq Scan on transaction_breakdown tb
     249->  Hash
     250->  Seq Scan on transaction t
     251Filter: (date >= (now() - '1 year'::interval))
     252->  Hash
     253->  Seq Scan on transaction_account ta
     254Planning Time: 0.451 ms
     255Execution Time: 0.166 ms
     256}}}
     257
     258'''Споредба:'''
     259
     260||= Метрика =||= Пред индекси =||= По индекси =||
     261|| Planning Time || 0.403 ms || 0.451 ms ||
     262|| Execution Time || 0.191 ms || 0.166 ms ||
     263|| Тип на скенирање || `Seq Scan` || `Seq Scan` ||
     264|| Дали новите индекси се користат || Не || Не ||
     265
     266'''Заклучок:'''
     267Времето на извршување се намали од 0.191 ms на 0.166 ms, што претставува намалување од приближно 13.1%. Сепак, планот за извршување не користи ниту еден од новите индекси. PostgreSQL продолжува со `Seq Scan` на `transaction`, `transaction_breakdown` и `transaction_account`, затоа што табелите имаат многу мал број редови и читањето на целата табела е поевтино од пристап преку индекс. Поради тоа, малото подобрување не се смета како директен ефект од индексите. Индексот на `transaction.date` сепак е оправдан за идно зголемување на бројот на трансакции, бидејќи извештајот филтрира според временски период.
     268
     269==== Сценарио 3: Годишни трендови на трансакции за корисник ====
     270
     271'''Цел:'''
     272Овој извештај ја прикажува потрошувачката по квартали за конкретен корисник во тековната година. Прашалникот користи `JOIN` помеѓу `transaction_breakdown`, `transaction` и `transaction_account`, филтрирање по корисник и временски период, `SUM` агрегации со `CASE WHEN`, `GROUP BY` и `ORDER BY`.
     273
     274Во анализата условот за година е напишан како временски опсег, наместо само со `EXTRACT(YEAR FROM t.date)`, за B-Tree индексот на `date` да може поефикасно да се користи кога табелата ќе порасне.
     275
     276'''Анализиран SQL:'''
     277
     278{{{#!sql
     279EXPLAIN (ANALYZE, BUFFERS)
     280SELECT
     281ta.account_name,
     282SUM(CASE WHEN EXTRACT(QUARTER FROM t.date) = 1 THEN tb.spent_amount ELSE 0 END) AS q1_spent,
     283SUM(CASE WHEN EXTRACT(QUARTER FROM t.date) = 2 THEN tb.spent_amount ELSE 0 END) AS q2_spent,
     284SUM(CASE WHEN EXTRACT(QUARTER FROM t.date) = 3 THEN tb.spent_amount ELSE 0 END) AS q3_spent,
     285SUM(CASE WHEN EXTRACT(QUARTER FROM t.date) = 4 THEN tb.spent_amount ELSE 0 END) AS q4_spent
     286FROM transaction_breakdown tb
     287JOIN "transaction" t
     288ON tb.transaction_id = t.transaction_id
     289JOIN transaction_account ta
     290ON tb.transaction_account_id = ta.transaction_account_id
     291WHERE
     292ta.user_id = 1
     293AND t.date >= DATE_TRUNC('year', CURRENT_DATE)
     294AND t.date < DATE_TRUNC('year', CURRENT_DATE) + INTERVAL '1 YEAR'
     295GROUP BY
     296ta.account_name
     297ORDER BY
     298ta.account_name;
     299}}}
     300
     301'''Релевантни индекси за ова сценарио:'''
     302
     303{{{#!sql
     304CREATE INDEX IF NOT EXISTS idx_transaction_date
     305ON "transaction"(date);
     306
     307CREATE INDEX IF NOT EXISTS idx_transaction_account_user_id
     308ON transaction_account(user_id);
     309
     310CREATE INDEX IF NOT EXISTS idx_transaction_breakdown_transaction_id
     311ON transaction_breakdown(transaction_id);
     312
     313CREATE INDEX IF NOT EXISTS idx_transaction_breakdown_account_id
     314ON transaction_breakdown(transaction_account_id);
     315}}}
     316
     317'''План пред додавање на индексите:'''
     318
     319{{{#!sql
     320Sort  (cost=4.16..4.17 rows=3 width=136) (actual time=0.141..0.144 rows=3 loops=1)
     321Sort Key: ta.account_name
     322Sort Method: quicksort  Memory: 25kB
     323Buffers: shared hit=3
     324->  HashAggregate  (cost=4.08..4.14 rows=3 width=136) (actual time=0.126..0.131 rows=3 loops=1)
     325Group Key: ta.account_name
     326->  Hash Join  (cost=2.35..3.85 rows=7 width=20) (actual time=0.070..0.089 rows=11 loops=1)
     327Hash Cond: (t.transaction_id = tb.transaction_id)
     328->  Seq Scan on transaction t
     329Filter: ((date >= date_trunc('year'::text, (CURRENT_DATE)::timestamp with time zone))
     330AND (date < (date_trunc('year'::text, (CURRENT_DATE)::timestamp with time zone) + '1 year'::interval)))
     331->  Hash
     332->  Hash Join
     333Hash Cond: (tb.transaction_account_id = ta.transaction_account_id)
     334->  Seq Scan on transaction_breakdown tb
     335->  Hash
     336->  Seq Scan on transaction_account ta
     337Filter: (user_id = 1)
     338Rows Removed by Filter: 2
     339Planning Time: 0.402 ms
     340Execution Time: 0.242 ms
     341}}}
     342
     343'''План по додавање на индексите:'''
     344
     345{{{#!sql
     346Sort  (cost=4.16..4.17 rows=3 width=136) (actual time=0.131..0.135 rows=3 loops=1)
     347Sort Key: ta.account_name
     348Sort Method: quicksort  Memory: 25kB
     349Buffers: shared hit=3
     350->  HashAggregate  (cost=4.08..4.14 rows=3 width=136) (actual time=0.117..0.122 rows=3 loops=1)
     351Group Key: ta.account_name
     352->  Hash Join  (cost=2.35..3.85 rows=7 width=20) (actual time=0.063..0.082 rows=11 loops=1)
     353Hash Cond: (t.transaction_id = tb.transaction_id)
     354->  Seq Scan on transaction t
     355Filter: ((date >= date_trunc('year'::text, (CURRENT_DATE)::timestamp with time zone))
     356AND (date < (date_trunc('year'::text, (CURRENT_DATE)::timestamp with time zone) + '1 year'::interval)))
     357->  Hash
     358->  Hash Join
     359Hash Cond: (tb.transaction_account_id = ta.transaction_account_id)
     360->  Seq Scan on transaction_breakdown tb
     361->  Hash
     362->  Seq Scan on transaction_account ta
     363Filter: (user_id = 1)
     364Rows Removed by Filter: 2
     365Planning Time: 0.500 ms
     366Execution Time: 0.212 ms
     367}}}
     368
     369'''Споредба:'''
     370
     371||= Метрика =||= Пред индекси =||= По индекси =||
     372|| Planning Time || 0.402 ms || 0.500 ms ||
     373|| Execution Time || 0.242 ms || 0.212 ms ||
     374|| Тип на скенирање || `Seq Scan` || `Seq Scan` ||
     375|| Дали новите индекси се користат || Не || Не ||
     376
     377'''Заклучок:'''
     378Времето на извршување се намали од 0.242 ms на 0.212 ms, што претставува намалување од приближно 12.4%. Сепак, планот останува ист во однос на пристапот до табелите: PostgreSQL користи `Seq Scan`, а не новите индекси. Причината е малата количина на податоци: `transaction` има 12 редови, `transaction_breakdown` има 11 редови, а `transaction_account` има 5 редови. За ваква големина, секвенцијалното читање е поевтино од пристап преку индекс. Индексите сепак се соодветни за идно зголемување на базата, особено затоа што извештајот филтрира по `user_id` и по временски период.
     379
     380==== Финален заклучок од анализата ====
     381
     382Во сите три сценарија, PostgreSQL продолжи да користи `Seq Scan` и по додавање на предложените индекси. Тоа значи дека индексите не беа реално искористени во моменталниот execution plan.
     383
     384Ова однесување е очекувано поради малата количина на податоци во тест базата. Во моментот на тестирањето, најголемите релевантни табели имаат само 12 трансакции, 11 breakdown записи и 16 tag-assignment записи. За вакви табели, PostgreSQL правилно проценува дека целосно скенирање на табелата е поевтино од користење индекс.
     385
     386Иако execution time се намали во сите три сценарија, тоа не може директно да се припише на индексите, бидејќи execution plan-от не покажува `Index Scan`, `Bitmap Index Scan` или `Index Only Scan`. Малите разлики во времето најверојатно се резултат на кеширање, повторно користење на податоци во shared buffers и нормална варијација при извршување.
     387
     388Сепак, предложените индекси остануваат оправдани како подготовка за поголема количина на податоци. Со раст на бројот на корисници, трансакции, сметки, тагови и breakdown записи, индексите на `transaction.date`, `transaction_account.user_id`, `transaction_breakdown.transaction_id`, `transaction_breakdown.transaction_account_id`, `tag_assigned_to_transaction.transaction_id` и `tag_assigned_to_transaction.tag_id` ќе овозможат поефикасно филтрирање и поврзување на податоците во аналитичките извештаи.