Запрос выбора счетчика очень медленный в холодном кеше

Проблема

Следующий запрос занимает 42 секунды, если большая часть данных не кэширована:

EXPLAIN (ANALYZE, BUFFERS) select count(*) from packages where company_id = 178381;
                                                                      QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=395914.63..395914.63 rows=1 width=8) (actual time=42411.940..42411.942 rows=1 loops=1)
   Buffers: shared hit=21775 read=94888
   I/O Timings: read=39723.315
   ->  Bitmap Heap Scan on packages  (cost=1053.07..395761.41 rows=306442 width=0) (actual time=83.104..42336.765 rows=322432 loops=1)
         Recheck Cond: (company_id = 178381)
         Heap Blocks: exact=116385
         Buffers: shared hit=21775 read=94888
         I/O Timings: read=39723.315
         ->  Bitmap Index Scan on packages_company_id_index  (cost=0.00..1037.75 rows=306442 width=0) (actual time=45.846..45.847 rows=325795 loops=1)
               Index Cond: (company_id = 178381)
               Buffers: shared hit=1 read=277
               I/O Timings: read=7.090
 Planning:
   Buffers: shared hit=2
 Planning Time: 0.237 ms
 Execution Time: 42413.042 ms

Повторный запуск запроса сразу после этого, естественно, намного быстрее:

distru_prod=> EXPLAIN (ANALYZE, BUFFERS) select count(*) from packages where company_id = 178381;
                                                                      QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=395914.63..395914.63 rows=1 width=8) (actual time=416.943..416.957 rows=1 loops=1)
   Buffers: shared hit=116589
   ->  Bitmap Heap Scan on packages  (cost=1053.07..395761.41 rows=306442 width=0) (actual time=78.925..395.495 rows=322432 loops=1)
         Recheck Cond: (company_id = 178381)
         Heap Blocks: exact=116308
         Buffers: shared hit=116589
         ->  Bitmap Index Scan on packages_company_id_index  (cost=0.00..1037.75 rows=306442 width=0) (actual time=46.359..46.360 rows=325351 loops=1)
               Index Cond: (company_id = 178381)
               Buffers: shared hit=281
 Planning:
   Buffers: shared hit=448
 Planning Time: 1.375 ms
 Execution Time: 418.321 ms

Больше информации

  • PostgreSQL 14.12
  • Работа на Google Cloud Platform (8 виртуальных ЦП, 50 ГБ памяти, твердотельный накопитель).
  • Загрузка ЦП была ниже 25 % на момент выполнения приведенных выше запросов.

Вот некоторая основная информация об этой таблице:

select count(distinct company_id) from packages;
 count
-------
   691

select count(*) from packages;
  count
----------
 10764441

select count(*) from packages where company_id = 178381;
 count
--------
 322432

select pg_size_pretty(pg_total_relation_size('packages'));
 pg_size_pretty
----------------
 12 GB

select pg_size_pretty(pg_total_relation_size('packages_company_id_index'));
 pg_size_pretty
----------------
 79 MB

Используемый индекс:

CREATE INDEX packages_company_id_index ON public.packages USING btree (company_id);

Даже при использовании pg_hint_plan для принудительного сканирования только индекса на packages_company_id_index, buffers по-прежнему такие же высокие, как при сканировании кучи растровых изображений:

/*+ IndexOnlyScan(packages packages_company_id_index) */ EXPLAIN (ANALYZE, BUFFERS) select count(*) from packages where company_id = 178381;
                                                                           QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=517401.31..517401.32 rows=1 width=8) (actual time=172.448..172.450 rows=1 loops=1)
   Buffers: shared hit=116586
   ->  Index Only Scan using packages_company_id_index on packages  (cost=0.09..517248.09 rows=306442 width=0) (actual time=0.034..150.510 rows=322432 loops=1)
         Index Cond: (company_id = 178381)
         Heap Fetches: 325351
         Buffers: shared hit=116586
 Planning:
   Buffers: shared hit=2
 Planning Time: 0.238 ms
 Execution Time: 172.546 ms

Вопросы

  1. Почему для сканирования только индекса по-прежнему требуется столько же buffers, сколько и для исходного запроса, хотя размер индекса на диске составляет всего 79 МБ?
  2. Почему Postgres не выполняет сканирование только индекса, если не указано иное? Казалось бы, этот запрос можно выполнить, вообще не обращаясь к таблице packages?
  3. Исходному запросу требуется 42 секунды для подсчета 322 тысяч строк в таблице размером 10 миллионов строк, даже если для него существует идеально соответствующий индекс.
    1. Это кажется слишком высоким, даже с учетом холодного кэша, но, может быть, такая производительность ожидаема?
    2. Если этого не ожидается, что я могу делать не так?
    3. Если это ожидается, есть ли какие-либо способы повысить производительность приведенного выше запроса в холодном кеше без предварительного разогрева кеша?

Обратите внимание, что вопросы о переполнении стека должны содержать один вопрос, а не шесть.

TylerH 30.07.2024 22:49
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
1
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий
  1. Сканирование только по индексу считывает так много буферов, потому что на самом деле это не сканирование только по индексу. VACUUM таблица, чтобы обновить карту видимости, и вы увидите разницу.

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

  3. Согласно выложенным вами данным, таблица должна иметь размер не менее 900 МБ (116589 страниц по 8 КБ каждая).

    1. Ожидаете ли вы, что ваш диск будет читать со скоростью более 19 МБ/с?

    2. Невозможно ответить, потому что это зависит от неизвестного «если».

    3. См. 1 и 2 выше.

Большое спасибо, Лоренц! Вы были в точку. Запуск vacuum позволил Postgres выполнять сканирование только индекса без выборки данных из кучи. Я не знал о карте видимости. Просто прочитайте об этом в двух замечательных статьях, которые вы написали (1 2), и теперь все имеет смысл. Еще раз спасибо!

Jordi Chacón 29.07.2024 16:12

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

Конечно, обновление карты видимости, чтобы сканирование только по индексу могло работать правильно, и поэтому избавление или значительное сокращение «Выборки кучи: 325351» может сделать это бессмысленным.

Другие вопросы по теме