PostgreSQL 14: Часть 3 или «ноябрьское затишье» (Коммитфест 2020-11)

?v=1

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

Громкими киллер-фичами нас не побаловали, надеемся разработчики припрятали их на последние два коммитфеста в январе и марте следующего года. Тем не менее, рассказать есть о чем. Например разберемся с такими вопросами:

  • Не пора ли увеличивать wal_buffers?
  • Можно ли перегружать хранимые подпрограммы по OUT-параметрам?
  • По умолчанию pg_stat_statements собирает данные о 5000 запросов. Как понять много это или мало?
  • Что будет, если в операционной системе обновится библиотека libc?


Документация


Документирование установки пакетных сборок
commit: 5b36221c

Сделан еще один шаг к документированию пакетных сборок. В предыдущей статье серии уже говорились про изменения в главе об управлении сервером. Новая глава документации Installation from Binaries не может похвастаться подробным описанием. В ней всего четыре предложения. Но она официально отсылает к разделу Download, где находятся подробные описания установки для поддерживаемых платформ.

Мониторинг


Добавлена информация о странице, где произошла ошибка в процессе восстановления
commit: 9d0bd95f

В текст сообщения об ошибке при восстановлении добавлена информация о том, в каком файле и на какой странице (блоке) эта ошибка произошла. Будет полезным, например, при диагностике проблем с применением WAL-записей на физической реплике, без использования pg_waldump.

Новое представление pg_stat_wal
commit: 8d9a9359, 01469241

Всем любителям статистики! Новое представление для мониторинга и настройки WAL-буфера ― pg_stat_wal. Например, столбец wal_buffers_full показывает сколько раз данные сбрасывались на диск из-за переполнения буфера. На основе этой информации можно принимать решение об изменении wal_buffers.

Новое представление pg_stat_statements_info
commit: 9fbc3f31

Количество отслеживаемых команд в pg_stat_statements задается параметром max и по умолчанию равно 5000. Много это или мало? А если мало, то насколько? Как правильно определить размер?

Для ответов на эти вопросы нас вооружили представлением pg_stat_statements_info. Пока в нем всего один столбец dealloc и он показывает сколько раз значение max было превышено и наименее ресурсоемкие запросы отбрасывались. Большое значение этого счетчика — сигнал для увеличения pg_stat_statements.max.

pg_stat_statements: отслеживание количества обработанных строк для REFRESH MATERIALIZED VIEW
commit: b62e6056

В статье об июльском коммитфесте уже писалось о том, что в pg_stat_statements добавили поддержку счетчика обработанных записей для CREATE TABLE AS, SELECT INTO, CREATE MATERIALIZED VIEW, FETCH. Теперь к этим командам добавилась REFRESH MATERIALIZED VIEW.

Производительность


Ускорение проверки на соответствие строки определенной форме нормализации Unicode и самой нормализации строк Unicode
commit: 2a731645, 80f8eb79, 783f0cc6

Ускорение достигнуто за счет более оптимальной генерации хеш-функций (первый патч). В результате (второй коммит) выражение IS [form] NORMALIZED стало работать быстрее, а сама нормализация (функция normalize) ускорена в несколько раз (третий коммит).

Подсказка компилятору о низкой вероятности обработки ошибок
commit: 913ec71d

Суть оптимизации в подсказке компилятору о том, что ветки кода с вызовами elog (ERROR) или ereport (ERROR) выполняются с меньшей вероятностью, чем остальные.

Параллельное выполнение CREATE INDEX CONCURRENTLY
commit: c98763bf

Команда CREATE INDEX CONCURRENTLY, для неблокирующего создания индекса, сама блокируется другими командами CREATE INDEX CONCURRENTLY. В результате создание таким способом большого индекса заблокирует создание индексов меньшего размера.

Теперь команды CREATE INDEX CONCURRENTLY не будут блокировать друг друга, если они строят индекс только по своей таблице и данные других таблиц им не нужны. Проще говоря, ждать придется только неблокирующего создания индексов по выражениям и частичных индексов (с выражением WHERE), поскольку потенциально они могут содержать вызовы пользовательских функций, обращающихся к другим таблицам.

На очереди сделать аналогичный патч для REINDEX CONCURRENTLY.

Кеширование результата конвертации now () во внутренние структуры памяти
commit: 0a87ddff

Если в одной транзакции выполняется много вызовов таких функций как CURRENT_DATE, CURRENT_TIME, LOCALTIME, то они будут использовать кешированное значение. От этого выиграют транзакции, загружающие большое количество строк командой COPY в таблицу, где столбцы имеют такие функции в качестве значений по умолчанию.

Репликация


Запись в журнал сервера о проблемах выполнения restore_command
commit: a4ef0329

Команда restore_command на реплике может выполниться успешно, но по каким-то причинам не восстановить WAL-файл из архива. Последующее восстановление завершится не очень информативной ошибкой.

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

pg_rewind: использование реплики в качестве исходного сервера
commit: 9c4f5192

В качестве исходного сервера pg_rewind теперь сможет использовать реплику. Раньше это было невозможно из-за того, что утилита создавала временную таблицу на исходном сервере. Теперь код переписан, временные таблицы больше не нужны.

Логическая репликация: детализация сообщения об ошибке применения записей на подписчике
commit: f0770709

Если в таблице на подписчике не хватает столбцов, то в сообщении об ошибке отсутствующие столбцы будут перечислены.

Сервер


Отслеживание изменения версий правил сортировки у индексов
commit: 7d1297df, cd6f479e, 257836a7

У правил сортировки ICU был механизм отслеживания версий. При создании правила сортировки ICU номер версии сохранялся в pg_collation.collversion. Выполняя запрос к любому объекту, использующему это правило сортировки, сначала проверялось соответствие версий в pg_collation и в операционное системе. Если отличаются ― выдавалось предупреждение о том, что нужно пересоздать все объекты с этим правилом сортировки. А затем можно было выполнить ALTER COLLATION… REFRESH VERSION для обновления версии в системном каталоге.

Больше такого механизма нет. А в pg_collation больше нет столбца collversion (первый коммит). И на то были причины. В таком виде механизм нельзя использовать для правила сортировки по умолчанию. А это один из барьеров для использования правил сортировки ICU при инициализации кластера или создании базы данных. Кроме того, какие-то объекты (индексы) могут быть созданы уже после расхождения версий. И нет возможности отследить какие именно объекты были созданы со старой версией (требуют перестройки), а какие с новой (можно оставить).

Новое место для хранения версии ― pg_depend.refobjversion (второй коммит). Именно здесь будет храниться версия правила сортировки (третий коммит).

Вот как работает новый механизм. Создаем таблицу и индекс:

CREATE TABLE t (col text COLLATE "ru-x-icu");
CREATE INDEX t_ind ON t(col);


Теперь индекс зависит от правила сортировки ru-x-icu и конкретно от версии 153.80.32.1:

SELECT (SELECT pc.collname FROM pg_collation pc
        WHERE pc.oid = pd.refobjid
       ) AS refobj
       ,refobjversion
FROM pg_depend pd
WHERE classid = 'pg_class'::regclass
AND objid = 't_ind'::regclass
AND refclassid = 'pg_collation'::regclass;
  refobj  | refobjversion
----------+---------------
 ru-x-icu | 153.80.32.1


Если в библиотеке ICU изменится версия этого правила, то при обращении к индексу t_ind будет выдаваться предупреждение о необходимости его перестройки. Что и нужно сделать командой REINDEX, которая заодно обновит версию в pg_depend. Просто обновить версию можно и командой ALTER INDEX… ALTER COLLATION… REFRESH VERSION, но при уверенности, что индекс перестраивать не нужно.

Хоть и с оговорками, но механизм может работать не только для правил сортировки ICU, но и для libc!

А что если для индекса используется правило сортировки по умолчанию. Будет ли отслеживаться его версия?

ALTER TABLE t ADD col_default text;
CREATE INDEX t_ind_default ON t(col_default);

SELECT (SELECT pc.collname FROM pg_collation pc
        WHERE pc.oid = pd.refobjid
       ) AS refobj
       ,refobjversion
FROM pg_depend pd
WHERE classid = 'pg_class'::regclass
AND objid = 't_ind_default'::regclass
AND refclassid = 'pg_collation'::regclass;
 refobj  | refobjversion
---------+---------------
 default | 2.27


Да, будет. С теми же оговорками для libc.

postgres_fdw: автоматическое повторное подключение, если сеанс на внешнем сервере больше недоступен
commit: 32a9c0bd

Используемый в postgres_fdw сеанс на внешнем сервере может оказаться недоступным. Например он мог аварийно завершиться или внешний сервер был перезагружен. Попытка выполнить запрос к внешней таблице в таком случае завершается ошибкой.

Теперь будет предприниматься попытка создать новое подключение и всё-таки выполнить запрос.

Воспроизведем ситуацию. Для упрощения, внешняя таблица расположена в другой базе данных кластера.

CREATE EXTENSION postgres_fdw;
CREATE SERVER demo_srv FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (dbname 'demo');

CREATE USER MAPPING FOR postgres SERVER demo_srv
OPTIONS (user 'postgres', password 'postgres');

IMPORT FOREIGN SCHEMA bookings
    LIMIT TO (airports)
    FROM SERVER demo_srv
    INTO public;


Внешняя таблица создана, к ней можно обращаться:

SELECT count(*) FROM airports;
 count
-------
   104


Найдем обслуживающий внешний процесс и прервем его:

SELECT datname, pid  FROM pg_stat_activity WHERE application_name = 'postgres_fdw';
 datname | pid  
---------+------
 demo    | 8943
(1 row)
SELECT pg_terminate_backend(8943);
 pg_terminate_backend
----------------------
 t


Повторяем запрос … и получаем данные, как ни в чем не бывало:

SELECT count(*) FROM airports;
 count
-------
   104


Теперь нас обслуживает другой процесс:

SELECT datname, pid  FROM pg_stat_activity WHERE application_name = 'postgres_fdw';
 datname | pid  
---------+------
 demo    | 8966


Операторы |>> и <<| для типа point
commit: 0cc99327

В документации к геометрическим функциям и операторам по 13 версии находим описание для оператора |>>

Первый объект строго выше второго? Имеется для типов box, polygon, circle

И аналогичное для <<| (строго ниже).

А вот для точек (тип point) эти операторы не поддерживаются, хотя есть похожие: >^ и <^.

В результате доработки появилась поддержка |>> и <<| для точек:

SELECT '(0,1)'::point |>> '(0,0)'::point;
 ?column?
----------
 t


Операторы >^ и 

pg_hba.conf: пересмотр параметра clientcert
commit: 253f1025

Общий параметр аутентификации clientcert теперь поддерживает строковые значения verify-ca и verify-full вместо предыдущих 1 и 0.

Также больше не поддерживается значение no-verify, т.к. это то же самое как и просто не задавать значение параметру clientcert.

Хеш-функции для составных типов
commit: 01e658fa

Новые хеш-функции для типа record:

\df hash_record*
                                 List of functions
   Schema   |         Name         | Result data type | Argument data types | Type
------------+----------------------+------------------+---------------------+------
 pg_catalog | hash_record          | integer          | record              | func
 pg_catalog | hash_record_extended | bigint           | record, bigint      | func


Планы запросов, использующие хеширование, смогут применяться для операций с составными типами UNION/INTERSECT/EXCEPT DISTINCT, рекурсивных запросов с UNION DISTINCT, соединений хешированием и операций с секционированными по хешу таблицами.

pg_trgm: поддержка оператора =
commit: 935f6666

Индекс созданный для класса оператора gin_trgm_ops или gist_trgm_ops используется для запросов с условием

column LIKE 'строка'


Но не используется для равнозначного условия

column = 'строка' 


Поддержку оператора равенства добавили в pg_trgm.

Команды SQL


CREATE [OR REPLACE] TRIGGER
commit: 92bf7e2d

Командой CREATE TRIGGER теперь можно не только создать новый, но и изменить определение существующего триггера, добавив OR REPLACE. Заменить можно всё, кроме имени триггера и таблицы, для которой триггер срабатывает.

OUT-параметры в процедурах
commit: 2453ea14

Параметры INOUT поддерживаются с момента появления процедур в версии 11, однако реализация OUT-параметров была отложена. Теперь закрыт и этот пробел.

CREATE PROCEDURE multiply (a int, b int, OUT x int) AS $$
BEGIN
    x := a * b;
END;
$$ LANGUAGE plpgsql;


В отличие от функций, OUT-параметры являются частью сигнатуры процедуры. Поэтому их нужно обязательно указывать при вызове. Если такая процедура вызывается напрямую оператором CALL, то в качестве значения можно указать NULL:

CALL multiply(2,2,NULL);
 x
---
 4


Во вложенном вызове, например из анонимного блока или другой подпрограммы, нужно использовать переменную:

DO $$
DECLARE
    v int;
BEGIN
    CALL multiply(5,5,v);
    RAISE NOTICE '%', v;
END;$$;
NOTICE:  25
DO


Поиск элемента от конца строки в функции split_part
commit: ec0294fb

Функция split_part теперь понимает, что если номер возвращаемого элемента меньше нуля, то искать нужно с конца строки:

SELECT split_part('1:2:3', ':', -1);
 split_part
------------
 3


Системное администрирование


pg_upgrade --check: добавлена проверка на существование каталогов для табличных пространств
commit: 3c0471b5

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

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

Изменение портировали в предыдущие версии, по 9.5 включительно.

pg_upgrade: скрипт analyze_new_cluster больше не создается
commit: 8f113698

После завершения обновления создавался скрипт analyze_new_cluster, содержащий всего одну команду для скорейшего сбора статистики в несколько проходов:

vacuumdb --all --analyze-in-stages


Больше этого скрипта не будет, а вместо рекомендации запустить analyze_new_cluster будет рекомендация выполнить vacuumdb.

Поддержка абстрактных Unix-сокетов
commit: c9f0624b

Абстрактные сокеты вместо файловой системы используют «абстрактное» пространство имен. Имена таких сокетов начинаются с @.

Поддержка добавлена для Linux и Windows.

Разное


Тип данных для значений по умолчанию в функциях LEAD/LAG
commit: 5c292e6b, 9e38c2bb

Оконные функции LEAD и LAG могут принимать третий аргумент ― значение по умолчанию. И это значение должно быть такого же типа как и первый аргумент с данными (anyelement):

\df lag
                                List of functions
   Schema   | Name | Result data type |       Argument data types       |  Type  
------------+------+------------------+---------------------------------+--------
 pg_catalog | lag  | anyelement       | anyelement                      | window
 pg_catalog | lag  | anyelement       | anyelement, integer             | window
 pg_catalog | lag  | anyelement       | anyelement, integer, anyelement | window


В некоторых случаях такая строгость типа для значения по умолчанию не очевидна:

SELECT amount, LAG(amount, 1, 0) OVER (ORDER BY amount)
FROM ticket_flights
WHERE ticket_no = '0005432659770'
ORDER BY amount;
ERROR:  function lag(numeric, integer, integer) does not exist
...


Если сделать явное приведение 0 к типу numeric, а столбец amount имеет тип numeric, то запрос будет работать. Однако стандарт SQL предполагает, что значение по умолчанию может быть другого типа, но приводимого к типу первого аргумента. Столкнувшись с таким поведением, Маркус Винанд в таблицу совместимости оконных функций стандарту SQL добавил сноску для PostgreSQL о том, что в этой СУБД значения по умолчанию не поддерживаются вообще.

В 14 версии у функций LEAD и LAG заменили тип данных первого и третьего аргумента на появившийся в 13 версии anycompatible. Явное преобразование типа больше не требуется:

SELECT amount, LAG(amount, 1, 0) OVER (ORDER BY amount)
FROM ticket_flights
WHERE ticket_no = '0005432659770'
ORDER BY amount;
  amount   |   lag    
-----------+----------
   5900.00 |        0
  18000.00 |  5900.00
 199300.00 | 18000.00
(3 rows)


Более того, подобную замену типа с anyelement на anycompatible сделали для многих функций, работающих с массивами (второй коммит). Например теперь можно добавлять в массив совместимые по типу элементы:

SELECT array_append(ARRAY[1.0, 2.0], 0);
 array_append
--------------
 {1.0,2.0,0}


Новый модуль contrib: old_snapshot
commit: aecf5ee2

Вред от долгих транзакций известен: снимок данных долгой транзакции не дает вычищать старые версии строк, что приводит к чрезмерному разрастанию таблиц и индексов.

У DBA есть два инструмента «борьбы» с долгими транзакциями. Кроме параметра idle_in_transaction_session_timeout есть еще old_snapshot_threshold.

В old_snapshot_threshold задается интервал времени, в течение которого нужные снимку версии строк не будут очищаться. После этого очистка имеет право удалить эти версии строк, а транзакция при обращении к ним получит ошибку «snapshot too old».

Для реализации такой схемы работы нужна связь между изменяющимися на единицу номерами транзакций и временем. Новый модуль old_snapshot позволяет эту связь увидеть.

Установим old_snapshot_threshold в один час:

ALTER SYSTEM SET old_snapshot_threshold = '1h';


Перезагружаем сервер для применения изменений и создаем расширение:

CREATE EXTENSION old_snapshot;


Запускаем тест pgbench на 5 минут с интенсивностью 10 транзакций в секунду:

pgbench -T 300 -R 10


В расширении одна функция ― pg_old_snapshot_time_mapping, данные которой обновляются раз в минуту. Через несколько минут смотрим:

SELECT * FROM pg_old_snapshot_time_mapping();
 array_offset |     end_timestamp      | newest_xmin
--------------+------------------------+-------------
            0 | 2020-12-14 09:01:00+03 |        3341
            1 | 2020-12-14 09:02:00+03 |        3343
            2 | 2020-12-14 09:03:00+03 |        3874
            3 | 2020-12-14 09:04:00+03 |        4494
            4 | 2020-12-14 09:05:00+03 |        5101
            5 | 2020-12-14 09:06:00+03 |        5706
            6 | 2020-12-14 09:07:00+03 |        6293


Как видим, с каждой минутой (end_timestamp) значение newest_xmin продвигается примерно на 600 транзакций.
На этом пока всё. Продолжение следует после январского коммитфеста.

© Habrahabr.ru