PostgreSQL: один индекс быстрый, а другой медленный - PullRequest
3 голосов
/ 05 марта 2019

У нас есть база данных с 450 миллионами строк, структурированная так:

   uid      id_1     id_2   d1  d2  d3  d4  d5  d6  d7  d8  d9  d10 d11 d12 d13 d14 d15 d16 d17
81038392    5655067 5468882 373 117 185 152 199 173 168 138 185 159 154 34  38  50  34  41  57
81038393    5655067 5468883 374 116 184 118 170 143 144 113 164 137 138 37  39  53  37  42  60
81038394    5655067 5468884 371 118 187 118 170 143 144 105 157 131 136 32  35  47  32  39  53
81038395    5655067 5468885 370 116 184 118 170 143 144 105 157 131 136 31  35  46  31  38  53
81038396    5655067 5468886 370 117 185 118 170 143 144 105 157 131 136 29  34  44  29  37  50
81038397    5655067 5470853 368 117 185 110 163 137 140 105 157 131 136 34  36  48  34  39  55
81038398    5655067 5470854 372 119 188 118 170 143 144 113 164 137 138 34  36  49  34  40  55
81038399    5655067 5470855 360 115 182 103 151 131 136 98  145 125 131 30  34  45  30  38  51
81038400    5655067 5470856 357 112 177 103 151 131 136 98  145 125 131 30  34  45  30  37  51
81038401    5655067 5470857 356 111 176 103 151 131 136 98  145 125 131 28  33  43  28  36  50
81038402    5655067 5470858 358 113 179 103 151 131 136 98  145 125 131 31  35  46  31  38  52
81038403    5655067 5472811 344 109 173 152 199 173 168 138 185 159 154 31  36  46  31  39  52
81038404    5655068 5468882 373 117 185 152 199 173 168 138 185 159 154 34  38  50  34  41  57
81038405    5655068 5468883 374 116 184 118 170 143 144 113 164 137 138 37  39  53  37  42  60
81038406    5655068 5468884 371 118 187 118 170 143 144 105 157 131 136 32  35  47  32  39  53
81038407    5655068 5468885 370 116 184 118 170 143 144 105 157 131 136 31  35  46  31  38  53
81038408    5655068 5468886 370 117 185 118 170 143 144 105 157 131 136 29  34  44  29  37  50
81038409    5655068 5470853 368 117 185 110 163 137 140 105 157 131 136 34  36  48  34  39  55
81038410    5655068 5470854 372 119 188 118 170 143 144 113 164 137 138 34  36  49  34  40  55
81038411    5655068 5470855 360 115 182 103 151 131 136 98  145 125 131 30  34  45  30  38  51
81038412    5655068 5470856 357 112 177 103 151 131 136 98  145 125 131 30  34  45  30  37  51
81038413    5655068 5470857 356 111 176 103 151 131 136 98  145 125 131 28  33  43  28  36  50
81038414    5655068 5470858 358 113 179 103 151 131 136 98  145 125 131 31  35  46  31  38  52

Нам нужно постоянно делать такие запросы:

Запрос 1:

EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM mytable WHERE id_1 = 5655067;

                                                               QUERY PLAN                                                                
-----------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using id_1_idx on mytable (cost=0.57..99187.68 rows=25742 width=80) (actual time=47.081..2600.899 rows=21487 loops=1)
   Index Cond: (id_1 = 5655067)
   Buffers: shared hit=9 read=4816
   I/O Timings: read=2563.181
 Planning time: 0.151 ms
 Execution time: 2602.320 ms
(6 rows)

Запрос 2:

EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM mytable WHERE id_2 = 5670433;

                                                            QUERY PLAN                                                             
-----------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on mytable (cost=442.02..89887.42 rows=23412 width=80) (actual time=113.200..42127.512 rows=21487 loops=1)
   Recheck Cond: (id_2 = 5670433)
   Heap Blocks: exact=16988
   Buffers: shared hit=30 read=17020
   I/O Timings: read=41971.798
   ->  Bitmap Index Scan on id_2_idx  (cost=0.00..436.16 rows=23412 width=0) (actual time=104.928..104.929 rows=21487 loops=1)
         Index Cond: (id_2 = 5670433)
         Buffers: shared hit=2 read=60
         I/O Timings: read=99.235
 Planning time: 0.163 ms
 Execution time: 42132.556 ms
(11 rows)

Есть около 23 000 до 25 000 уникальных Значения id_1 и id_2 и оба запроса всегда будут возвращать около 24 000 строк данных. Мы только читаем данные, и данные не меняются со временем.

Проблема:

  • Запрос 1 занимает около 3 секунд, что немного, но все же терпимо.

  • Запрос 2 занимает до 30-40 секунд, что для нас слишком много, поскольку сервис представляет собой интерактивный веб-сервис.

Мы проиндексировали id_1 и id_2. Мы также добавили объединенный индекс для id_1 и id_2, как это было предложено платформой Azure PostgreSQL в качестве службы, где расположены данные. Это не помогло.

Я предполагаю, что Запрос 1 быстрый, поскольку все строки расположены последовательно в базе данных, тогда как при использовании запроса 2 строки всегда распределяются по всей базе данных не последовательно.

Реструктуризация данных для ускорения Query 2 не очень хорошая идея, поскольку это снизит производительность Query 1. Я понимаю, что способ структурирования этих данных не идеален, но я не контролирую его. Любые предложения, как я мог бы ускорить Query 2 до разумного уровня?

Редактировать 2:

Создать индексные операторы:

CREATE INDEX id_1_idx ON mytable (id_1);
CREATE INDEX id_2_idx ON mytable (id_2);

Очистка стола не изменила план. Выходы EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM mytable WHERE id_1 = 5655067 очень похожи после очистки. Вот вывод из многословного вакуума:

VACUUM (VERBOSE, ANALYZE) mytable;

INFO:  vacuuming "public.mytable"
INFO:  index "mytable_pkey" now contains 461691169 row versions in 1265896 pages
DETAIL:  0 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 2695.21 s.
INFO:  index "id_1_idx" now contains 461691169 row versions in 1265912 pages
DETAIL:  0 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 1493.20 s.
INFO:  index "id_2_idx" now contains 461691169 row versions in 1265912 pages
DETAIL:  0 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 1296.06 s.
INFO:  index "mytable_id_1_id_2_idx" now contains 461691169 row versions in 1265912 pages
DETAIL:  0 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 2364.16 s.
INFO:  "mytable": found 0 removable, 389040319 nonremovable row versions in 5187205 out of 6155883 pages
DETAIL:  0 dead row versions cannot be removed yet, oldest xmin: 12767
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 13560.60 s.
INFO:  analyzing "public.mytable"
INFO:  "mytable": scanned 30000 of 6155883 pages, containing 2250000 live rows and 0 dead rows; 30000 rows in sample, 461691225 estimated total rows
VACUUM

Ответы [ 2 ]

1 голос
/ 05 марта 2019

TL; DR

Ввод / вывод хранилища - это ваше основное узкое место + недостаточно ОЗУ для индексов, поскольку вы можете просто рассчитать сами:

Для сканирования кучи растрового изображенияВы можете рассчитать среднюю задержку чтения блока ~ 2,5 миллисекунды (чтение 17020 блоков за 41971,798 мс), что слишком медленно.

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

Длинная версия :

Считывание идеального результата EXPLAIN указывает на то, что оценка затрат, выполненная планировщиком, далека, и падение производительности происходит из-за операций чтения с диска.

Как вы писали, данные не изменяются со временем (и, следовательно, вы заранее знаете диапазоны значений) вы также можете разбить таблицу по этим двум столбцам, чтобы затем сканировать только определенный раздел (используя меньшие индексы и читая меньшую кучу таблиц).Но если приложение, получающее доступ к этим данным, в конечном итоге получает доступ ко всему диапазону данных в большей или меньшей степени, это тоже не очень поможет.

В результате вам следует подумать о замене подсистемы хранения, чтобы иметь возможность обрабатывать ваши данные.запросы в рамках требований к производительности, которые есть у вашего приложения.

У меня есть подозрение, что сервер PostgreSQL по-прежнему работает на жестком диске, а не на твердотельном накопителе.Небольшой тест с только 120M строками показывает следующие характеристики для обоих индексов:

create table nums (uid integer primary key, id_1 integer, id_2 integer, d1 integer, d2 integer, d3 integer, d4 integer, d5 integer, d6 integer, d7 integer, d8 integer, d9 integer, d10 integer, d11 integer, d12 integer, d13 integer, d14 integer, d15 integer, d16 integer, d17 integer);

INSERT INTO nums select generate_series(80000001, 200000000) AS uid, (random() * 23000)::integer + 5600000 AS id_1, (random() * 25000)::integer + 5600000 AS id_2, (random() * 1000)::integer AS d1, (random() * 1000)::integer AS d2, (random() * 1000)::integer AS d3, (random() * 1000)::integer AS d4, (random() * 1000)::integer AS d5, (random() * 1000)::integer AS d6, (random() * 1000)::integer AS d7, (random() * 1000)::integer AS d8, (random() * 1000)::integer AS d9, (random() * 1000)::integer AS d10, (random() * 1000)::integer AS d11, (random() * 100)::integer AS d12, (random() * 100)::integer AS d13, (random() * 100)::integer AS d14, (random() * 100)::integer AS d15, (random() * 100)::integer AS d16, (random() * 100)::integer AS d17;

create index id_1_idx on nums (id_1);
create index id_2_idx on nums (id_2);
cluster nums using id_1_idx;

..., что приводит к следующему (оба холодных чтения):

explain (analyze, buffers) select * from nums where id_1 = 5606001;
                                                       QUERY PLAN                                                        
-------------------------------------------------------------------------------------------------------------------------
 Index Scan using id_1_idx on nums  (cost=0.57..5816.92 rows=5198 width=80) (actual time=1.680..6.394 rows=5185 loops=1)
   Index Cond: (id_1 = 5606001)
   Buffers: shared read=88
   I/O Timings: read=4.397
 Planning Time: 4.002 ms
 Execution Time: 7.475 ms
(6 rows)

Time: 15.924 ms

... идля id_2:

explain (analyze, buffers) select * from nums where id_2 = 5606001; 
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Index Scan using id_2_idx on nums  (cost=0.57..5346.53 rows=4777 width=80) (actual time=0.376..985.689 rows=4748 loops=1)
   Index Cond: (id_2 = 5606001)
   Buffers: shared hit=1 read=4755
   I/O Timings: read=972.555
 Planning Time: 0.203 ms
 Execution Time: 986.590 ms
(6 rows)

Time: 987.296 ms

Так что, хотя моя таблица "просто", 12 ГиБ + 3x 2,5 ГиБ (PK + 2 индекса) все еще достаточно быстр.

В случае, если серверуже работает на SSD, убедитесь, что (физически) разделите хранилище данных для WAL / журнала, табличных данных (табличное пространство), индексов (табличное пространство), чтобы максимально использовать параллелизм и уменьшить помехи ввода-вывода, вызванные другими службами/ Приложения в той же системе.

Также подумайте о серверной системе с гораздо большим объемом памяти для таблицы и данных индекса (для этой таблицы ~ 48 ГиБ + ~ 10 ГБ на индекс, при условии, что все integer столбцы)а затем выполните прогрев, чтобы перенести данные с диска в память.По крайней мере, индексы должны полностью оставаться в памяти.

EDIT : причина, по которой мой сервер не использует растровое сканирование (индекс + куча), заключается в том, что яЯ работаю на SSD, и я изменил стоимость случайной страницы со значения по умолчанию 4 до 1.1.Для системы с жесткими дисками это, конечно, не имеет смысла.

EDIT # 2 : повторное тестирование ситуации выявило интересное поведение:

В моем тестеЯ предположил, что первый столбец uid является столбцом первичного ключа и является serial (последовательным целым числом), по которому записи первоначально сортируются на диске.При генерации данных значения для обоих интересных индексированных столбцов id_1 и id_2 генерируются случайным образом, что обычно заканчивается худшим случаем для больших таблиц.

Однако в данном случае это не так.После создания тестовых данных и индексов и после анализа таблицы , но до переупорядочивание данных с использованием индекса по столбцу id_1 Теперь я получаю следующие результаты:

explain (analyze, buffers) select * from nums where id_1 = 5606001;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on nums  (cost=63.32..7761.68 rows=5194 width=80) (actual time=1.978..41.007 rows=5210 loops=1)
   Recheck Cond: (id_1 = 5606001)
   Heap Blocks: exact=5198
   Buffers: shared read=5217
   I/O Timings: read=28.732
   ->  Bitmap Index Scan on id_1_idx  (cost=0.00..62.02 rows=5194 width=0) (actual time=1.176..1.176 rows=5210 loops=1)
         Index Cond: (id_1 = 5606001)
         Buffers: shared read=19
         I/O Timings: read=0.124
 Planning Time: 7.214 ms
 Execution Time: 41.419 ms
(11 rows)

...and:

explain (analyze, buffers) select * from nums where id_2 = 5606001;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on nums  (cost=58.52..7133.04 rows=4768 width=80) (actual time=7.305..43.830 rows=4813 loops=1)
   Recheck Cond: (id_2 = 5606001)
   Heap Blocks: exact=4805
   Buffers: shared hit=12 read=4810
   I/O Timings: read=28.181
   ->  Bitmap Index Scan on id_2_idx  (cost=0.00..57.33 rows=4768 width=0) (actual time=5.102..5.102 rows=4813 loops=1)
         Index Cond: (id_2 = 5606001)
         Buffers: shared read=17
         I/O Timings: read=2.414
 Planning Time: 0.227 ms
 Execution Time: 44.197 ms
(11 rows)

Все планы и оптимизации доступны здесь:

Я также следовал своим собственным рекомендациям и выделил индексы для другого табличного пространства на другом физическом твердотельном накопителе.

Как мы можем видеть, чтобы получить ~ 5000 результирующих строк, которые у него естьчтобы прочитать более или менее одинаковое количество блоков здесь, в обоих случаях используется сканирование кучи растрового изображения.

Корреляция для двух столбцов в этом случае:

 attname | correlation | n_distinct 
---------+-------------+------------
 id_1    |  -0.0047043 |      23003
 id_2    |  0.00157998 |      25004

Теперь, повторный тест запросов после , CLUSTER ... USING id_1_idx и после повторного анализа, что приводит к следующей корреляции:

 attname | correlation  | n_distinct 
---------+--------------+------------
 id_1    |            1 |      22801
 id_2    | -0.000898521 |      24997

... показал следующие выступления:

explain (analyze, buffers) select * from nums where id_1 = 5606001;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Index Scan using id_1_idx on nums  (cost=0.57..179.02 rows=5083 width=80) (actual time=2.604..5.256 rows=5210 loops=1)
   Index Cond: (id_1 = 5606001)
   Buffers: shared read=90
   I/O Timings: read=4.107
 Planning Time: 4.039 ms
 Execution Time: 5.563 ms
(6 rows)

... что намного лучше - как и ожидалось - но:

explain (analyze, buffers) select * from nums where id_2 = 5606001;
                                                       QUERY PLAN                                                       
------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on nums  (cost=58.57..7140.12 rows=4775 width=80) (actual time=5.866..99.707 rows=4813 loops=1)
   Recheck Cond: (id_2 = 5606001)
   Heap Blocks: exact=4806
   Buffers: shared read=4823
   I/O Timings: read=31.389
   ->  Bitmap Index Scan on id_2_idx  (cost=0.00..57.38 rows=4775 width=0) (actual time=2.992..2.992 rows=4813 loops=1)
         Index Cond: (id_2 = 5606001)
         Buffers: shared read=17
         I/O Timings: read=0.338
 Planning Time: 0.210 ms
 Execution Time: 100.155 ms
(11 rows)

... более чем в в два раза медленнее , несмотря на то, что нужно было прочитать почти такое же количество блоков, как в первом случайном прогоне.

Почему он так сильно тормозит?

Физическое переупорядочение данных таблицы с использованием индекса id_1_idx также повлияло на физический порядок столбца. Теперь цель сканирования кучи растрового изображения - получить список блоков для чтения в физическом (на диске) порядке из сканирования растрового индекса. В первом случае (случайном) была довольно высокая вероятность того, что несколько строк, соответствующих критериям, были расположены в последовательных блоках на диске, что привело к уменьшению случайного доступа к диску.

Интересно (но это может быть только потому, что я работаю на SSD), отключение растрового сканирования показало приемлемые цифры:

explain (analyze, buffers) select * from nums where id_2 = 5606001;
                                                        QUERY PLAN                                                        
--------------------------------------------------------------------------------------------------------------------------
 Index Scan using id_2_idx on nums  (cost=0.57..7257.12 rows=4775 width=80) (actual time=0.151..35.453 rows=4813 loops=1)
   Index Cond: (id_2 = 5606001)
   Buffers: shared read=4823
   I/O Timings: read=30.051
 Planning Time: 1.927 ms
 Execution Time: 35.810 ms
(6 rows)

Все эти числа являются почти полными операциями холодного запуска (как вы можете видеть без или с очень низкими Buffers: shared hit числами.

Интересно также то, что временные интервалы ввода-вывода очень похожи между сканированием растрового изображения и сканированием индекса для id_2, но сканирование растрового изображения, кажется, вносит огромные издержки.

0 голосов
/ 05 марта 2019

Разница в том, что id_1 сильно коррелирует, то есть порядок этого столбца соответствует физическому порядку строк, в то время как id_2 не коррелируется.

Тест с

SELECT attname, correlation
FROM pg_stats
WHERE tablename = 'mytable'
  AND attname IN ('id_1', 'id_2');

Если корреляция высокая, строки для одного значения столбца будут находиться в нескольких смежных блоках таблицы.Если корреляция низкая, строки будут по всей таблице, и нужно будет прочитать еще много блоков.

Чтобы добиться высокой корреляции, вы можете переписать таблицу с помощью оператора CLUSTER, чтобы изменить порядок строк.Если удалений и обновлений нет, таблица будет физически упорядочена в порядке вставки.

Вы можете ускорить один или другой запрос, но не оба сразу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...