рекомендации объяснения.tensor.ru

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


Клиент сообщил о проблеме, из-за которой интерфейс работал медленно при просмотре журналов. Анализ показал, что причина в долгом выполнении запроса (приводим сокращенно):

SELECT
	rc.pack
,	rc.recno
,	rc.ts
,	rc.type
,	rc.duration
,	coalesce(rc.unparsed, '') unparsed
,	rc.dt::text
,	get_rawdata_str(rc.dt, rc.pack, rc.recno) "text"
,	( SELECT ... ) query
,	regexp_replace(( SELECT ... )::text, '^\[(.*)\]$', '\1') context
,	( SELECT ... ) parameters
,	( SELECT ... ) exectime
,	( SELECT ... ) plan
,	( SELECT ... ) "lock"
,	err.error
,	err.errargs
,	err.msg
FROM
	record rc
LEFT JOIN
	LATERAL( SELECT ...	) err
		ON TRUE
WHERE
	(rc.pack, rc.dt) = ($1::uuid, $2::date)
ORDER BY
	recno;

Загрузка плана запроса в объяснение.tensor.ru :

план запроса

план запроса

Мы видим, что все время ушло на корневой узел»Левое присоединение к вложенному циклу«.

Это произошло в результате вызова функций по столбцам корневого JOIN, но где get_rawdata_str Или regexp_replace . В этом случае обе функции не отображаются в плане, а все их время и ресурсы отражаются в корневом узле:

план-схема

план-схема

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

Проверка функции PL/pgSQL get_rawdata_str — генерирует свой план, но при вызове он не отображается EXPLAIN ANALYZE . Получить план запроса из такой функции можно только с помощью модуля auto_explain когда настройка включена:

SET auto_explain.log_nested_statements = on;

В этом случае журнал будет содержать такие записи, как CONTEXT с планом вызываемой функции:

CONTEXT:  SQL function "get_rawdata_str" statement 1

Мы включаем этот параметр и загружаем план запроса из тела функции:

план запроса функции get_rawdata_str

план запроса функции get_rawdata_str

Видим, что сам запрос занимает 309 мс, но JIT занимает еще 1,7 секунды:

ЧИТАТЬ   РПЦ хочет дегуманизировать ИИ
JIT с точки зрения спроса

JIT с точки зрения спроса

Но почему JIT включен на клиенте, а не на наших серверах?

Вообще JIT появился в 11 версии, Здесь была некоторая дискуссия по поводу его включения.

В версии 11 он остался отключенным по умолчанию, а во всех версиях, начиная с 12, JIT включен.

Помимо опций конфигурации, использование JIT требует выполнения нескольких условий:

  1. Сборку ГУ необходимо производить с опцией —with-llvm в официальных пакетах это так, но в случае сборки из исходников проверить наличие опции можно командой:

pg_config --configure | grep with-llvm
  1. наличие JIT-провайдера, по умолчанию это llvmjit который устанавливается из пакета postgresql-llvmjit.

Когда сервер работает инструкции установлены только пакеты postgresql-server а также зависимый postgresql И postgresql-libs и поставщик JIT не установлен по умолчанию.

Мы также не устанавливаем провайдер на наш сервер, поэтому у нас отключен JIT.

Кстати, проверить работоспособность JIT можно так:

SELECT pg_jit_available();

Заказчик сообщил, что база данных была развернута в Docker, так как это была тестовая установка с небольшим объемом данных.

Похоже, что в образе Docker PostgreSQL включен JIT и он работает «из коробки». Мы проверяем:

docker pull postgres
docker run --name postgres -e POSTGRES_PASSWORD=password -d postgres
docker exec -it postgres find / -name "*jit*"

/usr/lib/postgresql/16/lib/llvmjit_types.bc
/usr/lib/postgresql/16/lib/llvmjit.so
/usr/lib/postgresql/16/lib/bitcode/postgres/jit
/usr/lib/postgresql/16/lib/bitcode/postgres/jit/jit.bc
/usr/share/postgresql-common/server/test-with-jit.conf

docker exec -it postgres psql -U postgres -Atc 'SELECT pg_jit_available();'
t

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

Решение о применении JIT-оптимизации принимается на основе общей стоимости запроса. Если значение превышено jit_above_cost (по умолчанию 100 000) Выполняется JIT-оптимизация выражений в WHERE, агрегатов, целевых списков и проекций, а также преобразование кортежей по мере их загрузки с диска в память. В этом случае параметры будут включены в план запроса в разделе JIT. Фразы И Деформация:

JIT:
  Options: Inlining false, Optimization false, Expressions true, Deforming true

Если стоимость запроса превышает jit_inline_above_cost (по умолчанию 500 000) или jit_optimize_above_cost (тоже 500 000 по умолчанию), то тела небольших функций и инструкций будут интегрированы в код и можно будет применить дорогостоящие оптимизации. В плане это будет отображаться в настройках В сети И Оптимизация:

JIT:
  Options: Inlining true, Optimization true, Expressions true, Deforming true

В нашем запросе в теле функции get_rawdata_str несколько LEFT JOIN на таблицах с большим количеством строк, поэтому стоимость запроса была очень большой и планировщик подключал JIT для оптимизации.

ЧИТАТЬ   Базовые запросы в GPT CHAT

Просим клиента отключить JIT:

ALTER SYSTEM SET jit=off;
SELECT pg_reload_conf();

А время выполнения запроса снизилось до 30 мс:

план запроса без JIT

план запроса без JIT

Задача решена. Но чтобы этого не повторилось, мы рекомендуем запускать PostgreSQL в Docker с отключенным по умолчанию JIT.

Для этого добавим в команду запуска опцию отключения JIT:

docker run --name postgres -e POSTGRES_PASSWORD=postgres -d postgres -c jit=off

Или создайте свою собственную конфигурацию и запустите PostgreSQL с помощью:

docker run -i --rm postgres cat /usr/share/postgresql/postgresql.conf.sample > my-postgres.conf
echo "jit=off" >> my-postgres.conf
docker run -d --name postgres -v "$PWD/my-postgres.conf":/etc/postgresql/postgresql.conf -e POSTGRES_PASSWORD=password postgres -c 'config_file=/etc/postgresql/postgresql.conf'

Source

От admin