
Продолжаем цикл статей с обзором изменений 19 версии. Сегодня о январском коммитфесте 2026 года.
Самое интересное из предыдущих коммитфестов можно прочитать здесь: 2025-07, 2025-09, 2025-11.
Секционирование: слияние и разделение секций
pg_dump[all]/pg_restore: выгрузка и восстановление расширенной статистики
file_fdw: пропуск нескольких начальных строк
Логическая репликация: включение и отключение логического декодирования WAL без перезапуска сервера
Мониторинг задержек синхронизации слотов логической репликации
pg_available_extensions показывает каталог установки расширения
Новая функция pg_get_multixact_stats: статистика использования мультитранзакций
Улучшения в мониторинге выполнения очистки и анализа
Очистка: информация об использовании памяти
vacuumdb --dry-run
Оптимизация jsonb_agg
Оптимизация LISTEN/NOTIFY
ICU: оптимизация функций преобразования символов
Параметр standard_conforming_strings больше нельзя отключить
Секционирование: слияние и разделение секций
commit: f2e4cc42795, 4b3d173629f
Первая и неудачная попытка реализации команд для слияния и разделения секций секционированной таблицы была в 17-й версии: незадолго до выпуска патчи были отменены. Сейчас вторая попытка, хоть и с серьезными ограничениями.
Предположим, что секционированная таблица бронирований авиабилетов разбита на секции по месяцам. Сейчас в ней три секции за последние три месяца прошлого года:
SELECT tableoid::regclass AS partition, count(*) FROM bookings_range GROUP BY ALL;
partition | count -----------+-------- p2025_10 | 434287 p2025_11 | 410680 p2025_12 | 411025 (3 rows)
Если данные за прошлый год используются нечасто, их можно схлопнуть в одну секцию:
ALTER TABLE bookings_range MERGE PARTITIONS ( p2025_10, p2025_11, p2025_12 ) INTO p2025; SELECT tableoid::regclass AS partition, count(*) FROM bookings_range GROUP BY ALL;
partition | count -----------+--------- p2025 | 1255992 (1 row)
К разбиению по месяцам можно вернуться обратной командой:
ALTER TABLE bookings_range SPLIT PARTITION p2025 INTO ( PARTITION p2025_10 FOR VALUES FROM ('2025-10-01') TO ('2025-11-01'), PARTITION p2025_11 FOR VALUES FROM ('2025-11-01') TO ('2025-12-01'), PARTITION p2025_12 FOR VALUES FROM ('2025-12-01') TO ('2026-01-01') ); SELECT tableoid::regclass AS partition, count(*) FROM bookings_range GROUP BY ALL;
partition | count -----------+-------- p2025_10 | 434287 p2025_11 | 410680 p2025_12 | 411025 (3 rows)
Обе команды получают исключительную блокировку родительской таблицы на все время работы. Поэтому их не следует выполнять в нагруженных системах. Но в определенных ситуациях они могут быть полезны. К тому же наличие команд — это хорошая база для последующих оптимизаций: как по ослаблению уровня блокировок, так и по параллельному выполнению.
pg_dump[all]/pg_restore: выгрузка и восстановление расширенной статистики
commit: d756fa1019f, 0e80f3f88de, c32fb29e979, 302879bd68d, efbebb4e858
В 18-й версии pg_dump и pg_dumpall научились выгружать базовую статистику таблиц и индексов. А psql и pg_restore — ее восстанавливать. Теперь пришел черед всех видов расширенной статистики, включая статистику по выражениям.
В следующем примере создаются три вида расширенной статистики для таблицы:
CREATE TABLE test AS SELECT 1 AS id, 'a' AS descr; CREATE STATISTICS test_stats (ndistinct, dependencies, mcv) ON id, descr FROM test; ANALYZE test;
Расширенная статистика выгружается при помощи новой функции pg_restore_extended_stats, вызов которой можно найти в выводе pg_dump:
$ pg_dump --statistics-only| grep -A 12 pg_restore_extended_stats
SELECT * FROM pg_catalog.pg_restore_extended_stats( 'version', '190000'::integer, 'schemaname', 'public', 'relname', 'test', 'statistics_schemaname', 'public', 'statistics_name', 'test_stats', 'inherited', 'f'::boolean, 'n_distinct', '[{"attributes": [1, 2], "ndistinct": 1}]'::pg_ndistinct, 'dependencies', '[{"attributes": [1], "dependency": 2, "degree": 1.000000}, {"attributes": [2], "dependency": 1, "degree": 1.000000}]'::pg_dependencies, 'most_common_vals', '{{1,a}}'::text[], 'most_common_freqs', '{1}'::double precision[], 'most_common_base_freqs', '{1}'::double precision[] );
file_fdw: пропуск нескольких начальных строк
commit: 26cb14aea12
В статье об июльском коммитфесте 19-й версии говорилось о том, что при загрузке строк в таблицу командой COPY можно указать, сколько начальных строк пропустить, прежде чем начнутся данные. Теперь такая же возможность появилась в file_fdw.
Логическая репликация: включение и отключение логического декодирования WAL без перезапуска сервера
commit: 67c20979ce7
Для работы логической репликации требуется, чтобы в WAL записывалась информация, необходимая для логического декодирования. При значении параметра wal_level по умолчанию (replica) эта информация не пишется. А изменение параметра требует перезапуска сервера.
Теперь уровень записи в WAL будет определяться динамически, в зависимости от наличия слотов логической репликации. Как только появляется первый слот, фактический уровень журнала повышается до logical. И наоборот, после удаления последнего слота фактический уровень понизится до replica. Текущее значение фактического уровня журнала показывает новый параметр effective_wal_level, доступный только для чтения.
\dconfig+ *wal_level
List of configuration parameters Parameter | Value | Type | Context | Access privileges ---------------------+---------+------+------------+---------------------- effective_wal_level | replica | enum | internal | wal_level | replica | enum | postmaster | postgres=sA/postgres+ | | | | alice=sA/postgres (2 rows)
Создаем слот логической репликации, фактический уровень журнала повышается:
SELECT pg_create_logical_replication_slot('slot', 'pgoutput');
pg_create_logical_replication_slot ------------------------------------ (slot,2/4BB9C340) (1 row)
\dconfig *wal_level
List of configuration parameters Parameter | Value ---------------------+--------- effective_wal_level | logical wal_level | replica (2 rows)
Удаляем единственный слот — и ближайшая контрольная точка снизит фактический уровень журнала:
SELECT pg_drop_replication_slot('slot'); CHECKPOINT; \dconfig *wal_level
List of configuration parameters Parameter | Value ---------------------+--------- effective_wal_level | replica wal_level | replica (2 rows)
Мониторинг задержек синхронизации слотов логической репликации
commit: 76b78721ca, e68b6adad96, 5db6a344abc
Если ожидаемая синхронизация слотов логической репликации не срабатывает, хочется понять причину задержки. Для этой цели в представление pg_replication_slots добавлен столбец slotsync_skip_reason.
А в представлении pg_stat_replication_slots появились два новых столбца slotsync_skip_count и slotsync_last_skip, показывающие, сколько раз синхронизация слотов была пропущена и когда это случилось в последний раз.
pg_available_extensions показывает каталог установки расширения
commit: f3c9e341cdf
В представления pg_available_extensions и pg_available_extension_versions добавлен столбец location, показывающий каталог, где установлено расширение.
SELECT name, location FROM pg_available_extensions LIMIT 5;
name | location ------------------+---------- pg_surgery | $system dict_int | $system plpython3u | $system seg | $system ltree_plpython3u | $system (5 rows)
Значение $system — это каталог по умолчанию для системы.
Новая функция pg_get_multixact_stats: статистика использования мультитранзакций
commit: 97b101776ce
Заблокируем одну и ту же строку в режиме KEY SHARE в двух сеансах:
1=# BEGIN; 1=*# SELECT * FROM bookings WHERE book_ref = '2EW1SQ' FOR KEY SHARE;
book_ref | book_date | total_amount ----------+-------------------------------+-------------- 2EW1SQ | 2025-09-01 03:00:12.557744+03 | 8125.00 (1 row)
2=# BEGIN; 2=*# SELECT * FROM bookings WHERE book_ref = '2EW1SQ' FOR KEY SHARE;
book_ref | book_date | total_amount ----------+-------------------------------+-------------- 2EW1SQ | 2025-09-01 03:00:12.557744+03 | 8125.00 (1 row)
Две транзакции в этих сеансах используют мультитранзакцию для удержания разделяемой блокировки на строку. Функция pg_get_multixact_stats покажет агрегированную статистику использования мультитранзакций:
SELECT * FROM pg_get_multixact_stats() \gx
-[ RECORD 1 ]----+-- num_mxids | 1 num_members | 2 members_size | 0 oldest_multixact | 1
Эта статистика включает:
общее количество мультитранзакций, присутствующих сейчас в системе;
общее количество членов (транзакций) этих мультиранзакций;
объем памяти (в байтах), требуемый для хранения информации в каталоге pg_multixact/members;
самый старый номер используемой сейчас мультитранзакции.
Улучшения в мониторинге выполнения очистки и анализа
commit: 0d789520619, ab40db3852d
В представление pg_stat_progress_vacuum добавлены два столбца:
modeс возможными значениямиnormal,aggressiveиfailsafe;started_by—manual,autovacuumиautovacuum_wraparound.
Столбец started_by появился и в представлении pg_stat_progress_analyze. Здесь у него два варианта значения: manual и autovacuum.
Очистка: информация об использовании памяти
commit: 736f754eed0
Команда VACUUM с параметром verbose выводит новую строку (memory usage) с информацией о том, сколько всего памяти потребовалось для хранения идентификаторов мертвых строк, сколько раз (resets) список идентификаторов не удалось поместить в отведенный лимит памяти (maintenance_work_mem).
VACUUM(verbose) bookings;
INFO: vacuuming "demo.bookings.bookings" … memory usage: dead item storage 0.02 MB accumulated across 0 resets (limit 64.00 MB each) … VACUUM
Эта же строка появится в журнале сервера, если включено журналирование автоочистки.
Стоит отметить, в 17-й версии для хранения списка идентификаторов мертвых строк процесс очистки стал использовать сжатое префиксное дерево (radix tree) вместо обычного массива. Благодаря этой прекрасной оптимизации получить значение resets больше 0 стало очень сложно — даже для целей тестирования. 🙂
vacuumdb --dry-run
commit: d107176d27c
По аналогии с некоторыми утилитами сервера, vacuumdb получила параметр --dry-run. С этим параметром утилита покажет, какие команды очистки и анализа предполагается отправить на сервер.
Создадим таблицу в схеме public и посмотрим, что vacuumdb посчитает нужным выполнить для этой схемы:
$ psql -d postgres -c 'CREATE TABLE public.test (id int)'
$ vacuumdb -d postgres -n public --dry-run
vacuumdb: Executing in dry-run mode. No commands will be sent to the server. vacuumdb: vacuuming database "postgres" VACUUM (SKIP_DATABASE_STATS) public.test; VACUUM (ONLY_DATABASE_STATS);
Оптимизация jsonb_agg
commit: 0986e95161c, b61aa76e458, 005a2907dc3
Функция jsonb_agg стала значительно быстрее. Особенно при работе с типами integer и numeric. Простой замер времени выполнения в 18-й и 19-й версиях:
18=# \timing 18=# SELECT jsonb_agg(g.x) FROM generate_series(1.0,1000000) AS g(x)\g /dev/null
Time: 677,667 ms
В 19-й версии:
19=# SELECT jsonb_agg(g.x) FROM generate_series(1.0,1000000) AS g(x)\g /dev/null
Time: 374,138 ms
Речь об ускорении именно jsonb_agg, просто json_agg и без этой оптимизации работает примерно с такой же скоростью.
Оптимизация LISTEN/NOTIFY
commit: 282b1cde9de
Механизм LISTEN/NOTIFY хорошо работает в ситуации, когда несколько получателей отслеживают уведомления из одного канала. Но производительность механизма страдает, если разные получатели ждут уведомлений из разных каналов. Процесс NOTIFY пытается доставить уведомление получателям всех каналов, а не только тем, кто подписался на канал.
В 19-й версии механизм LISTEN/NOTIFY поддерживает хеш-таблицу каналов и получателей, что позволило в разы увеличить производительность отправки уведомлений.
ICU: оптимизация функций преобразования символов
commit: c4ff35f1044
В базах данных с кодировкой UTF8 оптимизированы функции преобразования регистра символов с правилами сортировки ICU.
18=# SELECT count(upper(passenger_name COLLATE "ru-x-icu")) FROM tickets;
count ---------- 10836563 (1 row) Time: 3107,008 ms (00:03,107)
В 19-й версии этот же запрос выполняется существенно быстрее:
19=# SELECT count(upper(passenger_name COLLATE "ru-x-icu")) FROM tickets;
count ---------- 10836563 (1 row) Time: 1815,985 ms (00:01,816)
Параметр standard_conforming_strings больше нельзя отключить
commit: 45762084545
Параметр standard_conforming_strings больше нельзя отключить. А связанный параметр escape_string_warning удален.
Поведение в 18-й версии:
SET standard_conforming_strings = off; SET escape_string_warning = off; SELECT 'A\nB';
?column? ---------- A + B (1 row)
Т.е. в строке с одинарными кавычками символ \ воспринимается как специальный.
В 19-й версии такого поведения больше не будет:
SET standard_conforming_strings = off;
ERROR: non-standard string literals are not supported
SET escape_string_warning = off;
ERROR: unrecognized configuration parameter "escape_string_warning"
SELECT 'A\nB';
?column? ---------- A\nB (1 row)
А для использования специальных символов перед строковой константой нужно ставить E:
SELECT E'A\nB';
?column? ---------- A + B (1 row)
Можно было бы спокойно пройти мимо этого патча, если бы не 1C. Для работы этой популярной в России (и не только) системы рекомендуется отключить standard_conforming_strings. Вероятно для перехода на 19-ю версию разработчикам 1С и СУБД на основе PostgreSQL придется протестировать работу со строками с учетом этих изменений.
На этом пока всё. Впереди события пятого мартовского коммитфеста 19-й версии.
