| 11 | | CREATE OR REPLACE FUNCTION test_portfolio_diversification() |
| 12 | | RETURNS void AS $$ |
| 13 | | DECLARE |
| 14 | | start_time timestamptz; |
| 15 | | end_time timestamptz; |
| 16 | | duration int; |
| 17 | | BEGIN |
| 18 | | start_time := clock_timestamp(); |
| 19 | | |
| 20 | | PERFORM |
| 21 | | ph.portfolio_id, |
| 22 | | ph.stock_id, |
| 23 | | s.symbol, |
| 24 | | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| 25 | | ph.quantity::NUMERIC * (s.current_price::NUMERIC |
| 26 | | - ph.avg_price::NUMERIC) AS unrealized_pnl |
| 27 | | FROM portfolio_holdings ph |
| 28 | | JOIN stock s ON ph.stock_id = s.id |
| 29 | | WHERE ph.quantity > 0; |
| 30 | | |
| 31 | | end_time := clock_timestamp(); |
| 32 | | duration := round(1000 * (extract(epoch FROM end_time) |
| 33 | | - extract(epoch FROM start_time))); |
| 34 | | RAISE NOTICE 'Query executed in: % ms', duration; |
| 35 | | END; |
| 36 | | $$ LANGUAGE plpgsql; |
| 37 | | |
| 38 | | |
| 44 | | -- run 1: no index |
| 45 | | SELECT test_portfolio_diversification(); |
| 46 | | |
| 47 | | -- run 2: + idx_ph_stock_id |
| | 16 | EXPLAIN ANALYZE |
| | 17 | SELECT |
| | 18 | ph.portfolio_id, |
| | 19 | ph.stock_id, |
| | 20 | s.symbol, |
| | 21 | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| | 22 | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| | 23 | FROM portfolio_holdings ph |
| | 24 | JOIN stock s ON ph.stock_id = s.id |
| | 25 | WHERE quantity > 0 AND portfolio_id = 1; |
| | 26 | |
| | 27 | |
| | 28 | |
| | 29 | QUERY PLAN | |
| | 30 | --------------------------------------------------------------------------------------------------------------------------------+ |
| | 31 | Nested Loop (cost=0.15..1418.59 rows=50 width=85) (actual time=0.085..8.009 rows=50 loops=1) | |
| | 32 | -> Seq Scan on portfolio_holdings ph (cost=0.00..1400.00 rows=50 width=26) (actual time=0.037..7.462 rows=50 loops=1) | |
| | 33 | Filter: ((quantity > 0) AND (portfolio_id = 1)) | |
| | 34 | Rows Removed by Filter: 59950 | |
| | 35 | -> Memoize (cost=0.15..0.49 rows=1 width=21) (actual time=0.005..0.005 rows=1 loops=50) | |
| | 36 | Cache Key: ph.stock_id | |
| | 37 | Cache Mode: logical | |
| | 38 | Hits: 0 Misses: 50 Evictions: 0 Overflows: 0 Memory Usage: 7kB | |
| | 39 | -> Index Scan using stock_pkey on stock s (cost=0.14..0.48 rows=1 width=21) (actual time=0.003..0.003 rows=1 loops=50)| |
| | 40 | Index Cond: (id = ph.stock_id) | |
| | 41 | Planning Time: 0.799 ms | |
| | 42 | Execution Time: 8.064 ms | |
| | 43 | |
| | 44 | }}} |
| | 45 | |
| | 46 | |
| | 47 | '''Индекси:''' |
| | 48 | |
| | 49 | |
| | 50 | |
| | 51 | **1. portfolio_holdings - Index (stock_id)** |
| | 52 | {{{ |
| 50 | | SELECT test_portfolio_diversification(); |
| 51 | | |
| 52 | | -- run 3: + idx_ph_portfolio_id |
| | 55 | |
| | 56 | EXPLAIN ANALYZE |
| | 57 | SELECT |
| | 58 | ph.portfolio_id, |
| | 59 | ph.stock_id, |
| | 60 | s.symbol, |
| | 61 | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| | 62 | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| | 63 | FROM portfolio_holdings ph |
| | 64 | JOIN stock s ON ph.stock_id = s.id |
| | 65 | WHERE quantity > 0 AND portfolio_id = 1; |
| | 66 | |
| | 67 | QUERY PLAN | |
| | 68 | --------------------------------------------------------------------------------------------------------------------------------+ |
| | 69 | Nested Loop (cost=0.15..1418.59 rows=50 width=85) (actual time=0.052..5.758 rows=50 loops=1) | |
| | 70 | -> Seq Scan on portfolio_holdings ph (cost=0.00..1400.00 rows=50 width=26) (actual time=0.018..5.389 rows=50 loops=1) | |
| | 71 | Filter: ((quantity > 0) AND (portfolio_id = 1)) | |
| | 72 | Rows Removed by Filter: 59950 | |
| | 73 | -> Memoize (cost=0.15..0.49 rows=1 width=21) (actual time=0.003..0.003 rows=1 loops=50) | |
| | 74 | Cache Key: ph.stock_id | |
| | 75 | Cache Mode: logical | |
| | 76 | Hits: 0 Misses: 50 Evictions: 0 Overflows: 0 Memory Usage: 7kB | |
| | 77 | -> Index Scan using stock_pkey on stock s (cost=0.14..0.48 rows=1 width=21) (actual time=0.002..0.002 rows=1 loops=50)| |
| | 78 | Index Cond: (id = ph.stock_id) | |
| | 79 | Planning Time: 0.471 ms | |
| | 80 | Execution Time: 5.802 ms | |
| | 81 | |
| | 82 | }}} |
| | 83 | |
| | 84 | --- |
| | 85 | |
| | 86 | **2. portfolio_holdings - Index (portfolio_id)** |
| | 87 | {{{ |
| 55 | | SELECT test_portfolio_diversification(); |
| 56 | | |
| 57 | | -- run 4: + idx_ph_quantity_positive (partial index) |
| | 90 | |
| | 91 | EXPLAIN ANALYZE |
| | 92 | SELECT |
| | 93 | ph.portfolio_id, |
| | 94 | ph.stock_id, |
| | 95 | s.symbol, |
| | 96 | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| | 97 | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| | 98 | FROM portfolio_holdings ph |
| | 99 | JOIN stock s ON ph.stock_id = s.id |
| | 100 | WHERE quantity > 0 AND portfolio_id = 1; |
| | 101 | |
| | 102 | |
| | 103 | QUERY PLAN | |
| | 104 | --------------------------------------------------------------------------------------------------------------------------------+ |
| | 105 | Nested Loop (cost=0.15..1418.59 rows=50 width=85) (actual time=0.052..5.758 rows=50 loops=1) | |
| | 106 | -> Seq Scan on portfolio_holdings ph (cost=0.00..1400.00 rows=50 width=26) (actual time=0.018..5.389 rows=50 loops=1) | |
| | 107 | Filter: ((quantity > 0) AND (portfolio_id = 1)) | |
| | 108 | Rows Removed by Filter: 59950 | |
| | 109 | -> Memoize (cost=0.15..0.49 rows=1 width=21) (actual time=0.003..0.003 rows=1 loops=50) | |
| | 110 | Cache Key: ph.stock_id | |
| | 111 | Cache Mode: logical | |
| | 112 | Hits: 0 Misses: 50 Evictions: 0 Overflows: 0 Memory Usage: 7kB | |
| | 113 | -> Index Scan using stock_pkey on stock s (cost=0.14..0.48 rows=1 width=21) (actual time=0.002..0.002 rows=1 loops=50)| |
| | 114 | Index Cond: (id = ph.stock_id) | |
| | 115 | Planning Time: 0.471 ms | |
| | 116 | Execution Time: 5.802 ms | |
| | 117 | |
| | 118 | }}} |
| | 119 | Користење: GROUP BY и JOIN по `portfolio_id` во CTE-ата `stock_weights` и `portfolio_totals` |
| | 120 | |
| | 121 | Подобрување: Index Scan наместо Seq Scan при групирање по портфолио |
| | 122 | |
| | 123 | --- |
| | 124 | |
| | 125 | **3. Add index on portfolio_id** |
| | 126 | {{{ |
| | 127 | CREATE INDEX idx_ph_portfolio_id ON portfolio_holdings(portfolio_id); |
| | 128 | ANALYZE portfolio_holdings; |
| | 129 | |
| | 130 | EXPLAIN ANALYZE |
| | 131 | SELECT |
| | 132 | ph.portfolio_id, |
| | 133 | ph.stock_id, |
| | 134 | s.symbol, |
| | 135 | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| | 136 | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| | 137 | FROM portfolio_holdings ph |
| | 138 | JOIN stock s ON ph.stock_id = s.id |
| | 139 | WHERE quantity > 0 AND portfolio_id = 1; |
| | 140 | |
| | 141 | |
| | 142 | |
| | 143 | QUERY PLAN | |
| | 144 | ----------------------------------------------------------------------------------------------------------------------------------+ |
| | 145 | Hash Join (cost=9.00..181.17 rows=50 width=85) (actual time=0.245..0.515 rows=50 loops=1) | |
| | 146 | Hash Cond: (ph.stock_id = s.id) | |
| | 147 | -> Bitmap Heap Scan on portfolio_holdings ph (cost=4.75..175.92 rows=50 width=26) (actual time=0.156..0.265 rows=50 loops=1) | |
| | 148 | Recheck Cond: (portfolio_id = 1) | |
| | 149 | Filter: (quantity > 0) | |
| | 150 | Rows Removed by Filter: 10 | |
| | 151 | Heap Blocks: exact=60 | |
| | 152 | -> Bitmap Index Scan on idx_ph_portfolio_id (cost=0.00..4.74 rows=60 width=0) (actual time=0.136..0.137 rows=60 loops=1)| |
| | 153 | Index Cond: (portfolio_id = 1) | |
| | 154 | -> Hash (cost=3.00..3.00 rows=100 width=21) (actual time=0.059..0.060 rows=100 loops=1) | |
| | 155 | Buckets: 1024 Batches: 1 Memory Usage: 14kB | |
| | 156 | -> Seq Scan on stock s (cost=0.00..3.00 rows=100 width=21) (actual time=0.019..0.035 rows=100 loops=1) | |
| | 157 | Planning Time: 0.526 ms | |
| | 158 | Execution Time: 0.554 ms | |
| | 159 | |
| | 160 | }}} |
| | 161 | |
| | 162 | |
| | 163 | --- |
| | 164 | |
| | 165 | **4. partial index** |
| | 166 | {{{ |
| 62 | | SELECT test_portfolio_diversification(); |
| 63 | | |
| 64 | | -- run 5: + idx_users_role |
| | 172 | |
| | 173 | EXPLAIN ANALYZE |
| | 174 | SELECT |
| | 175 | ph.portfolio_id, |
| | 176 | ph.stock_id, |
| | 177 | s.symbol, |
| | 178 | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| | 179 | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| | 180 | FROM portfolio_holdings ph |
| | 181 | JOIN stock s ON ph.stock_id = s.id |
| | 182 | WHERE quantity > 0 AND portfolio_id = 1; |
| | 183 | |
| | 184 | |
| | 185 | QUERY PLAN | |
| | 186 | ---------------------------------------------------------------------------------------------------------------------------------------+ |
| | 187 | Hash Join (cost=8.93..158.07 rows=50 width=85) (actual time=0.315..0.573 rows=50 loops=1) | |
| | 188 | Hash Cond: (ph.stock_id = s.id) | |
| | 189 | -> Bitmap Heap Scan on portfolio_holdings ph (cost=4.68..152.81 rows=50 width=26) (actual time=0.225..0.320 rows=50 loops=1) | |
| | 190 | Recheck Cond: ((portfolio_id = 1) AND (quantity > 0)) | |
| | 191 | Heap Blocks: exact=50 | |
| | 192 | -> Bitmap Index Scan on idx_ph_quantity_positive (cost=0.00..4.67 rows=50 width=0) (actual time=0.209..0.209 rows=50 loops=1)| |
| | 193 | Index Cond: (portfolio_id = 1) | |
| | 194 | -> Hash (cost=3.00..3.00 rows=100 width=21) (actual time=0.059..0.060 rows=100 loops=1) | |
| | 195 | Buckets: 1024 Batches: 1 Memory Usage: 14kB | |
| | 196 | -> Seq Scan on stock s (cost=0.00..3.00 rows=100 width=21) (actual time=0.021..0.037 rows=100 loops=1) | |
| | 197 | Planning Time: 0.608 ms | |
| | 198 | Execution Time: 0.609 ms | |
| | 199 | |
| | 200 | }}} |
| | 201 | |
| | 202 | |
| | 203 | --- |
| | 204 | |
| | 205 | **5. Add index on users.role** |
| | 206 | {{{ |
| 67 | | SELECT test_portfolio_diversification(); |
| 68 | | |
| 69 | | DROP FUNCTION test_portfolio_diversification(); |
| 70 | | }}} |
| 71 | | |
| 72 | | |
| 73 | | |
| 74 | | |
| 75 | | |
| 76 | | '''Индекси:''' |
| 77 | | |
| 78 | | **1. portfolio_holdings - Index (portfolio_id)** |
| 79 | | {{{ |
| 80 | | CREATE INDEX idx_ph_portfolio_id |
| 81 | | ON portfolio_holdings(portfolio_id); |
| 82 | | }}} |
| 83 | | Користење: GROUP BY и JOIN по `portfolio_id` во CTE-ата `stock_weights` и `portfolio_totals` |
| 84 | | |
| 85 | | Подобрување: Index Scan наместо Seq Scan при групирање по портфолио |
| 86 | | |
| 87 | | --- |
| 88 | | |
| 89 | | **2. portfolio_holdings - Index (stock_id)** |
| 90 | | {{{ |
| 91 | | CREATE INDEX idx_ph_stock_id |
| 92 | | ON portfolio_holdings(stock_id); |
| 93 | | }}} |
| 94 | | Користење: JOIN `ph.stock_id = s.id` во `holding_values` CTE |
| 95 | | |
| 96 | | Подобрување: Директен lookup наместо Hash Join со Seq Scan |
| 97 | | |
| 98 | | --- |
| 99 | | |
| 100 | | **3. portfolio_holdings - Partial Index (quantity > 0)** |
| 101 | | {{{ |
| 102 | | CREATE INDEX idx_ph_quantity_positive |
| 103 | | ON portfolio_holdings(portfolio_id, stock_id) |
| 104 | | WHERE quantity > 0; |
| 105 | | }}} |
| 106 | | Користење: WHERE филтер `ph.quantity > 0` во `holding_values` CTE |
| 107 | | |
| 108 | | Подобрување: Индексот ги содржи само редовите со quantity > 0, елиминирајќи ги нулираните холдинзи уште при скенирањето |
| 109 | | |
| 110 | | --- |
| 111 | | |
| 112 | | **4. users - Index (role)** |
| 113 | | {{{ |
| 114 | | CREATE INDEX idx_users_role |
| 115 | | ON users(role); |
| 116 | | }}} |
| 117 | | Користење: WHERE филтер `u.role = 'USER'` во финалниот SELECT |
| 118 | | |
| 119 | | Подобрување: Директен Index Scan наместо Seq Scan на целата users табела |
| 120 | | |
| 121 | | --- |
| 122 | | |
| 123 | | |
| | 209 | |
| | 210 | EXPLAIN ANALYZE |
| | 211 | SELECT |
| | 212 | ph.portfolio_id, |
| | 213 | ph.stock_id, |
| | 214 | s.symbol, |
| | 215 | ph.quantity::NUMERIC * s.current_price::NUMERIC AS market_value, |
| | 216 | ph.quantity::NUMERIC * (s.current_price::NUMERIC - ph.avg_price::NUMERIC) AS unrealized_pnl |
| | 217 | FROM portfolio_holdings ph |
| | 218 | JOIN stock s ON ph.stock_id = s.id |
| | 219 | WHERE quantity > 0 AND portfolio_id = 1; |
| | 220 | |
| | 221 | |
| | 222 | QUERY PLAN | |
| | 223 | ---------------------------------------------------------------------------------------------------------------------------------------+ |
| | 224 | Hash Join (cost=8.93..158.07 rows=50 width=85) (actual time=0.123..0.377 rows=50 loops=1) | |
| | 225 | Hash Cond: (ph.stock_id = s.id) | |
| | 226 | -> Bitmap Heap Scan on portfolio_holdings ph (cost=4.68..152.81 rows=50 width=26) (actual time=0.032..0.125 rows=50 loops=1) | |
| | 227 | Recheck Cond: ((portfolio_id = 1) AND (quantity > 0)) | |
| | 228 | Heap Blocks: exact=50 | |
| | 229 | -> Bitmap Index Scan on idx_ph_quantity_positive (cost=0.00..4.67 rows=50 width=0) (actual time=0.016..0.017 rows=50 loops=1)| |
| | 230 | Index Cond: (portfolio_id = 1) | |
| | 231 | -> Hash (cost=3.00..3.00 rows=100 width=21) (actual time=0.060..0.060 rows=100 loops=1) | |
| | 232 | Buckets: 1024 Batches: 1 Memory Usage: 14kB | |
| | 233 | -> Seq Scan on stock s (cost=0.00..3.00 rows=100 width=21) (actual time=0.018..0.037 rows=100 loops=1) | |
| | 234 | Planning Time: 0.299 ms | |
| | 235 | Execution Time: 0.416 ms | |
| | 236 | |
| | 237 | }}} |
| | 238 | |
| | 239 | |
| | 240 | --- |