PostgreSQL. Часть 7. Производительность и конкурентный доступ PostgreSQL
PostgreSQL. Часть 7. Производительность и конкурентный доступ PostgreSQL
Эта одна из публикаций цикла конспектов по PostgreSQL. Материал этих публикаций не особо структурирован и представляет собой просто заметки – конспектировалось что-то новое и то, что посчиталось интересным и важным. Эта публикация некий конспект про производительность и конкурентный доступ PostgreSQL.
Список литературы
При подготовке этого конспекта использовались следующие материалы:
1. Книга PostgreSQL 14 Administration Cookbook.
2. Курс DBA1: Администрирование PostgreSQL 13. Базовый курс.
Производительность и конкурентный доступ
Поиск медленных запросов
1. Проверить, что расширение pg_stat_statements установлено:
\x
\dx pg_stat_statements
2. Если расширение установлено, то должна отобразиться соответствующая запись:
-[ RECORD 1 ]--------------------------------------------
-----------
Name
| pg_stat_statements
Version
| 1.9
Schema
| public
Description | track execution statistics of all SQL
statements executed
3. 10 самых нагруженных запросов. Пример запроса:
SELECT calls, total_time, query
FROM pg_stat_statements
ORDER BY total_time DESC LIMIT 10;
Параметр для записи медленных запросов в журнал. Все запросы, выполняющие более 10 секунд будут логироваться:
ALTER SYSTEM SET log_min_duration_statement = 10000;
Выяснение того, что замедляет запросы
Потенциальные проблемы с теми запросами, которые выполняются медленно:
- Запрос возвращает слишком много данных.
- Запросу приходится обрабатывать слишком много данных.
- Отсутствие нужных индексов.
- Неправильная оценка плана выполнения сервером PostgreSQL. Например, в тех ситуациях, когда статистика для базы данных не обновлялась (отключена автоматическая очистка).
- Проблемы взаимоблокировок.
- Недостаточная производительность дисковой подсистемы, CPU, малое количество оперативной памяти на сервере или высокая нагрузка на сетевую подсистему.
Пример запроса плана выполнения запроса:
EXPLAIN (ANALYZE, BUFFERS) SELECT count(*) FROM t;
Уменьшение выводимого количества строк
Например, в некоторых ситуациях вам нужно вывести не все строки, а только определенный набор строк, чтобы понять общий профиль данных. В таком случае можно использовать опцию TABLESAMPLE. Она выведет определенное количество строк от общего числа строк в таблице:
SELECT * FROM t TABLESAMPLE SYSTEM (0.1);
Критерии сложности запросов
Считать запрос сложны только из-за того, что он возвращает много результирующих строк не совсем правильно. В определении критериев сложности запроса, пожалуй, может помочь инструкция EXPLAIN. Если в результате анализа плана выполнения запроса через EXPLAIN получается очень много строк, соединений и других вызовов, то запрос, действительно, можно считать сложным.
Если запрос, на ваш взгляд, выглядит сложным из-за количества текста, который формирует его, но при анализе плана выполнения через инструкцию EXPLAIN получается не много шагов, то запрос стоит отнести к несложным запросам.
Оптимизация запросов без необходимости их переписывать
Один из вариантов проверки причин, по которым запрос работает медленно – это выявление недостатка ресурсов или установленных значений конфигурационных параметров. Например, увеличение объема используемой памяти.
Определить текущее значение для параметра work_mem:
SHOW work_mem;
Затем выполнить анализ времени выполнения запроса:
explain select * from t;
Установить более подходящий параметр:
SET work_mem = '128MB';
Затем повторно выполнить анализ времени выполнения запроса:
explain select * from t;
Проанализировать результат. Если запрос стал работать гораздо быстрее, то есть смысл увеличить значение параметра в конфигурационном файле. Или для конкретной базы данных.
Если увеличение значения какого-то параметра не помогло, то можно вернуть его значение к значение по умолчанию:
RESET work_mem;
Почему запрос не использует индекс
Сначала стоит проанализировать таблицу:
ANALYZE;
После анализа таблицы проверить запрос еще раз.
Сравнить время выполнения и план выполнения без индекса:
EXPLAIN ANALYZE SELECT count(*) FROM itable WHERE id > 500;
Сравнить время выполнения и план выполнения при использовании индекса:
SET enable_seqscan TO false;
EXPLAIN ANALYZE SELECT count(*) FROM itable WHERE id > 500;
Как видно из скриншота выше при использовании индекса время выполнения запроса получается больше, чем время выполнения запроса при последовательном обходе таблицы. Поэтому индекс не используется в самом первоначальном запросе.
Принудительное использование индекса
Время выполнения запроса можно потенциально уменьшить при использовании параллельной его обработки. Запрос будет “разбит” сервером на несколько частей и каждая из этих частей будет выполняться в своем отдельном потоке. Однако, это увеличивает количество используемых ресурсов.
Сначала выполнил запрос в один поток
\timing
SET max_parallel_workers_per_gather = 0;
Выполним запрос и зафиксируем время его выполнения:
SELECT count(*) FROM big;
count
---------
1000000
(1 row)
Time: 46.399 ms
Теперь увеличим количество потоков:
SET max_parallel_workers_per_gather = 2;
Повторно выполним запрос:
SELECT count(*) FROM big;
count
---------
1000000
(1 row)
Time: 25.009 ms
При использовании нескольких потоков запрос завершился быстрее. Но важно вести наблюдение за общей нагрузкой на сервер PostgreSQL при использовании многопоточной обработки запросов.