Увеличение work_mem и shared_buffers на Postgres 9.2 значительно замедляет запросы

39

У меня есть экземпляр PostgreSQL 9.2, работающий на RHEL 6.3, 8-ядерный компьютер с 16 ГБ ОЗУ. Сервер выделен для этой базы данных. Учитывая, что файл postgresql.conf по умолчанию довольно консервативен в отношении настроек памяти, я подумал, что было бы неплохо разрешить Postgres использовать больше памяти. К моему удивлению, следующий совет на wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server значительно замедлил практически каждый выполняемый мной запрос, но он явно более заметен в более сложных запросах.

Я также попытался запустить pgtune, который дал следующую рекомендацию с более настроенными параметрами, но это ничего не изменило. Он предлагает shared_buffers размером 1/4 ОЗУ, что, по-видимому, согласуется с рекомендациями в других местах (и в частности в PG wiki).

default_statistics_target = 50
maintenance_work_mem = 960MB
constraint_exclusion = on
checkpoint_completion_target = 0.9
effective_cache_size = 11GB
work_mem = 96MB
wal_buffers = 8MB
checkpoint_segments = 16
shared_buffers = 3840MB
max_connections = 80

Я попытался переиндексировать всю базу данных после изменения настроек (используя reindex database), но это тоже не помогло. Я играл с shared_buffers и work_mem. Постепенно изменяя их с очень консервативных значений по умолчанию (128 КБ / 1 МБ), постепенно снижается производительность.

Я запустил EXPLAIN (ANALYZE,BUFFERS)несколько запросов, и виновник, похоже, в том, что Hash Join значительно медленнее. Мне не понятно почему.

Чтобы привести конкретный пример, у меня есть следующий запрос. Он работает ~ 2100 мс в конфигурации по умолчанию и ~ 3300 мс в конфигурации с увеличенным размером буфера:

select count(*) from contest c
left outer join contestparticipant cp on c.id=cp.contestId
left outer join teammember tm on tm.contestparticipantid=cp.id
left outer join staffmember sm on cp.id=sm.contestparticipantid
left outer join person p on p.id=cp.personid
left outer join personinfo pi on pi.id=cp.personinfoid
where pi.lastname like '%b%' or pi.firstname like '%a%';

EXPLAIN (ANALYZE,BUFFERS) для запроса выше:

Вопрос в том, почему я наблюдаю снижение производительности при увеличении размеров буфера? У машины точно не хватает памяти. Выделение, если для разделяемой памяти в ОС задано ( shmmaxи shmall) очень большое значение, это не должно быть проблемой. Я не получаю никаких ошибок в журнале Postgres либо. Я использую автовакуум в конфигурации по умолчанию, но не ожидаю, что это как-то связано с этим. Все запросы выполнялись на одной и той же машине с интервалом в несколько секунд, только с измененной конфигурацией (и перезапускали PG).

Изменить: Я только что нашел один особенно интересный факт: когда я выполняю тот же тест на моем iMac в середине 2010 года (OSX 10.7.5) также с Postgres 9.2.1 и 16 ГБ оперативной памяти, я не испытываю замедления. В частности:

set work_mem='1MB';
select ...; // running time is ~1800 ms
set work_mem='96MB';
select ...' // running time is ~1500 ms

Когда я делаю точно такой же запрос (тот, что выше) с точно такими же данными на сервере, я получаю 2100 мс с work_mem = 1 МБ и 3200 мс с 96 МБ.

У Mac есть SSD, так что это понятно быстрее, но он демонстрирует поведение, которое я ожидаю.

Смотрите также последующее обсуждение pgsql-performance .

Петр Праус
источник
1
Похоже, что в более медленном случае каждый шаг постоянно медленнее. Остальные настройки остались прежними?
Дезсо
1
Возможно, стоит спросить об этом на более специализированном форуме, а не на общем. В этом случае я предлагаю pgsql-общий список рассылки archives.postgresql.org/pgsql-general
Colin 't Hart
1
О, и доложите, пожалуйста, ответьте на свой вопрос, если найдете ответ! (Это разрешено, даже поощряется).
Colin 't Hart
1
Я действительно удивляюсь, насколько Postgres похож на Oracle в этом отношении: я помню курс Джонатана Льюиса (гуру Oracle), в котором он продемонстрировал, что выделение большего количества памяти для сортировок иногда делает их медленнее. Я забыл подробности, но это было связано с тем, что Oracle выполняет частичные сортировки, а затем записывает их во временное хранилище, а затем объединяет их позже. Каким-то образом больше памяти замедлило этот процесс.
Colin 't Hart
2
Вопрос теперь размещен на pgsql-performance: archives.postgresql.org/pgsql-performance/2012-11/msg00004.php
Петр Праус

Ответы:

28

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

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


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

work_mem : сколько памяти мы можем выделить для сортировки или связанной операции соединения. Это для каждой операции, а не для оператора или серверной части, поэтому один сложный запрос может многократно использовать этот объем памяти. Не ясно, что вы достигаете этого предела, но это стоит отметить и знать. если вы увеличите это значение слишком сильно, вы потеряете память, которая может быть доступна для кэша чтения и общих буферов.

shared_buffers : сколько памяти выделить для действительной очереди страниц PostgreSQL. Теперь, в идеале, интересный набор вашей базы данных должен оставаться в кеше памяти здесь и в буферах чтения. Тем не менее, это гарантирует, что наиболее часто используемая информация во всех бэкэндах кэшируется, а не сбрасывается на диск. В Linux этот кеш значительно медленнее, чем дисковый кеш ОС, но он гарантирует, что дисковый кеш ОС не работает и прозрачен для PostgreSQL. Это довольно ясно, где ваша проблема.

Так что получается, что когда у нас есть запрос, мы сначала проверяем общие буферы, так как PostgreSQL хорошо знает этот кеш, и ищем страницы. Если их там нет, мы просим ОС открыть их из файла, и если ОС кэширует результат, она возвращает кэшированную копию (это быстрее, чем общие буферы, но Pg не может определить, кэширована она или включена. диск и диск намного медленнее, поэтому PostgreSQL обычно не использует этот шанс). Имейте в виду, что это также влияет на случайный и последовательный доступ к страницам. Таким образом, вы можете получить лучшую производительность при более низких настройках shared_buffers.

У меня есть ощущение, что вы, вероятно, получите лучшую или, по крайней мере, более стабильную производительность в средах с высоким уровнем параллелизма и большими настройками shared_buffer. Также имейте в виду, что PostgreSQL захватывает эту память и удерживает ее, поэтому, если в системе выполняются другие функции, буферы чтения будут хранить файлы, прочитанные другими процессами. Это очень большая и сложная тема. Большие параметры общего буфера обеспечивают лучшие гарантии производительности, но могут в некоторых случаях обеспечивать меньшую производительность.

Крис Траверс
источник
10

Помимо, казалось бы, парадоксального эффекта увеличения work_memпроизводительности (у @Chris может быть объяснение), вы можете улучшить свою функцию по крайней мере двумя способами.

  • Перепишите две фальшивые LEFT JOINс JOIN. Это может запутать планировщик запросов и привести к худшим планам.

SELECT count(*) AS ct
FROM   contest            c
JOIN   contestparticipant cp ON cp.contestId = c.id
JOIN   personinfo         pi ON pi.id = cp.personinfoid
LEFT   JOIN teammember    tm ON tm.contestparticipantid = cp.id
LEFT   JOIN staffmember   sm ON sm.contestparticipantid = cp.id
LEFT   JOIN person        p  ON p.id = cp.personid
WHERE (pi.firstname LIKE '%a%'
OR     pi.lastname  LIKE '%b%')
  • Предполагая , что фактические схемы поиска более избирательны, использование триграммы индексов на pi.firstnameи pi.lastnameподдерживать , не привязанные LIKEпоиски. (Более короткие шаблоны, такие '%a%'как также поддерживаются, но индекс вряд ли поможет для неселективных предикатов.):

CREATE INDEX personinfo_firstname_gin_idx ON personinfo USING gin (firstname gin_trgm_ops);
CREATE INDEX personinfo_lastname_gin_idx  ON personinfo USING gin (lastname gin_trgm_ops);

Или один многоколонный индекс:

CREATE INDEX personinfo_name_gin_idx ON personinfo USING gin (firstname gin_trgm_ops, lastname gin_trgm_ops);

Должен сделать ваш запрос немного быстрее. Для этого вам нужно установить дополнительный модуль pg_trgm . Подробности по этим связанным вопросам:


Кроме того, вы пытались установить work_mem локально - только для текущей транзакции ?

SET LOCAL work_mem = '96MB';

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

Эрвин Брандштеттер
источник
3
Я хочу поддержать местное предложение Эрвина work_mem. Поскольку work_mem изменяет виды запросов, которые выполняются быстрее, вам может потребоваться изменить его для некоторых запросов. Т.е. низкие уровни work_mem лучше всего подходят для запросов, которые сложным образом сортируют / объединяют небольшое количество записей (т. Е. Много объединений), в то время как высокие уровни work_mem лучше всего подходят для запросов, которые имеют несколько сортировок, но которые сортируют или объединяют большое количество строк одновременно. ,
Крис Трэверс
Тем временем я улучшил запрос (вопрос с октября прошлого года), но спасибо :) Этот вопрос больше о неожиданном эффекте, чем конкретный запрос. Запрос служит главным образом для демонстрации эффекта. Спасибо за подсказку по индексу, попробую!
Петр Праус