Как стать автором
Обновить

Аналитические запросы теста TPC-H в PostgreSQL

Уровень сложностиСредний
Время на прочтение14 мин
Количество просмотров1.2K

В статье рассматривается использование теста TPC-H с PostgreSQL и проблемы, связанные с запросами Q17-Q20 теста.

Введение

Вместе с PostgreSQL поставляется утилит pg_bench с "TPC-B like" тестом. Кроме этого теста были созданы тесты TPC-R для отчётов, TPC-D для OLAP, TPC-W для заказов в веб-магазине, которые не получили распространения. На основе TPC-D был создан более удачный тест TPC-H для хранилищ данных и аналитических запросов ("OLAP нагрузка"). В тесте используется 8 таблиц и 17 ограничений целостности. В TPC-H выделены номинации по размерам обрабатываемых данных от "до 100Гб" до  30-100Тб. Тест TPC-H предназначен для хранилищ данных, включает в себя 22 запроса, которые называют Q1 ... Q22.

Запросы теста TPC-H не меняют данные в таблицах, а значит, для повторных тестирований не нужно пересоздавать или вакуумировать таблицы. В тестах TPC-B, TPC-C, TPC-E запросы довольно простые. В реальных приложениях запросы более сложные, чем в этих тестах. Поэтому для тестирования того, как СУБД выполняет запросы, которые могут встретиться в реальных приложениях, можно использовать все или отдельные запросы из теста TPC-H. Для быстрого аудита производительности различных СУБД используют вариант с 1Гб данных. В этом варианте запросы выполняются быстро, не нужно много памяти под экземпляр СУБД и много места на диске. Можно найти программы или скрипты для большинства СУБД, например, для PostgreSQL, Oracle Database, MySQL. После теста TPC-H появился тест TPC-DS с 99 запросами, но он менее популярен.

Создание таблиц теста TPC-H

mkdir gotpc
cd gotpc
wget https://raw.githubusercontent.com/pingcap/go-tpc/master/install.sh
chmod +x install.sh
./install.sh
cd $HOME/.go-tpc/bin
time ./go-tpc tpch prepare -d postgres -U postgres -p 'postgres' -D gotpch -H 127.0.0.1 -P 5432 --conn-params sslmode=disable
psql -d gotpch
\dt+
                                     List of relations
 Schema |   Name   | Type  |  Owner   | Persistence | Access method |  Size   | Description 
--------+----------+-------+----------+-------------+---------------+---------+
 public | customer | table | postgres | permanent   | heap          | 30 MB   | 
 public | lineitem | table | postgres | permanent   | heap          | 973 MB  | 
 public | nation   | table | postgres | permanent   | heap          | 40 kB   | 
 public | orders   | table | postgres | permanent   | heap          | 221 MB  | 
 public | part     | table | postgres | permanent   | heap          | 34 MB   | 
 public | partsupp | table | postgres | permanent   | heap          | 146 MB  | 
 public | region   | table | postgres | permanent   | heap          | 40 kB   | 
 public | supplier | table | postgres | permanent   | heap          | 1912 kB | 
(8 rows)
select count(*) from lineitem;
  count
---------
 6001215
(1 row)

После создания таблиц можно запустить тест, но для оценки скорости выполнения запросов можно выполнить каждый запрос отдельно и измерить время выполнения. На всех версиях ванильного PostgreSQL хотя бы один запрос выполняется чрезвычайно долго - час или больше. Чем это плохо?

Например, выбирается СУБД для внедрения. Запускается тест и на одной СУБД все запросы выполняются за минуту, а на других проходит час-два или больше. Кто-то из тестировщиков считает, что СУБД  "подвисла", прерывает запрос и считает, что СУБД не способна пройти тест. Такая СУБД вряд ли будет выбрана для промышленного использования. Если СУБД не может выполнить запрос за разумное время, то велика вероятность того, что на реальных запросах она проделает то же самое. Проблемы никому не нужны. То, что запросы выполняются медленнее (до полутора раз), обычно, несущественно. Существенно, чтобы не было падений экземпляра, повреждений данных, выполнения запросов на порядок дольше, ошибок при выполнении. Если, к примеру, форки Greenplum могут выполнять все запросы TPC-H, то они и будут внедряться, вместо форков PostgreSQL. В тесте TPC-H есть запрос, который все верcии PostgreSQL выполняют очень долго - час или больше. Это запрос Q17.

Запрос Q17

Запрос Q17 выполняется больше часа. Остальные запросы выполняются быстро - за секунды. Запросы Q18-Q20 могли выполняться долго на старых версиях или форках PostgreSQL.

В  2017 году (в то время использовалась версия PostgreSQL 9.5) обсуждалась проблема выполнения запроса Q20 от часа до 19 часов: https://www.postgresql.org/message-id/flat/CAOGQiiMsnM-cGjCfnpWQNyrzjf%3DFM0s_3hn1guqficSmwWWVfw%40mail.gmail.com

Если запрос выполняется долго, то время выполнения не зависит от железа. Через 3 месяца обсуждений (это очень быстро для сообщества) Том Лейн пришел к выводу: Maybe not with the rest of them, but we're certainly having an issue there with Q20.

По запросу Q17 таких обсуждений не инициировалось и запрос имеет проблемы на всех версиях PostgreSQL. Проблема отдельным исследователям была известна: https://www.enterprisedb.com/blog/tpc-h-performance-postgresql-83 Было написано: "We can clearly see that most of the queries (up to Q17) got faster, but then two of the queries (Q18 and Q20) got somewhat slower". https://www.postgresql.org/message-id/flat/17153-bd309320429d24b7%40postgresql.org

Другие СУБД также испытывали проблемы с этими запросами. Запросы Q17..Q20 содержат коррелированные подзапросы, которые сложны для выполнения. Было найдено решение в виде создания индекса на столбцы внешнего ключа: https://www.percona.com/blog/improving_tpc_h_like_queries_q17/

Однако, это меняет условия теста TPC-H и является читом. Был заполнен баг репорт https://www.postgresql.org/message-id/flat/17153-bd309320429d24b7%40postgresql.org на который ответили, что после того как производитель одного из тестов HammerDB увидев, что часть СУБД не способны выполнить часть запросов за разумное время включило чит создания индексов по внешним ключам, чтобы пользователи утилиты и СУБД не расстраивались и получали красивые графики или могли сравнивать работу СУБД. Если тест не может выполняться для популярных СУБД, то тест потеряет популярность. Тест HammerDB не позиционируется себя как тест TPC.

Запрос Q17 на PostgreSQL версии 17

explain (analyze, buffers on, timing off) select sum(l_extendedprice) / 7.0 as avg_yearly from lineitem, part where p_partkey = l_partkey and p_brand = 'Brand#43' and p_container = 'LG PACK' and l_quantity < ( select 0.2 * avg(l_quantity) from lineitem where l_partkey = p_partkey);
                                 QUERY PLAN
---------------------------------------------------------------------
 Aggregate  (cost=2202829.63..2202829.64 rows=1 width=32) (actual rows=1 loops=1)
   Buffers: shared hit=59294965 read=696952753
   ->  Hash Join  (cost=7350.36..2202824.90 rows=1890 width=8) (actual rows=538 loops=1)
         Hash Cond: (lineitem.l_partkey = part.p_partkey)
         Join Filter: (lineitem.l_quantity < (SubPlan 1))
         Rows Removed by Join Filter: 5535
         Buffers: shared hit=59294965 read=696952753
         ->  Seq Scan on lineitem  (cost=0.00..184517.24 rows=6001224 width=21) (actual rows=6001215 loops=1)
               Buffers: shared hit=9731 read=114774
         ->  Hash  (cost=7348.00..7348.00 rows=189 width=8) (actual rows=203 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 16kB
               Buffers: shared hit=161 read=4187
               ->  Seq Scan on part  (cost=0.00..7348.00 rows=189 width=8) (actual rows=203 loops=1)
                     Filter: ((p_brand = 'Brand#43'::bpchar) AND (p_container = 'LG PACK'::bpchar))
                     Rows Removed by Filter: 199797
                     Buffers: shared hit=161 read=4187
         SubPlan 1
           ->  Aggregate  (cost=199520.38..199520.39 rows=1 width=32) (actual rows=1 loops=6073)
                 Buffers: shared hit=59285073 read=696833792
                 ->  Seq Scan on lineitem lineitem_1  (cost=0.00..199520.30 rows=32 width=5) (actual rows=31 loops=6073)
                       Filter: (l_partkey = part.p_partkey)
                       Rows Removed by Filter: 6001184
                       Buffers: shared hit=59285073 read=696833792
 Planning:
   Buffers: shared hit=8
 Planning Time: 0.247 ms
  Execution Time: 4089201.631 ms
(30 rows)
Time: 4089203,084 ms (01:08:09,203)

Запрос Q17 выполняется больше часа.

После создания индекса

CREATE INDEX LINEITEM_PART_SUPP_FKIDX ON LINEITEM (L_PARTKEY,L_SUPPKEY);
explain (analyze, buffers, timing off) select sum(l_extendedprice) / 7.0 as avg_yearly from lineitem, part where p_partkey = l_partkey and p_brand = 'Brand#43' and p_container = 'LG PACK' and l_quantity < ( select 0.2 * avg(l_quantity) from lineitem where l_partkey = p_partkey);
                                                               QUERY PLAN
-------------------------------------------------------
 Aggregate  (cost=209175.16..209175.17 rows=1 width=32) (actual rows=1 loops=1)
   Buffers: shared hit=205522 read=129512
   ->  Hash Join  (cost=7355.50..209170.16 rows=2000 width=8) (actual rows=538 loops=1)
         Hash Cond: (lineitem.l_partkey = part.p_partkey)
         Join Filter: (lineitem.l_quantity < (SubPlan 1))
         Rows Removed by Join Filter: 5535
         Buffers: shared hit=205522 read=129512
         ->  Seq Scan on lineitem  (cost=0.00..184744.57 rows=6001057 width=21) (actual rows=6001215 loops=1)
               Buffers: shared hit=5738 read=118996
         ->  Hash  (cost=7353.00..7353.00 rows=200 width=8) (actual rows=203 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 16kB
               Buffers: shared read=4353
               ->  Seq Scan on part  (cost=0.00..7353.00 rows=200 width=8) (actual rows=203 loops=1)
                     Filter: ((p_brand = 'Brand#43'::bpchar) AND (p_container = 'LG PACK'::bpchar))
                     Rows Removed by Filter: 199797
                     Buffers: shared read=4353
         SubPlan 1
           ->  Aggregate  (cost=131.71..131.72 rows=1 width=32) (actual rows=1 loops=6073)
                 Buffers: shared hit=199784 read=6163
                 ->  Bitmap Heap Scan on lineitem lineitem_1  (cost=4.68..131.62 rows=32 width=5) (actual rows=31 loops=6073)
                       Recheck Cond: (l_partkey = part.p_partkey)
                       Heap Blocks: exact=187549
                       Buffers: shared hit=199784 read=6163
                       ->  Bitmap Index Scan on lineitem_part_supp_fkidx  (cost=0.00..4.67 rows=32 width=0) (actual rows=31 loops=6073)
                             Index Cond: (l_partkey = part.p_partkey)
                             Buffers: shared hit=18159 read=239
 Planning:
   Buffers: shared hit=4 read=12
 Planning Time: 0.325 ms
 Execution Time: 2118.095 ms
(30 rows)
Time: 2118.826 ms (00:02.119)

появился Bitmap Index Scan on lineitem_part_supp_fkidx и запрос стал выполняться за 2 секунды.

Можно говорить "создайте индекс и нет проблемы", но цель теста не выполнить запрос, а провести тестирование - как будет работать СУБД при эксплуатации. Если есть СУБД, которая работает в любых условиях - без индексов, с индексами, то она предпочтительнее.

Как запрос Q17 выполняется в Oracle Database?

Это коммерческая СУБД, ориентированная на результат и компания имеет ресурсы для доработки продукта. До версии 9.0 запрос Q17 выполнялся относительно долго - 5 минут, не часы. Начиная с версии 9i в планировщик были внесены улучшения, которые позволили выполнять запрос Q17 за секунды. Остальные запросы выполняются за доли секунды и существенно быстрее, чем у остальных СУБД. Возможно, из-за теста TPC-H, а точнее запроса Q17, пошло расхожее выражение "PostgreSQL это уровень Oracle версии 8i". С точки зрения планировщика, вероятно, так и есть - даже в последней версии PostgreSQL запрос Q17 выполняется так же как в предыдущих версиях PostgreSQL. С другой стороны, когда-то говорили, что компания Oracle специально оптимизировала планировщик под тесты TPC. Но это не так: ускорить запросы настолько, чтобы они выполнялись за доли секунд маловероятно и другие производители СУБД не смогли добиться такого результата.

Время выполнения запросов гигабайтного теста TPC-Hв Oracle Database от долей секунды до 3 секунд, даже без сбора статистики по объектам (в этом случае используется dynamic sampling) ни на одном запросе, начиная с 9 версии нет "затыков". Чтобы не было стыдно за PostgreSQL, длительность выполнения запросов TPC-H в Oracle Database скрыты:

Скрытый текст

Elapsed: 00:00:03.132 Elapsed: 00:00:00.331 Elapsed: 00:00:01.116 Elapsed: 00:00:01.582 Elapsed: 00:00:01.112 Elapsed: 00:00:00.917 Elapsed: 00:00:01.935 Elapsed: 00:00:01.109 Elapsed: 00:00:00.634 Elapsed: 00:00:02.800 Elapsed: 00:00:00.946 Elapsed: 00:00:00.651 Elapsed: 00:00:00.674 Elapsed: 00:00:01.348 Elapsed: 00:00:01.334 Elapsed: 00:00:03.534 Elapsed: 00:00:02.395 Elapsed: 00:00:03.035 Elapsed: 00:00:00.260

Выполнение запроса Q17 в Oracle Database

select cast(sum(l_extendedprice) / 7.0 as dec(18,2)) as avg_yearly from lineitem,part where p_partkey = l_partkey and p_brand = 'Brand#23' and p_container = 'MED BOX' and l_quantity < (select 0.2 * avg(l_quantity) from lineitem where l_partkey = p_partkey)
-------------------------------------------------------------------------------------
| Id  | Operation               | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |           |       |       | 60383 (100)|          |
|   1 |  SORT AGGREGATE         |           |     1 |    13 |            |          |
|   2 |   VIEW                  | VM_NWVW_2 | 81599 |  1035K| 60383   (1)| 00:00:03 |
|*  3 |    FILTER               |           |       |       |            |          |
|   4 |     HASH GROUP BY       |           | 81599 |     9M| 60383   (1)| 00:00:03 |
|*  5 |      HASH JOIN          |           | 81599 |     9M| 60380   (1)| 00:00:03 |
|*  6 |       HASH JOIN         |           |  3160 |   231K| 30713   (1)| 00:00:02 |
|*  7 |        TABLE ACCESS FULL| PART      |   122 |  5978 |  1051   (1)| 00:00:01 |
|   8 |        TABLE ACCESS FULL| LINEITEM  |  6319K|   156M| 29641   (1)| 00:00:02 |
|   9 |       TABLE ACCESS FULL | LINEITEM  |  6319K|   307M| 29646   (1)| 00:00:02 |
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - filter("L_QUANTITY"<.2*(SUM("L_QUANTITY")/COUNT("L_QUANTITY")))
   5 - access("P_PARTKEY"="L_PARTKEY")
   6 - access("L_PARTKEY"="P_PARTKEY")
   7 - filter(("P_BRAND"='Brand#23' AND "P_CONTAINER"='MED BOX')) 
Note
- dynamic statistics used: dynamic sampling (level=2)
Elapsed: 00:00:00.658

Запрос Q17 выполняется за 0.7 секунд с фуллсканом таблиц, без индексов, в то время как в PostgreSQL не меньше часа, а с индексами ~9 секунд.

Oracle использует трансформацию complex view merging (строка VIEW VM_NWVW_2)  https://blogs.oracle.com/optimizer/post/optimizer-transformations-view-merging-part-2

Запрос Q17, трансформированный планировщиком Oracle:

Final query after transformations:******* UNPARSED QUERY IS *******
SELECT SUM("VW_WIF_1"."ITEM_1")/7.0 "AVG_YEARLY" FROM  (SELECT CASE  WHEN "LINEITEM"."L_QUANTITY"<0.2*AVG("LINEITEM"."L_QUANTITY") OVER ( PARTITION BY "LINEITEM"."L_PARTKEY") THEN "LINEITEM"."L_EXTENDEDPRICE" END  "ITEM_1" FROM "TPCH_1GB"."PART" "PART","TPCH_1GB"."LINEITEM" "LINEITEM" WHERE "PART"."P_CONTAINER"='LG PACK' AND "PART"."P_BRAND"='Brand#43' AND "PART"."P_PARTKEY"="LINEITEM"."L_PARTKEY") "VW_WIF_1"

Если убрать двойные кавычки, то запрос можно выполнить в PostgreSQL:

SELECT SUM(VW.ITEM_1)/7.0 AVG_YEARLY
FROM (SELECT CASE  WHEN l.L_QUANTITY<0.2*AVG(l.L_QUANTITY)
      OVER ( PARTITION BY l.L_PARTKEY)
      THEN l.L_EXTENDEDPRICE END  ITEM_1 FROM PART p,
      LINEITEM l WHERE p.P_CONTAINER='LG PACK'
      AND p.P_BRAND='Brand#43' AND p.P_PARTKEY=l.L_PARTKEY) VW;

На версии оптимизатора Oracle 8i запрос выполняется 5 минут. Медленно, но всё равно гораздо быстрее, чем на PostgreSQL:

alter session set optimizer_features_enable='8.1.7';
set timing on;
select sum(l_extendedprice) / 7.0 as avg_yearly from lineitem, part where p_partkey = l_partkey and p_brand = 'Brand#43' and p_container = 'LG PACK' and l_quantity < ( select 0.2 * avg(l_quantity) from lineitem where l_partkey = p_partkey);
AVG_YEARLY
----------
309865.026
Elapsed: 00:05:19.398
SELECT * FROM table (dbms_xplan.display_cursor);
-----------------------------------------------------------------
| Id  | Operation            | Name     | Rows  | Bytes | Cost  |
|   0 | SELECT STATEMENT     |          |       |       | 17169 |
|   1 |  SORT AGGREGATE      |          |     1 |    41 |       |
|*  2 |   FILTER             |          |       |       |       |
|*  3 |    HASH JOIN         |          |  6002 |   240K| 17169 |
|*  4 |     TABLE ACCESS FULL| PART     |   202 |  5454 |   586 |
|   5 |     TABLE ACCESS FULL| LINEITEM |  6001K|    80M| 16568 |
|   6 |    SORT AGGREGATE    |          |     1 |     8 |       |
|*  7 |     TABLE ACCESS FULL| LINEITEM |    30 |   240 | 16568 |
-----------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("L_QUANTITY"<)
   3 - access("P_PARTKEY"="L_PARTKEY")
   4 - filter(("P_BRAND"='Brand#43' AND "P_CONTAINER"='LG PACK'))
   7 - filter("L_PARTKEY"=:B1)

Чтобы не расстраиваться за PostgreSQL: в 2010 году Greenplum был не способен выполнять коррелированные подзапросы и не проходил тест TPC-H (https://tholis.webnode.page/news/tpc-h-fun-with-greenplum-single-node-edition-/) А сейчас база данных Clickhouse не способна выполнять коррелированные подзапросы, как Greenplum 15 лет назад https://github.com/ClickHouse/ClickHouse/issues/6697

Выполнение трансформированного запроса в PostgreSQL

Планировщик Oracle трансформировал запрос

select sum(l_extendedprice) / 7.0 as avg_yearly from lineitem, part
where p_partkey = l_partkey and p_brand = 'Brand#43'
and p_container = 'LG PACK' and l_quantity < ( select 0.2 * avg(l_quantity)
                          from lineitem where l_partkey = p_partkey);

в эквивалент:

SELECT SUM(VW.ITEM_1)/7.0 AVG_YEARLY
FROM (SELECT CASE  WHEN l.L_QUANTITY<0.2*AVG(l.L_QUANTITY)
      OVER ( PARTITION BY l.L_PARTKEY)
      THEN l.L_EXTENDEDPRICE END  ITEM_1 FROM PART p,
      LINEITEM l WHERE p.P_CONTAINER='LG PACK'
      AND p.P_BRAND='Brand#43' AND p.P_PARTKEY=l.L_PARTKEY) VW;

Попробуем выполнить переписанный планировщиком Oracle запрос в PostgreSQL 17.
Сначала уберём чит (индекс):

drop index lineitem_part_supp_fkidx;
set parallel_setup_cost = 1000000000;
\timing on \\
explain (analyze, buffers, timing off) SELECT SUM(VW.ITEM_1)/7.0 "AVG_YEARLY" FROM  (SELECT CASE  WHEN l.L_QUANTITY<0.2*AVG(l.L_QUANTITY) OVER ( PARTITION BY l.L_PARTKEY) THEN l.L_EXTENDEDPRICE END  ITEM_1 FROM PART p, LINEITEM l WHERE p.P_CONTAINER='LG PACK' AND p.P_BRAND='Brand#43' AND p.P_PARTKEY=l.L_PARTKEY) VW;
                              QUERY PLAN 
----------------------------------------------------
 Aggregate  (cost=208229.18..208229.19 rows=1 width=32) (actual rows=1 loops=1)
   Buffers: shared hit=512 read=128340
   ->  WindowAgg  (cost=208009.69..208150.79 rows=6271 width=40) (actual rows=6073 loops=1)
         Buffers: shared hit=512 read=128340
         ->  Sort  (cost=208009.69..208025.37 rows=6271 width=21) (actual rows=6073 loops=1)
               Sort Key: l.l_partkey
               Sort Method: quicksort  Memory: 477kB
               Buffers: shared hit=512 read=128340
               ->  Hash Join  (cost=7349.61..207614.16 rows=6271 width=21) (actual rows=6073 loops=1)
                     Hash Cond: (l.l_partkey = p.p_partkey)
                     Buffers: shared hit=512 read=128340
                     ->  Seq Scan on lineitem l  (cost=0.00..184512.30 rows=6000730 width=21) (actual rows=6001215 loops=1)
                           Buffers: shared hit=256 read=124249
                     ->  Hash  (cost=7347.00..7347.00 rows=209 width=8) (actual rows=203 loops=1)
                           Buckets: 1024  Batches: 1  Memory Usage: 16kB
                           Buffers: shared hit=256 read=4091
                           ->  Seq Scan on part p  (cost=0.00..7347.00 rows=209 width=8) (actual rows=203 loops=1)
                                 Filter: ((p_container = 'LG PACK'::bpchar) AND (p_brand = 'Brand#43'::bpchar))
                                 Rows Removed by Filter: 199797
                                 Buffers: shared hit=256 read=4091
 Planning:
   Buffers: shared hit=8
 Planning Time: 0.201 ms
 Execution Time: 2079.081 ms
(27 rows)
Time: 2079,664 ms (00:02,080)

Запрос без индексов с полным сканированием таблиц на PostgreSQL выполнился за 2 секунды, даже немного быстрее, чем с дополнительно созданным индексом. Результат запроса одинаков, то есть ошибок при трансформации не было.

Параллельные планы отключались, чтобы не переусложнить план. С распараллеливанием (2 рабочих процесса) время выполнения запроса будет 798,561 ms. Распараллеливание в PostgreSQL эффективно.

Если сложно придумать, как переписать запрос, чтобы он работал быстрее, можно использовать планировщик Oracle Database и посмотреть как он перепишет запрос. Сложно догадаться, что можно переписать запрос Q17 на использование оконной функции. Для просмотра запросов после трансформации в Oracle Database можно использовать команды:

alter session set events '10053 trace name context forever,level 1';
alter session set max_dump_file_size = unlimited;
запросы;
alter session set events '10053 trace name context off';

в полученном файле трассировки переписанный запрос будет идти после фразы "Final query after transformations:".

В PostgreSQL также можно увидеть переписанные запросы, используя параметр конфигурации debug_print_rewritten, но оптимизаций, переписывающих запросы, в PostgreSQL не слишком много.

Список трансформаций в OracleDatabase:

***************************************
join predicate push-down
filter push-down
predicate move-around
complex view merging
select-project-join
set join conversion
subquery unnesting
order by elimination
star transformation
count(col) to count(*) transformation
Join Elimination
join factorization
connect by
select list pruning
distinct placement
vector transformation
Approximate Aggregate Transformation
OR-Expansion
Legacy OR-Expansion
***************************************

В списке даже есть трансформация, заменяющая count(столбец) на count(*), так как count(*) выполняется быстрее не только в Oracle Database, но и PostgreSQL, в котором такой простой трансформации пока нет. Важны не названия трансформаций, а их функционал. Трансформация с названием complex view merging присутствовала в Oracle Database и до 9i версии, но без оконных функций.

Доработка планировщиков в виде поправок селективностей, адаптаций полезны. Трансформации есть в PostgreSQL и дают, как показано, существенный эффект. Возможно, трансформации будут улучшаться в PostgreSQL. Когда я готовил статью, мне подсказали, что на конференции сообщества PostgreSQL, которая прошла на прошлой неделе, был доклад Сергея Соловьёва, который на примере запроса Q17 показывал, как можно выполнять декорреляцию запросов. Конференция проходит каждый код и называется PgBootcamp https://pgbootcamp.ru/. Регистрация бесплатна, я регистрировался онлайн, но этот доклад я смотрел поверхностно, так как в нем много технических подробностей.

доклад Сергея Соловьёва на PgBootcamp 2025
доклад Сергея Соловьёва на PgBootcamp 2025

Оказалось, в докладе рассматривалась актуальная "проблема запроса Q17" и различные оптимизации на примере именно этого запроса. Докладчик рассказал какую бизнес-задачу решает запрос Q17, что-то похожее на "what-if": сколько бы годовой выручки было потеряно, если бы заказы на небольшие партии отдельного товара не выполнялись, чтобы компания могла сосредоточиться на выполнении больших заказов и увеличить прибыль.

На PgConf мне понравился открывающий доклад Павла Лузанова, а на PgBootcamp вступительная речь Брюса Момжана. Конференции прошли недавно и видеозаписи докладов обеих конференций должны скоро выложить в свободный доступ.

Код патча выложен в открытый доступ https://github.com/TantorLabs/meetups/tree/main/2025-04-10_Ekb/Maxim%20Milutin%2C%20Sergey%20Solovev%20-%20Hacking%20query%20planner%2C%20again...%20and%20again То есть разработчики форков PostgreSQL работают над улучшением кода планировщика. Патч интересен тем, что в нем показывается как можно вносить исправления в код планировщика и определять свои конфигурационные параметры.

В PostgreSQL есть команды CREATE RULE и даже CREATE TRANSFORM, только это к планировщику не имеет отношения. Несмотря на декаларирование "расширяемости PostgreSQL", код планировщика не позволяет создавать расширения даже для трансформаций запросов, нужно вносить изменения в ядро PostgreSQL. Возможно, в будущем добавят возможность создавать свои расширения для кода планировщика. На то, что это возможно, указывает то, что возможность создавать свои табличные методы доступа добавили относительно недавно - в 12 версии PostgreSQL.

Заключение

Запрос Q17 теста TPC-H выполняется чрезвычайно долго в PostgreSQL. Запрос относится к коррелированным подзапросам, которые встречаются в приложениях. Запрос может выполняться за секунды. В статье приведен эквивалент запроса ("query rewrite" "query transform"), который выполняется чрезвычайно быстро. Эквивалент был сформирован планировщиком Oracle Database. У планировщика Oracle много правил переписывания запросов. Часто сложно предположить, как можно переписать запрос так, чтобы получать тот же результат без ошибки. Было показано, как можно получать переписанные планировщиком Oracle запросы. Также был приведен пример ускорения выполнения запроса Q17 путем создания индекса по столбцам. Дана ссылка на пример патча, который добавляет трансформации в планировщик PostgreSQL.

Теги:
Хабы:
+10
Комментарии4

Публикации

Работа

Ближайшие события