Обновить

Комментарии 16

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

  • Число соединений к базе? min == max

  • Память JVM процсса? Xms == Xmx (и ещё -XX:+AlwaysPreTouch вдогонку)

Если только у вас база сама по себе не скейлится автоматически под нагрузкой, то причин начинать с маленького числа соединений в пуле — примерно ноль.

В мире микросервисов пришли новые грабли - а именно когда то было 5 pod- ов а теперь 100 pod- ов. И у каждого на базу по 20 соединений.

Да, есть PgBouncer, но начинается беда с PS.

Тут помогает только шардирование и/или репликация БД. Ну и паттерном Shared Database не стоит увлекаться.

А по поводу PgBouncer - ИМХО, это просто способ замаскировать архитектурные проблемы, пожертвовав производительностью Prepared Statements. Если нет жёсткого SLA на время отклика, то этот способ работает. А если есть - то начинаются проблемы.

PgBouncer на своей стороне умеет PS кэшировать, вместо того же hikari. Но в целом спора нет, думать про архитектуру надо смолоду

А какая разница, микросервисы или нет? Или будем держаться на надежде, что если один микросервис будет перегружен, то остальным база не будет нужна?
Тут это скорее выглядит как признание того, что в случае с микросервисами становится сложнее планировать нагрузку.

колоссальная статья

  • Пул vs. База: Размер пула не должен превышать количество ядер БД * 2. Формула «чем больше, тем лучше» не работает, больше соединений = больше контекста = медленнее.

Я бы ещё добавил рекомендацию о размере пула потоков, в котором выполняются запросы через пул соединений.

Т.к. JDBC (в отличии от R2DBC), предоставляет строго блокирующий API, то размер пула потоков должен быть равен размеру пула соединений.

Если в размер пула потоков меньше размера пула соединений, то в каждый момент времени часть соединений будет простаивать, т.к все доступные потоки заняты выполнением запросов в других соединениях. А если больше, то в каждый момент времени часть потоков будет простаивать, ожидая соединения из пула.

Логирование медленных запросов: Нужно настроить в драйвере или на уровне базы, чтобы видеть запросы, выполняющиеся дольше 100 мс.

На уровне БД - понятно. Но это не лучший способ. Сопоставлять сложно события в прикладном ПО с логом БД.

Но, не видел, что бы в драйвере Oracle или PG можно было настроить такое (warn). Можно настроить stmt.setQueryTimeout(..). Что будет порождать типа ORA-01013: user requested cancel of current operation (в PG похожее).
Но это отказ, а не warning для лога. Т.е. это предельный таймаут для отмены.

А что бы warn в логе..

  1. либо своя обертка вокруг jdbc (кстати, полезно для логирования вообще), если не используется стандартная обертка типа hibernate

  2. либо выставления "hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS=..." для hibernate

Если знаете (вдруг есть) какие то средства для того что бы на уровне jdbc драйвера выводить WARN на запросы, которые выполнились, но выполнились дольше чем указано - подскажите pls.

Копался и в Oracle jdbc и в PG исходниках jdbc.. Даже намека на такое не видел.

Бегло посмотрела, мне кажется нет напрямую. Если нужно такого вида логирование, то это всегда обёртки разного рода вокруг jdbc. А чем вас этот подход смущает? Вполне рабочий вариант. Ну и ещё если у вас spring boot аспектами можно, но опять это обёртка.

ничем не смущает. так и пользую.
Смутила фраза в тексте статьи

Нужно настроить в драйвере

В драйвере.
Подумал, что вдруг чего не знаю (хотя часто копался в исходниках PG jdbc и дебажил Oracle jdbc)

Спасибо! Отличная статья, все описанные проблемы и способы решения видела на практике.

В большинстве СУБД (PostgreSQL, Oracle и др.) PreparedStatement позволяет базе кешировать план запроса. Если вы 1000 раз вставите данные через один и тот же PreparedStatement (меняя параметры), база не будет каждый раз заново парсить запрос и строить план. Это экономит CPU на базе.

Затраты на создание плана запроса ничтожны по сравнению с самой операцией изменения/выборки данных.

План запроса зависит от данных, поэтому в реальных системах где данные часто меняются выгодней не использовать кеширование плана запросов.

И что? Статья о JDBC, что нам дает ваше замечание в этом контексте?

JDBC знать не знает о существовании планов выполнения запросов и, уже тем более о том, что сервер может уметь их кэшировать.

Но если вдруг он умеет их кэшировать, то что может являться ключем для закэшированного плана? - сам SQL-запрос, больше нечему.

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

Поэтому автор прав в утверждении "всегда используй PreparedStatement" как лучшую стратегию по умолчанию, даже с точки зрения производительности.
Я бы только добавил: если вы таки НЕ используете PreparedStatement, то должны четко понимать почему и зачем.

"всегда используй PreparedStatement" — этот совет основан на предположении, что кэшировать план запроса всегда хорошо. Это не так. В большой доле сценариев использование PreparedStatement с параметрами — будет мешать планировщику базы построить оптимальный план с учётом конкретного значения параметра. Планировщик вынужден строить план под более универсальный случай и терять в производительности.

Случаи когда binding вредит бывают, да: "bind value peeking issue" и т.п. Но говорить о том, что это "большая доля сценариев" - это крайне смело. Это, мягко говоря, не так (Если у вас есть линк на что-то внятное по теме, подтверждающие ваше мнение - дайте почитать, что-ли)

Кроме того, опять же, держимся контекста статьи:
PreparedStatement в первую очередь используют не ради перформанса: ради защита от инъекций. Именно поэтому PreparedStatement - всегда, по дефолту. То что в "большой части сценариев" ;) он еще и перформансу помогает (не намного, кстати, процентов на 10%) - приятный и полезный бонус.

Тема с Batch-ми не раскрыта до конца :) Засада: "batch size" (то самое N запесей в пакете)

Дело тут в чем: если вы захотите запихать 10000 запросов в batch, то это не значит что так оно одним блоком на сервер и уедет - неа.

Драйвер будет собирать запросы а пачки по batch-size запросов в каждой, и слать каждую пачку на сервер. И этот параметр по умолчанию очень не велик: 10 (от драйвера зависит, но всегда не особо велик).

И в JDBC API нет возможности на этот парамер влиять. Возможно у драйвера есть параметр, который можно воткнуть в connection uri (читаем доку по драйверу). А бывает и так, что это непоменять никак.

И даже если у вас есть возможность выставлять это batch size, то его точно не стоит делать очень большим. (читаем доку по драйверу :) )

Нет, сделать 1000 запросов вместо 10000 - все равно хороший выигрыш, но на практике, это почти всегда недостаточно. (Автор упомянет ускорение в 50 - 100 раз от executeBatch - я такого не видел никогда. В разы - да. Но не на 2 порядка.)

Поэтому когда речь идет о масcовом выполнении 10000+ запросов, то решение всегда уезжает за пределы JDBC: bulk insert, временные таблицы в памяти и вот это вот все, о чем упоминается в тексте заметки. И вот тут уже выгрыш на два порядка - легко. (Я видел проект где 8 часов превратилось в 70 cекунд)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS