У одного из клиентов нашей системы мониторинга 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;
Загружаем план запроса на explain.tensor.ru :
Видим, что все время ушло на корневой узел "Nested Loop Left Join" .
Это произошло в результате вызова функций в столбцах корневого 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
Включаем этот параметр, загружаем план запроса из тела функции:
Видим что сам запрос отрабатывает за 309 мс, а вот JIT еще 1.7 сек :
Но почему у клиента включился JIT, а на наших серверах нет ?
Вообще JIT появился в 11 версии, здесь было обсуждение по поводу его включения.
В 11 версии он остался выключенным по умолчанию, а во всех версиях, начиная с 12, JIT включен.
При этом кроме опций конфига для использования JIT требуется соблюдение нескольких условий:
сборка PG должна быть произведена с опцией --with-llvm , в официальных пакетах это так и есть, а в случае сборки из исходников наличие опции проверить можно командой:
pg_config --configure | grep with-llvm
наличие провайдера JIT, по умолчанию это llvmjit , который устанавливается из пакета
postgresql-llvmjit
.
При развороте сервера по инструкции устанавливаются только пакеты postgresql-server
, а также зависимые postgresql
и postgresql-libs
, при этом провайдер JIT по умолчанию не устанавливается.
На нашем сервере мы также не устанавливаем провайдер, поэтому у нас JIT выключен.
Кстати, проверить работоспособность JIT можно так:
SELECT pg_jit_available();
Клиент сообщил, что базу развернули в 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 будут включены параметры Expressions и Deforming:
JIT:
Options: Inlining false, Optimization false, Expressions true, Deforming true
Если же стоимость запроса превысила jit_inline_above_cost (по умолчанию 500 000) или jit_optimize_above_cost (по умолчанию также 500 000) то тела небольших функций и операторов будут встроены в код и может применяться дорогостоящая оптимизация. В плане это будет отображено в параметрах Inlining и Optimization:
JIT:
Options: Inlining true, Optimization true, Expressions true, Deforming true
В нашем запросе в теле функции get_rawdata_str
несколько LEFT JOIN на таблицах с большим количеством строк, в результате стоимость запроса составила очень большие значения и планировщик подключил JIT для оптимизации.
Просим клиента отключить JIT:
ALTER SYSTEM SET jit=off;
SELECT pg_reload_conf();
И время выполнения запроса снизилось до 30 мс:
Проблема решена. Но чтобы она не повторялась, рекомендуем запускать 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'
Комментарии (44)
Kassiy_Pontiy_Pilat
23.04.2024 06:28+2Для 1С лучше выключать или нет? Что то особо нигде не упоминается jit в рекомендациях
MGorkov Автор
23.04.2024 06:28+2это лучше уточнить у консалтеров по 1С, но думаю что по умолчанию надо выключить
1nd1go
23.04.2024 06:28+20Выглядит как симптоматическое лечение. Я бы ожидал, что JIT должен отрабатывать все-таки один раз на запрос, дальше он скэшируется, и больше в этом запросе не должно наблюдаться проблем. У клиента дашборд тормозил при первом запросе или при повторных запросах также?
Также, интересно проверить, не будет ли воспроизводится пробелема с JITом, но без докера?
MGorkov Автор
23.04.2024 06:28+7тормозил постоянно, при повторных запросах тоже.
я проверил на standalone PG 15.5 - JIT не кэшируется, два первых запроса длительностью 3-5 сек, затем JIT выключил и стало менее 30 мс
1nd1go
23.04.2024 06:28понятно. Да, судя по документации jitа в pg много с настройками не разгуляешься.
yannmar
23.04.2024 06:28+5Может быть так, что запросы формируются так, что каждый запрос с точки зрения базы уникален? Может их там как-нибудь неаккуратно конкатенацией собирают без использования препаред стейтментов? Вот и приходится базе каждый запрос заново компилировать?
Sap_ru
23.04.2024 06:28+9Там проблема в том, что бинарный код после JIT жёстко привязан к адресному пространству и запросу, а потому:
1) каждый новый запрос будет занимать всё новые адреса. Выгружать это всё не получится, так как у кода жёсткая привязка к адресам. То есть выгрузить можно, но если при попытке загрузить из кэша обратно окажется, что по этому адресу уже есть какой-то другой запрос, то что тогда делать? Жонглировать запросами в адресном пространстве СУБД? Так себе идея, особенно учитывая, что обрабатывающих запросы потоков много, а адресное пространство у них общее - что делать если разные потоки захотят исполнять разные запросы, которые были скомпилированы по одному адресу? А ещё (все) ОС и процессор очень не любят динамического изменения кода.
В качестве решения предлагается относительного легковесное исправление всех адресов при извлечении запроса из кэша. Но это достаточного тяжёлая операция. Кроме того при патчинге адресов возникают некоторые конфликты с оптимизацией.
2) генерируемый JIT код очень жёстко оптимизируется под конкретный запрос. Например, у вас там проверка на ноль, какая-нибудь или на константу - этот будет очень круто заоптимизировано при JIT, и если в другом запросе константа другая, то ой - наш старый кэш уже не подходит. Если же мы начнём генерировать универсальный код без оптимизации констант и сравнений, то это сильно снизит степень оптимизации кода и замедлит сложные и длительные запросы, ради которых этот JIT и задумывался. Кроме того, конкретно в PG (но в целом это общая проблема всех СУБД) вся оптимизация завязана на планировщик запросов (что логично), и нужно отключать часть оптимизаций уже на уровне планировщика, а значит потенциально замедлять вообще все запросы вне зависимости от JIT и нашего кэша.
Короче, на данный момент создать эффективный кэш оптимизированного бинарного кода невозможно. Не придумано пока никакого нормального общего решения и всё. А все предполагаемые пути решения проблемы ведут к тому, что нужно с ноля писать планировщик и оптимизатор, которые изначально архитектурно будут рассчитаны на последующую работу JIT и кэша. А это громадная работа и переписывание примерно три всего кода СУБД. Причём, всё равно придётся идти на компромиссы в какие-то моменты. Например, нужно, чтобы планировщик очень точно считал вычислительную сложность запросов, а это само по себе нерешённая пока задача (особенно в PG).
andrydl
23.04.2024 06:28+3Проблема была постоянной, если бы это был разовый запрос , а потом все нормально, то никто бы даже не стал искать причину.
Tzimie
23.04.2024 06:28А вот не надо все что угодно совать в докер
gmini
23.04.2024 06:28+28при чем тут собственно докер? Если бы установили на хост со включенным jit было бы то же самое. Наоборот, докер тут может помочь получать воспроизводимый результат
AlexGluck
23.04.2024 06:28+14Вот бы автор убрал из заголовка, что проблема в докере, чтобы такие существа как вы не считали, что новая упаковка (вместо привычных deb\rpm\tgz) как то влияет на функционирование систем.
Tzimie
23.04.2024 06:28Я просто DBA high load систем. 100 Тб базы, 2тб памяти сервера с правильным пробросом numa (как там у докера с этим?), так что желание завернуть все в докер смешно
AlexGluck
23.04.2024 06:28+9Знание красивых слов не делают ваши утверждения инженерно верными. А у контейнеризации с этим никаких проблем нет, ведь упаковка никак не влияет на вкус шоколада, хотя в вашем случае цвет тот же, но это наверное не шоколад.
Tzimie
23.04.2024 06:28А корректный проброс numa? С этим даже у VMware проблемы
AlexGluck
23.04.2024 06:28+9Проброс куда и зачем? NUMA это по сути области памяти разделённые на домены между процессорами, когда вы настраиваете ограничения работы процесса чтобы он работал только в рамках конкретного домена, это в линуксе закрепление за определёнными ядрами. А раз вы для процесса устанавливаете параметры, то что вам мешает воспользоваться инструментом который проще предоставляет такие возможности? И конечно инструментарий не накладывает никаких ограничений, что процесс контейнеризован, что нет, это такой же процесс в операционной системе, значит докер никак на это не влияет, только кудахтанье в интернете без знаний матчасти.
Tzimie
23.04.2024 06:28Сюрприз! Некоторые умные процессы (SQL server) так оптимизируют свою деятельность, чтобы threads минимизировали cross-numa interactions (numa aware). Поэтому если для них numa "mispresented", то вы получаете performance penalty
AlexGluck
23.04.2024 06:28+12Переведу вашу белиберду на более понятный язык:
Хочу донести до вашего сведения, что программное обеспечение может обладать сложной логикой, которая во время выполнения вносит улучшения (оптимизации) работы, для уменьшения частоты переключения "потоков выполнения" (threads) на другие домены NUMA.
Ввиду изменений настроек, которые скрывают от программного обеспечения реальное состояние (количество и принадлежность) NUMA, вы получите меньшую производительность.
Ввиду вашего бреда (отсутствия логической связи), напомню, что по умолчанию контейнеризация не скрывает физические параметры сервера, а так же этого не надо делать вручную для контейнеров и для baremetal инсталляций. И вы этого тоже не делаете для линукс систем, потому что там другие механизмы привязки процессов к NUMA доменам.
Предлагаю вам прекратить писать бред и изучить матчасть. Не надо доказывать каждому встречному-поперечному в интернетах, что вы правы, особенно если это не так. Я написал только чтобы другие люди видели об отличном мнении от вашего, которое я считаю технически несостоятельным.
slonpts
23.04.2024 06:28+3Благодарю от имени всех, кто не разбирается в вопросе!
Это важное дело - публично опровергать ложные утверждения
sebres
23.04.2024 06:28Если речь про что-то типа NUMA Affinity или CPU Pinning и т.п., то будет достаточно добавить --cap-add=sys_nice и/или прописать кастомный seccomp, разрешающий numactl в контейнере.
Если docker только про упаковку, и безопасность не парит совсем (равнозначный хосту контейнер), можно дать ему --privileged и забыть про привилегии совсем.
> А корректный проброс numa? С этим даже у VMware проблемы
Ну, vmware - vmware рознь...
Если это про Tanzu и ко (контейнеризация) - никогда не слышал про те "проблемы"... Можно ссыль?
Если же про VMs, то как раз у контейнеризации тут должно быть всё много лучше и проще чем у "настоящей" виртуализации, собственно по определению обеих... Ибо контейнерная технология напрямую использует физические ресурсы ядра операционной хост-системы, а не эмулирует их как виртуальные компоненты для каждого экземпляра гостевой VM.
Т.е. как изоляция, так и противоположное действие много проще там (capabilities, namespaces, control groups, mapping и вот это вот всё на уровне ядра).
Поэтому собственно контейнеры - это больше про упаковку, чем про "безопасное", полностью изолированное от хост-системы и других гостей, окружение (как оно декларируется у виртуализации).
По этой же причине совершенно нормально пускать контейнеры в гостевой VM, при том что наоборот - это скорее исключение (и имеет смысл только в узко-специфических случаях, типа легаси софта виртуализации и т.п.)
snakers4
23.04.2024 06:28Если автор бы убрал из заголовка, статью бы прочитало кратно меньше людей. Но заголовок вроде "в такой-то версии официального Docker-образа постгреса jit-флаг включен по умолчанию, что тормозит какие-то запросы" не привлечет аудиторию.
chemtech
23.04.2024 06:28+35Поправьте пожалуйста заголовок. Проблема не в docker, а в jit.
MGorkov Автор
23.04.2024 06:28ставим PG в конфигурации по умолчанию (т.е. грубо yum install postgresql15-server) - проблемы нет, ставим также докер (docker pull postgres) - проблема есть. По идее образ докера надо также делать без провайдера JIT, и дополнительный образ с JIT кому это действительно необходимо.
AlexGluck
23.04.2024 06:28+6У меня такая же нога, но болит. В конфигурации по умолчанию, которая принята в компании "Х" проблема воспроизводится. Как долго вы ошибку выжившего будете предоставлять как аргумент некорректному заголовку статьи у вас?
MerdedSpade
23.04.2024 06:28+7JIT - встроенный функционал в PostgreSQL, который включен по умолчанию и призван его ускорить. Проблема в том, что он применился там, где это было не нужно. Это проблема PostgreSQL, а не того кто его собирал, тот кто собирает не должен знать и учитывать крайние случаи когда он вредит. И более того, JIT всегда можно отключить в настройках сервера, для этого не нужно делать несколько версий образа.
Также судя по всему, PostgreSQL собирается без LLVM не во всех дистрибутивах (в Debian и некоторых других собирается с ним), следовательно его отсутствие не является стандартной конфигурацией, а лишь решением мейнтейнера пакета, который мог не включить LLVM например в целях избежания конфликтов или притягивания довольно большой зависимости.
MGorkov Автор
23.04.2024 06:28+1его отсутствие не является стандартной конфигурацией, а лишь решением мейнтейнера пакета
В этом и есть проблема, фактический набор функционала PostgreSQL зависит от мейнтейнера пакета/образа.
В пакетах и образах для Debian, Ubuntu, FreeBSD, Docker провайдер JIT включен в основной пакет/образ при установке PG-сервера и таким образом включен по умолчанию.
В Redhat/Centos, SUSE, MacOS, Windows - при установке основного пакета/образа JIT не устанавливается, т.е. выключен.
kemko
23.04.2024 06:28Тогда максимум проблема в настройках конкретного docker-образа. Так что заголовок всё ещё обманывает.
lorc
23.04.2024 06:28+7Проблема решена. Но чтобы она не повторялась, рекомендуем запускать PostgreSQL в Docker с выключенным по умолчанию JIT.
Проблема не решена, к сожалению. Такое поведение PostgreSQL должно расцениваться как ошибка. И я очень удивлен что оно попало в релиз. Надо докопаться до реальной сути проблемы. Вы не пробовали написать в mailing list на эту тему? Если это реальная бага в postgres, то её надо хотя-бы зарепортить. Либо это какая-то ваша мисконфигурация в базе/системе/где-то еще и тогда ее нужно найти и устранить.
Buzzzzer
23.04.2024 06:28+10Проблема тут только в том, что в официальный докер-образ postgres включён пакет postgresql-llvmjit, а в инструкции по развороту на хосте ничего по postgresql-llvmjit и ни про какого другого провайдера не написано. На хосте, если следовать manам, jit не работает из-за отсутствия либы, а в докере работает.
Бага в документации, имхо.lorc
23.04.2024 06:28+13Ну в любом случае это следует прояснить с разработчиками. Если бага в документации - пусть исправят документацию, если бага в докер-файле - пусть исправят докер-файл.
Но вообще, включение JIT не должно приводить к просадкам. Тем более к ТАКИМ просадкам. Как по мне - это бага где-то глубже. А наличие
postgresql-llvmjit
просто провоцирует её.И вообще, come on, если ваша компания занимается разработкой тулинга для postgres, то вы явно должно понимать внутренности postgres лучше других и репортить такие штуки разработчикам самого движка.
Hellpain
23.04.2024 06:28+12Так тут docker не причем, дело в jit. На докер и так много клевещут, поправьте заголовок пожалуйста
AnotherAnkor
23.04.2024 06:28+1Мне кажется или в документации было прямым текстом написано не запускать в docker, если надо быстродействие?
AlexGluck
23.04.2024 06:28А как контейнеризация влияет на производительность? Старые заблуждения о том что нельзя СУБД упаковывать в контейнеры ошибочно попали в документацию.
AnotherAnkor
23.04.2024 06:28У меня всегда есть сомнения, что кто-то может знать о продукте больше, чем его создатель.
AlexGluck
23.04.2024 06:28+1Если немного нигилистки подойти и сомневаться даже в создателе, то иногда можно найти баги и ошибки у создателя.
dimas
23.04.2024 06:28Образ postgres не на базе alpine был? Не пробовали воспроизвести на базе другого дистра?
rPman
Интересно, при каких условиях jit-оптимизация начнет увеличивать производительность?
MGorkov Автор
В документации пишут
по поводу tuple deforming есть интересные результаты
rPman
спасибо, по ссылке таблицы с десятком миллионов случайных записей (числа) операции sum ускорились на 30%.... скорее всего чтобы это стало заметно, вычислений нужно действительно много
Fell-x27
Интересно, а можно явно указать, чтобы конкретный запрос делался с JIT? А то есть в системе запросы весом по 20 минут... они, конечно, не рилтаймовые, они, конечно, выполняются раз 5 дней и чисто внутренние, но все же.
MGorkov Автор
Можно установить параметр на уровне сессии или транзакции, для этого соответственно выполнить SET jit=on или SET LOCAL jit=on
Fell-x27
Отлично! Спасибо большое! Интересно, даст ли прирост. В запросе много вычислений. В теории, должно отлично лечь на JIT.