Производительность
Транзакции и сеансы
16
Авторские права
© Postgres Professional, 2023–2026
Авторы: Алексей Береснев, Илья Баштанов, Павел Толмачев, Игорь
Гнатюк
Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
edu@postgrespro.ru
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
64-битные номера транзакций
Автономные транзакции
Данные в памяти
Встроенный пул соединений
3
64-битные транзакции
Формат данных
Заморозка
4
32-битные номера
PostgreSQL, Postgres Pro Standard
32-х разрядный xid (~ 4 млрд. транзакций)
зацикливание идентификаторов
необходимость заморозки
1
3
2
32-битные идентификаторы транзакций допускают примерно четыре
миллиарда уникальных значений. В современных реалиях при большой
нагрузке идентификаторы могут закончиться через несколько дней
и работа СУБД станет невозможна. Поэтому применяют заморозку:
версии строк с достаточно большими xmin помечаются как очень
старые и, следовательно, видимые во всех снимках данных. После
заморозки версий с определенным номером во всех таблицах кластера
номер можно использовать повторно (т.Sн. transaction wraparound).
Заморозка выполняется как одна из задач процесса очистки и активно
использует подсистему ввода-вывода. При большой нагрузке требуется
постоянно мониторить сервер и принимать оперативные меры, если
заморозка не успевает освобождать номера транзакций.
5
Postgres Pro Enterprise
64-х разрядный счетчик транзакций
не подвержены зацикливанию
64-битные номера
табличная страница
– 4 байта
база xid
поле xmin
+
=
xmin
000000c3 0fe9a1be
00000006
000000c3 0fe9a1c4
Использование 64-битного счетчика позволило бы иметь практически
бесконечное число идентификаторов транзакций, отказаться от их
повторного использования и связанных с этим действий по
администрированию.
Но простое увеличение разрядности идентификаторов привело бы
к увеличению размера заголовков версий строк и, как следствие,
к повышенному расходу дискового пространства и ресурсов ввода-
вывода при обработке данных.
Поэтому в Postgres Pro Enterprise реализован компромиссный вариант
64-х битных идентификаторов: в специальной области в конце
страницы хранится 64-битное базовое значение xid, а в полях xmin и
xmax заголовка версии строки — 32-битное смещение. 64-битный
номер транзакции xmin или xmax вычисляется сложением базового
значения и значения, хранящегося в заголовке версии строки.
Такой же формат применяется для идентификаторов
мультитранзакций: базовое значение хранится в специальной области
страницы, а смещение — в поле xmax заголовка версии строки.
6
Синхронная
если смещения не хватает и необходимо увеличить базовое значение
При очистке
уменьшает pg_xact и pg_multixact
vacuum_freeze_min_age = 50 млн
vacuum_freeze_table_age = 150 млн
autovacuum_freeze_max_age = 10 млрд
vacuum_failsafe_age = 1.6 млрд
vacuum_multixact_freeze_min_age = 5 млн
vacuum_multixact_freeze_table_age = 150 млн
autovacuum_multixact_freeze_max_age = 20 млрд
vacuum_multixact_failsafe_age = 1.6 млрд
Заморозка
Поскольку смещения в заголовке версии строки 32-битные, диапазон
значений xid в пределах страницы — от базового до базового плюс
2
32
−1. Если в какую-либо версию строки на странице нужно записать
больший номер, сервер увеличивает базовое значение xid. Если при
этом версии строк с небольшими номерами xid препятствуют такому
увеличению, сервер замораживает эти версии «на лету».
Поскольку 64-битные номера транзакций не используются повторно,
своевременная заморозка не критична для работы многоверсионности:
параметр autovacuum_freeze_max_age имеет значение по умолчанию
10 млрд вместо 200 млн в обычном PostgreSQL, и значение
autovacuum_multixact_freeze_max_age также увеличено. Однако
заморозка все же выполняется как часть процедуры очистки, чтобы
уменьшать размер CLOG (подкаталога pg_xact) и статусов
мультитранзакций (pg_multixact).
Ограничением этого решения является то, что очень долгие транзакции
(возраст которой превышает 2
32
), удерживающие снимок, будут мешать
замораживать версии строк. Поэтому, хотя необходимость в регулярной
заморозке отпадает, долгих транзакций по-прежнему стоит избегать.
В PostgresPro Enterprise 17 появилась фоновая заморозка:
дополнительный процесс выполняет очистку и заморозку недавно
измененных страниц. Ее рекомендуется включать, если автоочистка не
справляется со своими задачами.
8
Автономные транзакции
Устройство автономных транзакций
Видимость данных в автономных транзакциях
Автономные транзакции в SQL
Автономные транзакции в PL/pgSQL и PL/Python
Ограничения автономных транзакций
10
Видимость
Снимки родительской и автономных транзакций
независимы
Read Committed видит изменения
Repeatable Read, Serializable не видят изменений
Возможны взаимоблокировки
Родительская и дочерняя автономная транзакция используют
собственные снимки данных, время жизни которых зависит от уровня
изоляции. Поэтому родительская транзакция с уровнем изоляции Read
Committed будет видеть все изменения дочерних автономных
транзакций, а родительская с уровнем изоляции Repeatable Read или
Serializable не увидит никаких изменений.
Автономная транзакция может вызвать взаимоблокировку, если она
попытается получить блокировку ресурса, уже заблокированного
родительской транзакцией.
11
Ограничения
max_autonomous_transactions = 100
Вложенность до 128 уровней
Не поддерживается блокировка FOR UPDATE
Нельзя обращаться к временным схемам
Нельзя изменить уровень изоляции в PL/pgSQL и PL/Python
Не поддерживаются в расширениях
in_memory
online_analyze
Нельзя создавать слоты логической репликации
Одновременно можно запустить не более max_autonomous_transactions
автономных транзакций во всех сеансах (по умолчанию 100).
Глубина вложенности автономных транзакций — не более 128.
FOR UPDATE не поддерживается, поэтому, например, нельзя
заблокировать строку в родительской транзакции, а в дочерней
автономной пропустить ее с помощью SKIP LOCKED.
В автономной транзакции нельзя обращаться к объектам временных
схем, в частности, ко временным таблицам.
В блоке PL/pgSQL и методе PL/Python нельзя задать уровень изоляции
автономной транзакции, отличающийся от текущего.
Автономные транзакции не работают с расширениями in_memory
и online_analyze.
Также нельзя создавать слоты логической репликации из автономной
транзакции.
13
Данные в памяти
pg_variables
in_memory
14
Расширение pg_variables
Функции для работы с переменными различных типов
скалярные
массивы
коллекции записей и произвольных типов
Поддержка транзакционности
Хранение в памяти сеанса
работает на реплике
несовместимо с пулами соединений
Расширение pg_variables позволяет работать в памяти сеанса
со скалярными переменными, массивами и коллекциями. Переменные
объединяются в так называемые пакеты, выполняющие роль
пространств имен.
Коллекции представляют собой множества пар ключ—значение.
В качестве таких пар могут выступать обычные составные значения
(записи), в которых первый атрибут считается ключом. Это позволяет
использовать коллекции в качестве быстрой альтернативы временным
таблицам, которая к тому же работает и на физических репликах.
Поддерживаются как транзакционные, так и нетранзакционные
переменные. Изменения нетранзакционных переменных: создание,
изменение, удаление – необратимы. Изменения транзакционных
переменных можно отменить, откатив транзакцию полностью или до
точки сохранения. Но в любом случае переменная существует только
до конца сеанса и другим сеансам не видна.
Утилита ora2pgpro, рассмотренная в теме «Миграция», использует
pg_variables для экспорта ассоциативных массивов Oracle в коллекции.
Расширение несовместимо с пулом соединений, включая встроенный
пул Postgres Pro.
16
Расширение in_memory
Обертка сторонних данных для оперативных таблиц
таблицы организованы по индексу
данные располагаются в листовых страницах B-дерева
Поддержка транзакционности
Хранение в общей памяти сервера
работает на реплике
несовместимо со встроенным пулом соединений и подготовленными
транзакциями
С помощью расширения in_memory можно создавать небольшие
оперативные таблицы, то есть таблицы в общей памяти сервера.
Работа с таблицей происходит с помощью обертки сторонних данных.
Оперативные таблицы организованы по индексу — строки таблиц
хранятся в страницах листьев индекса B-tree, что обеспечивает
быстрый доступ к данным по первичному ключу (вторичные индексы не
поддерживаются).
Для экономии памяти используются небольшие страницы размером 1
Кбайт. Поскольку индекс B-tree должен содержать на странице минимум
три записи, максимальная длина строки ограничена 304 байтами
(TOAST не поддерживается). Когда строки удаляются из оперативной
таблицы, соответствующие страницы данных не освобождаются; для
освобождения ресурсов требуется опустошение или удаление таблицы.
Оперативные таблицы полностью поддерживают транзакции, включая
точки сохранения. Однако уровень изоляции Serializable не
поддерживается, вместо него используется Repeatable Read.
Оперативные таблицы существуют только в памяти: они не
сохраняются на диск и не журналируются, но их можно использовать на
физических репликах.
Расширение нельзя использовать с подготовленными (двухфазными)
транзакциями и при работе со встроенным пулом соединений.
18
Пул соединений
Проблема и решения
Встроенный пул
Подключение
Ограничения пула
19
Проблема и решения
Один сеанс — один обслуживающий процесс
повышенный расход ресурсов
увеличение конкуренции
прогрев локальных кешей
Пул соединений в сервере приложений или драйвере
J2EE, JDBC, ODBC, node-postgres, Psycopg2
Пул соединений как сторонний продукт
PgBouncer, Odyssey
Встроенный пул Postgres Pro Enterprise сохраняет
контекст сеанса
Простейшая архитектура, при которой для каждого клиентского
соединения запускается отдельный обслуживающий процесс, имеет
ряд недостатков, которые при большом количестве клиентов
становятся критичными. Увеличивается конкуренция при обращении
к ресурсам, растут накладные расходы на запуск процессов
(в частности, на наполнение кешей) и освобождение ресурсов при их
завершении. Кроме того, размер многих структур в общей памяти
сервера пропорционален числу клиентских соединений.
По этим причинам в нагруженные многопользовательские системы
добавляют пул соединений, позволяющий нескольким клиентам
использовать одно соединение с сервером. Как правило, для этого либо
применяют отдельное решение, такое как PgBouncer или Odyssey, либо
задействуют возможности сервера приложений или драйвера
PostgreSQL.
При таком подходе администратору приходится обслуживать
дополнительную компоненту архитектуры, а разработчику — учитывать
особенности работы приложения с пулом соединений.
Встроенный пул соединений Postgres Pro Enterprise позволяет
воспользоваться преимуществами пула, сохранив при этом
большинство возможностей, связанных с контекстом сеанса.
В 17-й версии Postgres Pro Enterprise на смену встроенному пулу
придет proxima — расширение, объединяющее функциональность
прокси-сервера и пула соединений.
20
Postgres Pro
Встроенный пул
процесс
пул
клиент
процесс
клиент
процесс
клиент процесс
session_pool_size = 0
dedicated_databases =
template0,template1,postgres
dedicated_users = postgres
клиент
процесс
пул
клиент
max_sessions = 1000
Для каждой базы данных создается отдельный пул, количество пулов
не ограничено. Если количество процессов в пуле меньше значения
session_pool_size, для нового сеанса запускается новый процесс,
а если предел достигнут, сеанс привязывается к одному из
существующих процессов.
Процесс, к которому привязано несколько сеансов, поочередно
обслуживает их транзакции, сохраняя при этом контекст каждого
сеанса. Поэтому значение session_pool_size следует задавать
достаточно большим, чтобы клиент не ждал слишком долго, пока
завершится транзакция другого соединения.
Одновременно процесс обслуживает не больше max_sessions сеансов.
Если число соединений с одной базой достигнет max_sessions ×
× session_pool_size, следующая попытка подключения вернет ошибку.
Если клиент соединяется с базой данных, входящей в список
dedicated_databases, или подключается под ролью, входящей
в dedicated_users, для него будет создан выделенный процесс, не
входящий в пулы. По умолчанию это сеансы пользователя postgres
и подключения к базам template0, template1 и postgres. Количество
выделенных процессов не ограничено.
21
Postgres Pro
Подключение
процесс
клиент
приемник
connection_pool_workers = 2
процесс
пул
клиент
клиент postmaster
session_schedule = round-robin
idle_pool_backend_timeout = 0
restart_pooler_on_reload = false
Подключение нового клиента устроено следующим образом.
Процесс postmaster слушает входящие соединения. Подключаясь,
клиент передает стартовый пакет данных, в котором указаны
параметры соединения — имя базы данных, имя роли. Postmaster
передает этот пакет одному из процессов-приемников для анализа.
Количество процессов-приемников определяется значением параметра
connection_pool_workers и должно быть достаточным, чтобы анализ
входящих соединений не стал узким местом.
Получив от приемника информацию о базе данных и роли, postmaster
направляет соединение в подходящий пул. Применяется одна из трех
стратегий, которые можно указать в параметре session_schedule:
round-robin назначает процессы по очереди, random — случайным
образом, load-balancing выбирает процесс с наименьшим числом
сеансов.
Когда все соединения, использующие какой-то процесс, завершаются,
процесс продолжает работать. Автоматическое завершение
неиспользуемого процесса по истечении определенного времени
настраивается параметром idle_pool_backend_timeout.
Для перезагрузки рабочих процессов можно установить параметр
restart_pooler_on_reload = true и перечитать конфигурацию.
22
Ограничения пула
Не поддерживаются SSL-соединения
Долгие транзакции задерживают работу других соединений
Не поддерживаются рекомендательные блокировки уровня
сеанса
Нельзя перехватывать ошибку «connection to client lost»
Не работают модули, полагающиеся на состояние сеанса
Нельзя изменять параметры в других сеансах
Остановка сервера в режиме smart работает как fast
Параметр idle_session_timeout игнорируется
Нужно учитывать следующие ограничения при использовании пула.
●
SSL-соединения в текущей реализации не поддерживаются.
●
Долгая транзакция будет задерживать работу транзакций в других
соединениях, использующих тот же процесс.
●
Ошибка «connection to client lost» обрабатывается пулом, чтобы
завершать зависшие соединения по тайм-ауту, перехватывать ее не
нужно.
●
Не работают модули, использующие состояние на уровне сеанса,
такие как plantuner, а также расширения multimaster, in_memory,
online_analyze, pg_variables.
●
Нельзя изменять параметры других сеансов.
●
Не поддерживается передача сообщений между процессами
(команды LISTEN, NOTIFY и т.Sп.).
●
Остановка сервера в режиме smart работает как fast.
●
Решения 1С не поддерживаются, они используют собственный пул
соединений.
●
Тайм-аут idle_session_timeout не работает.
24
Итоги
64-битные идентификаторы транзакций упрощают
обслуживание данных при высокой нагрузке
Автономные транзакции дают возможность удобно
и эффективно реализовать аудит
Расширения pg_variables и in_memory могут служить
альтернативой временным таблицам
Встроенный пул позволяет ограничить число соединений,
не жертвуя контекстом сеанса
25
Практика
1. Воспроизведите взаимоблокировку основной транзакции
и вложенной в нее автономной.
2. Реализуйте аудит изменений данных в таблице двумя
способами: используя расширение dblink и автономные
транзакции. Сравните производительность.
3. Убедитесь, что при использовании встроенного пула
соединений сохраняется содержимое временных таблиц
уровня сеанса.
26
Практика PPEM
1. Создайте базу данных newbase и в ней схему dblink.
2. Установите расширение dblink в одноименную схему базы
newbase.
3. Используя расширение dblink и консоль psql, выведите
список моделей самолетов из базы данных demo.
3. Получить данные по моделям самолетов можно с помощью запроса
SELECT *
FROM dblink.dblink(
'dbname=demo',
'SELECT model FROM aircrafts'
) AS (m text);