Збільшення 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), але це не допомогло. Я розігрувався з спільними_буферами та work_mem. Поступово змінюючи їх із дуже консервативних значень за замовчуванням (128k / 1MB), поступово знижувалася продуктивність.

Я побіг 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. Я запускаю autovacuum у конфігурації за замовчуванням, але не очікую, що це має щось спільне з цим. Усі запити виконувались на одній машині за кілька секунд, лише зі зміненою конфігурацією (та перезапущеним PG).

Редагувати: Я щойно виявив один особливо цікавий факт: коли я виконую той самий тест на середині 2010 року iMac (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-продуктивності .


1
Схоже, що у повільному випадку кожен крок послідовно повільніший. Чи залишилися інші налаштування такими ж?
dezso

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 зазвичай не ризикує). Майте на увазі, що це впливає також на випадковий та послідовний доступ до сторінки. Таким чином, ви можете отримати кращу продуктивність із меншими налаштуваннями спільних_буферів.

Моє відчуття кишки полягає в тому, що ви, мабуть, покращуєте або, принаймні, стабільніша, продуктивність у середовищах з високою сумісністю із більшими налаштуваннями спільного_буфера. Також майте на увазі, що PostgreSQL захоплює цю пам'ять і зберігає її, тому якщо у вас є інші функції, які працюють у системі, буфери для читання містять файли, прочитані іншими процесами. Це дуже велика і складна тема. Більш великі настройки спільного буфера забезпечують кращі гарантії продуктивності, але можуть забезпечити меншу ефективність у деяких випадках.


10

Крім здавалося б парадоксального ефекту, який збільшує work_memзниження продуктивності ( @Chris може мати пояснення), ви можете покращити свою функцію хоча б двома способами.

  • Перепишіть дві підроблені LEFT JOIN«S з 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
Я хочу скористатись локальною пропозицією Erwin для локальної роботи. Оскільки work_mem змінює види запитів, які швидше, можливо, вам знадобиться змінити їх для деяких запитів. Тобто низький рівень work_mem найкраще підходить для запитів, які сортують / об'єднують невелику кількість записів складними способами (тобто великою кількістю приєднань), тоді як високий рівень work_mem найкращий для запитів, які мають кілька видів, але які сортують або приєднують велику кількість рядків відразу .
Кріс Траверс

Я покращила запит тим часом (питання з жовтня минулого року), але дякую :) Це питання стосується більше несподіваного ефекту, ніж конкретного запиту. Запит служить в основному для демонстрації ефекту. Дякую за підказку щодо індексу, я спробую це!
Петро Праус
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.