PostgreSQL 17: Часть 2 или Коммитфест 2023-09

от автора


Продолжаем следить за новинками 17-й версии PostgreSQL. На этот раз поговорим о том, чем запомнился сентябрьский коммитфест.

Самое интересное об июльском коммитфесте ― в предыдущей статье серии: 2023-07.

Удален параметр old_snapshot_threshold
Новый параметр event_triggers
Новые функции to_bin и to_oct
Новое системное представление pg_wait_events
EXPLAIN: счетчик времени на JIT-компиляцию преобразования кортежей
Планировщик: уточнение оценки начальной стоимости узла WindowAgg
pg_constraint: ограничения NOT NULL
Нормализация CALL, DEALLOCATE и команд управления двухфазной фиксацией
unaccent: целевое выражение правил поддерживает значения в кавычках
COPY FROM: FORCE_NOT_NULL и FORCE_NULL
Аудит подключений без аутентификации
pg_stat_subscription: новый столбец worker_type
Функция pg_promote при неуспешном переключении на реплику
Выбор метода синхронизации с диском в утилитах сервера
pg_restore: оптимизация восстановления в несколько потоков большого количества таблиц
pg_basebackup и pg_receivewal с параметром dbname
Имена параметров для ряда встроенных функций
psql: \watch min_rows

Удален параметр old_snapshot_threshold
commit: f691f5b8

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

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

Новый параметр event_triggers
commit: 7750fefd

Параметр event_triggers включает/отключает триггеры событий.

\dconfig+ event_triggers

               List of configuration parameters    Parameter    | Value | Type |  Context  | Access privileges ----------------+-------+------+-----------+-------------------  event_triggers | on    | bool | superuser | (1 row)

Предназначен в первую очередь для отладки триггеров. Ранее в документации к команде CREATE EVENT TRIGGER предлагалось запускать сервер в однопользовательском режиме, если ошибочно работающий триггер не удается удалить.

Новые функции to_bin и to_oct
commit: 260a1f18

К уже существующей функции to_hex для шестнадцатеричных чисел добавлены функции для перевода в двоичную и восьмеричную системы счисления:

SELECT to_bin(2), to_oct(8), to_hex(16);

 to_bin | to_oct | to_hex --------+--------+--------  10     | 10     | 10 (1 row)

См. также
Waiting for PostgreSQL 17 – Add to_bin() and to_oct(). – select * from depesz;

Новое системное представление pg_wait_events
commit: 1e68e43d

В документации к представлению pg_stat_activity для описания столбцов wait_event_type и wait_event прилагаются таблица с описанием типов событий ожидания и несколько таблиц с описанием событий ожидания каждого типа. Эти же описания теперь можно получить запросом к новому системному представлению pg_wait_events.

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

SELECT a.pid, a.state, a.wait_event_type, a.wait_event, w.description FROM pg_stat_activity a JOIN pg_wait_events w      ON (a.wait_event_type = w.type AND a.wait_event = w.name) WHERE a.backend_type = 'checkpointer'\gx

-[ RECORD 1 ]---+--------------------------------------------- pid             | 21090 state           | wait_event_type | Activity wait_event      | CheckpointerMain description     | Waiting in main loop of checkpointer process

EXPLAIN: счетчик времени на JIT-компиляцию преобразования кортежей
commit: 5a3423ad

В выводе EXPLAIN ANALYZE, а также в pg_stat_statements, отдельно учитывается время, затраченное на JIT-компиляцию преобразования кортежей (tuple deforming).

В следующем запросе JIT-компиляция используется для вычисления числа ?:

EXPLAIN (analyze, summary off) WITH pi AS (   SELECT random() x, random() y   FROM generate_series(1,10000000) ) SELECT 4*sum(1-floor(x*x+y*y))/count(*) val FROM pi;

                                                                  QUERY PLAN                                                                    -----------------------------------------------------------------------------------------------------------------------------------------------  Aggregate  (cost=525000.00..525000.02 rows=1 width=8) (actual time=3934.330..3934.331 rows=1 loops=1)    CTE pi      ->  Function Scan on generate_series  (cost=0.00..150000.00 rows=10000000 width=16) (actual time=725.126..1654.054 rows=10000000 loops=1)    ->  CTE Scan on pi  (cost=0.00..200000.00 rows=10000000 width=16) (actual time=725.129..3429.925 rows=10000000 loops=1)  JIT:    Functions: 6    Options: Inlining true, Optimization true, Expressions true, Deforming true    Timing: Generation 0.954 ms (Deform 0.161 ms), Inlining 8.476 ms, Optimization 25.632 ms, Emission 22.098 ms, Total 57.160 ms (8 rows)

Значение Generation в последней строке плана ― это суммарное время на компиляцию выражений (управляется параметром jit_expressions) и на компиляцию преобразования кортежей (jit_tuple_deforming). Сколько из этого времени заняла компиляция преобразования кортежей в 17-й версии отдельно указано в скобках (Deform).

Поскольку таблиц в этом примере нет, то можно не тратить время на компиляцию преобразования кортежей. Отключим соответствующий параметр и увидим изменения в счетчиках времени:

SET jit_tuple_deforming = off;

Часть плана запроса, касающаяся JIT:

 JIT:    Functions: 5    Options: Inlining true, Optimization true, Expressions true, Deforming false    Timing: Generation 0.665 ms (Deform 0.000 ms), Inlining 15.209 ms, Optimization 25.897 ms, Emission 16.664 ms, Total 58.435 ms

Планировщик: уточнение оценки начальной стоимости узла WindowAgg
commit: 3900a02c

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

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

16=> EXPLAIN (analyze, settings) SELECT t.ticket_no, SUM(tf.amount) OVER () FROM tickets t JOIN ticket_flights tf ON t.ticket_no = tf.ticket_no LIMIT 1;

                                                                           QUERY PLAN                                                                            ----------------------------------------------------------------------------------------------------------------------------------------------------------------  Limit  (cost=0.56..0.86 rows=1 width=46) (actual time=30416.237..30416.239 rows=1 loops=1)    ->  WindowAgg  (cost=0.56..2525161.21 rows=8391708 width=46) (actual time=30416.236..30416.237 rows=1 loops=1)          ->  Nested Loop  (cost=0.56..2420264.86 rows=8391708 width=20) (actual time=0.049..27742.105 rows=8391852 loops=1)                ->  Seq Scan on tickets t  (cost=0.00..78913.45 rows=2949845 width=14) (actual time=0.015..287.636 rows=2949857 loops=1)                ->  Index Scan using ticket_flights_pkey on ticket_flights tf  (cost=0.56..0.76 rows=3 width=20) (actual time=0.007..0.009 rows=3 loops=2949857)                      Index Cond: (ticket_no = t.ticket_no)  Settings: jit = 'off', search_path = 'bookings, public'  Planning Time: 0.456 ms  Execution Time: 30454.862 ms (9 rows)

Нас интересует вторая строка плана с узлом WindowAgg. Начальная стоимость равна 0.56, хотя на получение первой строки ушло ~30 секунд.

В 17-й версии оценка исправлена и планировщик выбирает более уместный hash join, что сразу отражается на скорости выполнения запроса:

17=> EXPLAIN (analyze, settings) SELECT t.ticket_no, SUM(tf.amount) OVER () FROM tickets t JOIN ticket_flights tf ON t.ticket_no = tf.ticket_no LIMIT 1;

                                                                   QUERY PLAN                                                                     -------------------------------------------------------------------------------------------------------------------------------------------------  Limit  (cost=523778.31..523778.31 rows=1 width=46) (actual time=7157.639..7157.642 rows=1 loops=1)    ->  WindowAgg  (cost=523778.31..523778.36 rows=8392150 width=46) (actual time=7157.638..7157.640 rows=1 loops=1)          ->  Hash Join  (cost=130215.56..418876.48 rows=8392150 width=20) (actual time=740.005..5075.390 rows=8391852 loops=1)                Hash Cond: (tf.ticket_no = t.ticket_no)                ->  Seq Scan on ticket_flights tf  (cost=0.00..153881.50 rows=8392150 width=20) (actual time=0.066..794.699 rows=8391852 loops=1)                ->  Hash  (cost=78938.47..78938.47 rows=2949847 width=14) (actual time=737.972..737.973 rows=2949857 loops=1)                      Buckets: 262144  Batches: 32  Memory Usage: 6190kB                      ->  Seq Scan on tickets t  (cost=0.00..78938.47 rows=2949847 width=14) (actual time=0.029..307.040 rows=2949857 loops=1)  Settings: search_path = 'bookings, public', jit = 'off'  Planning Time: 0.331 ms  Execution Time: 7202.957 ms (11 rows)

pg_constraint: ограничения NOT NULL
commit: b0e96f31

В системном каталоге pg_constraint официально появился новый, но хорошо известный, тип ограничения целостности ― NOT NULL. По разным причинам ограничения NOT NULL в pg_constraint не записывались.

Это изменение не несет новых возможностей для разработки приложений, но информация в системном каталоге становится более полной, а работа с ограничениями — более последовательной.

Ограничения NOT NULL в pg_constraint имеют тип contype = 'n':

CREATE TABLE t (     col1 int NOT NULL,     col2 int NOT NULL );  SELECT conname, contype, conkey FROM pg_constraint WHERE conrelid = 't'::regclass;

     conname     | contype | conkey -----------------+---------+--------  t_col1_not_null | n       | {1}  t_col2_not_null | n       | {2} (2 rows)

Ограничение можно удалить по имени, как и любое другое:

ALTER TABLE t DROP CONSTRAINT t_col2_not_null;

Увидеть ограничения можно в отдельной секции вывода команды \d+:

postgres@postgres(17.0)=# \d+ t

                                            Table "public.t"  Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description --------+---------+-----------+----------+---------+---------+-------------+--------------+-------------  col1   | integer |           | not null |         | plain   |             |              |  col2   | integer |           |          |         | plain   |             |              | Not-null constraints:     "t_col1_not_null" NOT NULL "col1" Access method: heap

В настоящий момент ограничения NOT NULL нельзя создать без проверки существующих данных (NOT VALID) или объявить отложенными (DEFERRED). Кроме того, ограничения NOT NULL для таблиц системного каталога в pg_constraint не записаны.

Нормализация CALL, DEALLOCATE и команд управления двухфазной фиксацией
commit: 11c34b34, 638d42a3, bb45156f

Механизм нормализации команд научился заменять параметры процедур в вызовах CALL на константы. Теперь вызов процедуры с разными параметрами будет учитываться в pg_stat_statements одной строкой:

CALL p(1); CALL p(2); CALL p(3);  SELECT queryid, query, calls                                                                                FROM pg_stat_statements WHERE query ILIKE 'CALL%';

       queryid       |   query    | calls ---------------------+------------+-------  7076031282516132618 | CALL p($1) |     3 (1 row)

Кроме того, на константы заменяются идентификаторы транзакций в командах управления двухфазной фиксацией (второй коммит) и имена подготовленных операторов в DEALLOCATE (третий коммит).

См. также
Нормализация DDL и служебных команд
Нормализация DDL и служебных команд, продолжение

unaccent: целевое выражение правил поддерживает значения в кавычках
commit: 59f47fb9

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

При определении собственных правил словаря целевые выражения теперь можно заключать в кавычки. Это нужно для того, чтобы целевое выражение могло начинаться с пробела или заканчиваться им. Раньше лидирующие и хвостовые пробелы обрезались.

COPY FROM: FORCE_NOT_NULL и FORCE_NULL
commit: f6d4c9cf

Параметры FORCE_NOT_NULL и FORCE_NULL команды COPY появились достаточно давно, в версиях 9.0 и 9.4 соответственно. Но требовали явного перечисления столбцов, даже если их нужно было применить ко всем столбцам таблицы.

Вместо перечисления всех столбцов теперь можно указать символ *.

CREATE TABLE t (col1 text NOT NULL, col2 text NOT NULL);  COPY t FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL *);

Enter data to be copied followed by a newline. End with a backslash and a period on a line by itself, or an EOF signal. >> "","" >> \. COPY 1

SELECT * FROM t WHERE col1 = '' and col2 = '';

 col1 | col2 ------+------       | (1 row)

Аудит подключений без аутентификации
commit: e48b19c5

Если пользователь подключается без аутентификации, то при включенном параметре log_connections запись об этом появится в журнале сервера с указанием номера сработавшей строки (117) с методом «trust» в pg_hba.conf:

2023-10-19 11:40:07.609 MSK [111233] LOG:  connection authenticated: user="postgres" method=trust (/home/pal/master/data/pg_hba.conf:117) 2023-10-19 11:40:07.609 MSK [111233] LOG:  connection authorized: user=postgres database=demo application_name=psql

Раньше для метода «trust» в журнал записывалась только вторая строка.

Заметим, что функция SYSTEM_USER по-прежнему возвращает NULL для таких подключений.

См. также
Waiting for PostgreSQL 17 – Generate new LOG for “trust” connections under log_connections – select * from depesz;

pg_stat_subscription: новый столбец worker_type
commit: 13aeaf07

Новый столбец worker_type представления pg_stat_subscription поможет быстро определить, какую работу выполняет процесс, обслуживающий подписку. Возможные значения говорят за себя: apply, parallel apply и table synchronization.

Стоит напомнить, что сервер подписки научился применять транзакции несколькими процессами в 16-й версии.

Функция pg_promote при неуспешном переключении на реплику
commit: f593c551

Один из вариантов переключения на реплику ― это вызов функции pg_promote. Если переключиться не удалось, то функция возвращает false. И неважно, что послужило причиной:

  • не уложились в таймаут на переключение,
  • не получилось отправить сигнал SIGUSR1 постмастеру,
  • или даже постмастер аварийно завершился в процессе переключения.

Для двух последних случаев в журнал сервера записывалось предупреждение. В 17-й версии эти предупреждения превратились в ошибки, соответственно функция pg_promote будет возвращать false только при превышении таймаута, в остальных случаях завершаться ошибкой.

Выбор метода синхронизации с диском в утилитах сервера
commit: 8c16ad3b

Утилиты сервера, пишущие файлы на диск, получили параметр --synch-method. Речь о следующих утилитах: initdb, pg_basebackup, pg_checksums, pg_dump, pg_rewind и pg_upgrade.

По умолчанию используется --synch-method=fsync.

В линукс-системах можно указать syncfs для синхронизации всей файловой системы сразу, вместо вызова fsync на каждый файл. Во многих случаях это будет быстрее. Однако возможны и побочные эффекты, если с этой же файловой системой активно работают другие приложения, изменяющие файлы: в таком случае придется дополнительно синхронизировать и их запись. В документации появилось новое приложение Appendix O. syncfs() Caveats, предупреждающее об этом.

pg_restore: оптимизация восстановления в несколько потоков большого количества таблиц
commit: c103d073, 5af0263a, 9bfd44bb

Восстановление в несколько потоков утилитой pg_restore выполнялось не оптимально при большом количестве таблиц в резервной копии. Речь о десятках тысяч таблиц и больше.

Проблема в том, что родительский процесс-координатор каждый раз сортировал список таблиц, чтобы найти самую большую и передать ее на восстановление рабочему процессу. Ресурсов на сортировку тратилось так много, что сам родительский процесс становился узким местом: рабочие процессы простаивали в ожидании работы.

Для оптимизации выбора следующей таблицы список таблиц поместили в двоичную кучу. Теперь pg_restore будет значительно быстрее восстанавливать из копий с большим количеством таблиц.

pg_basebackup и pg_receivewal с параметром dbname
commit: cca97ce6

pg_basebackup и pg_receivewal принимают параметр dbname для подключения. Вообще он не нужен этим утилитам, ведь резервные копии и WAL — это уровень всего кластера баз данных. Но в случае подключения через прокси, в частности pgbouncer, требуется указывать базу данных.

Нужно ли подключаться через pgbouncer для выполнения резервного копирования ― это отдельный вопрос. Но если другого способа подключиться нет, то данный патч дает такую возможность.

Имена параметров для ряда встроенных функций
commit: b575a26c

Название параметров необязательно при определении функции, обязателен только тип данных. Именно так и созданы большинство встроенных функций. Но если у функции более одного параметра с одинаковыми типами, то назначение параметров сложно разобрать в выводе \df, нужно заглянуть в документацию.

16=> \df string_agg

                            List of functions    Schema   |    Name    | Result data type | Argument data types | Type ------------+------------+------------------+---------------------+------  pg_catalog | string_agg | bytea            | bytea, bytea        | agg  pg_catalog | string_agg | text             | text, text          | agg (2 rows)

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

17=> \df string_agg

                                List of functions    Schema   |    Name    | Result data type |     Argument data types      | Type ------------+------------+------------------+------------------------------+------  pg_catalog | string_agg | bytea            | value bytea, delimiter bytea | agg  pg_catalog | string_agg | text             | value text, delimiter text   | agg (2 rows)

psql: \watch min_rows
commit: f347ec76

В 16-й версии команде \watch добавили параметр count, позволяющий указать количество повторений выполнения запроса.

Теперь появился еще один способ автоматически завершить работу \watch. Если запрос вернет меньше строк, чем указано в параметре min_rows, то повторение запроса прекращается. Может быть полезным для мониторинга выполнения заданий.

CREATE TABLE job (id int, status text DEFAULT 'NEW'); INSERT INTO job VALUES (1),(2),(3);

Для удобства демонстрации обработка заданий выполняется вместе с мониторинговым запросом:

WITH process_job AS (     UPDATE job SET status = 'PROCESSED'     WHERE id = (SELECT id                 FROM job                 WHERE status = 'NEW'                 ORDER BY id LIMIT 1) ) SELECT * FROM job WHERE status = 'NEW' \watch min_rows=1

Mon 16 Oct 2023 05:55:53 PM MSK (every 2s)   id | status ----+--------   1 | NEW   2 | NEW   3 | NEW (3 rows)  Mon 16 Oct 2023 05:55:55 PM MSK (every 2s)   id | status ----+--------   2 | NEW   3 | NEW (2 rows)  Mon 16 Oct 2023 05:55:57 PM MSK (every 2s)   id | status ----+--------   3 | NEW (1 row)  Mon 16 Oct 2023 05:55:59 PM MSK (every 2s)   id | status ----+-------- (0 rows)

См. также
Waiting for PostgreSQL 17 – Allow \watch queries to stop on minimum rows returned – select * from depesz;


На этом пока всё. Ждем ноябрьского коммитфеста 17-й версии.


ссылка на оригинал статьи https://habr.com/ru/articles/769598/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *