Согласно статистике, в сентябрьских коммитфестах меньше всего коммитов. Но похоже, что для релизного цикла 18-й версии это не так. Много принятых патчей и много интересных новых возможностей, информацией о которых хочется поделиться.


Напомню, что самое интересное из июльского коммитфеста можно прочитать здесь: 2024-07.


Мониторинг конфликтов логической репликации
Планировщик: теперь без 10000000000
Планировщик: управление памятью и мониторинг ее использования для временного хранилища строк
Чтение буферов при сканировании индекса в обратном на��равлении
pg_upgrade: асинхронное выполнение операций в нескольких базах данных
Оптимизация соединения хешированием
Оптимизация работы с текстовыми значениями в JSON
Оптимизация умножения чисел типа numeric
Оптимизация деления чисел типа numeric
ANALYZE ONLY и VACUUM ONLY
Уточненная статистика процесса контрольной точки
pg_stat_statements: нормализация команд SET
postgres_fdw_get_connections и статус удаленного соединения
file_fdw: игнорирование ошибок преобразования форматов
Функция has_largeobject_privilege
Функции crc32 и crc32c
Клиент-серверный протокол: информирование о search_path
psql: поддержка именованных подготовленных операторов
pg_verifybackup: проверка целостности копий в формате tar





Мониторинг конфликтов логической репликации
commit: 9758174e2, edcb71258, 640178c92, 6c2b5edec


Данные в таблицах на сервере-подписчике можно изменять независимо от сервера публикации. Сервер публикации об этих изменениях не узнает. Отсюда могут возникать конфликты логической репликации. Добавляем строку в таблицу на сервере публикации, а строка с таким же первичным ключом уже есть на подписчике. Или изменяем/удаляем строку на сервере публикации, а ее уже нет на подписчике. Всего вариантов конфликтов может быть 6 и теперь они описаны в одноименном разделе главы о логической репликации: Конфликты. Стоит отметить, что не все конфликты прерывают работу логической репликации.


Кроме описания появились еще и средства для отслеживания конфликтов. Рассмотрим их на примере конфликта update_missing.


На сервере публикации и сервере-подписчике созданы таблицы следующей командой:


CREATE TABLE test(id int PRIMARY KEY);

На сервере публикации добавим строку в таблицу:


pub=# INSERT INTO test (id) VALUES (1);

А на подписчике ее сразу удалим:


sub=# DELETE FROM test WHERE id = 1;

Все готово к появлению конфликта. На сервере публикации обновим единственную строку:


pub=# UPDATE test SET id = 2 WHERE id = 1 RETURNING *;

 id
----
  2
(1 row)

Обновление проходит, но оно не будет применено на подписчике, ведь там строки с id=1 нет. Логическая репликация, как и прежде, игнорирует эту команду на подписчике и продолжает работать. Но если в предыдущих версиях мы об этом даже не узнаем, то теперь есть две возможности получить детали случившегося.


Во-первых, в представлении pg_stat_subscription_stats появились счетчики для конфликтов всех типов. Обратившись к представлению, можно увидеть, что счетчик confl_update_missing уже не нулевой:


sub=# SELECT * FROM pg_stat_subscription_stats WHERE subname = 'sub'

-[ RECORD 1 ]---------------+------
subid                       | 16390
subname                     | sub
apply_error_count           | 0
sync_error_count            | 0
confl_insert_exists         | 0
confl_update_origin_differs | 0
confl_update_exists         | 0
confl_update_missing        | 1
confl_delete_origin_differs | 0
confl_delete_missing        | 0
stats_reset                 |

Во-вторых, в журнале сервера есть подробная информация о конфликте:


2025-01-23 23:33:35.763 MSK [170775] LOG:  conflict detected on relation "public.test": conflict=update_missing
2025-01-23 23:33:35.763 MSK [170775] DETAIL:  Could not find the row to be updated.
    Remote tuple (2); replica identity (id)=(1).
2025-01-23 23:33:35.763 MSK [170775] CONTEXT:  processing remote data for replication origin "pg_16390" during message type "UPDATE" for replication target relation "public.test" in transaction 746, finished at 0/183DC60

Примечание. Очень хотелось бы увидеть следующий логичный ход в разработке. А именно появление стратегий автоматического разрешения конфликтов, прерывающих работу логической репликации. Для таких надежд есть основания, ведь переписка разработчиков начинается в том числе с предложения реализовать две стратегии: apply(remote_apply) и skip(keep_local). Но задача отслеживания конфликтов ценна сама по себе и реализацию начали именно с неё. Ждем продолжения!


Планировщик: теперь без 10000000000
commit: e22253467, c01743aa4, 161320b4b, 84b8fccbe


Семейство параметров enable_* позволяет указать планировщику не использовать соответствующие пути выполнения запроса. Например предложим обойтись без последовательного сканирования таблицы:


CREATE TABLE t (id int);

SET enable_seqscan = off;
EXPLAIN (settings) SELECT * FROM t;

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


В плане запроса для 17-й версии мы увидим огромную стоимость:


                               QUERY PLAN                               
------------------------------------------------------------------------
 Seq Scan on t  (cost=10000000000.00..10000000035.50 rows=2550 width=4)
 Settings: enable_seqscan = 'off'
(2 rows)

К реальной стоимости узла Seq Scan добавлена запретительная константа 10_000_000_000. Добавление этой константы к стоимости последовательного сканирования сделало бы другие варианты доступа к таблице менее затратными, при их наличии. Но других вариантов нет и мы видим это огромное число.


В 18-й версии константу удалили, планировщик научился без нее учитывать значения параметров enable_*. А план запроса теперь выглядит так:


                     QUERY PLAN                      
-----------------------------------------------------
 Seq Scan on t  (cost=0.00..35.50 rows=2550 width=4)
   Disabled: true
 Settings: enable_seqscan = 'off'
(3 rows)

По-прежнему выбрано последовательное сканирование, но с реальной стоимостью, без «накруток». Строка Disabled: true говорит о том, что планировщик вынужден выбрать запрещенный узел из-за отсутствия других незапрещенных вариантов.


Если другой вариант есть, он будет использован:


CREATE INDEX ON t(id);
ANALYZE t;

EXPLAIN (settings) SELECT * FROM t;

                              QUERY PLAN                               
-----------------------------------------------------------------------
 Index Only Scan using t_id_idx on t  (cost=0.12..8.14 rows=1 width=4)
 Settings: enable_seqscan = 'off'
(2 rows)

Дополнительно заметим, что включение в команду EXPLAIN параметра settings (появился в 12-й версии) может быть очень полезным, т. к. позволяет узнать, изменение каких параметров повлияло на план запроса.



Планировщик: управление памятью и мониторинг ее использования для временного хранилища строк
commit: 1eff8279d, 53abb1e0e, 590b045c3, 97651b013, 04bcf9e19, 908a96861, 9fba1ed29, 95d6e9af0, 5d56d07ca, 40708acd6


Эта большая работа велась в рамках нескольких записей коммитфеста, отсюда и длинный список коммитов. Но их удобно описать в одном месте.


Ряд узлов плана запросов (список ниже) используют общий интерфейс для временного хранения строк (tuplestore). В этом интерфейсе произошли два важных изменения.


Во-первых, для хранения строк теперь используется отдельный контекст памяти с типом Generation, вместо более общего CurrentMemoryContext. Не вдаваясь в детали, такое изменение положительно влияет на управление памятью и скорость работы.


Во-вторых, EXPLAIN (analyze) теперь показывает тип используемой памяти (Memory или Disk) и максимальное количество выделенной памяти для узлов плана, использующих общий интерфейс. К этим узлам относятся:


  • Materialize ― сохранение промежуточного набора строк для последующего многократного использования;
  • CTE Scan ― для запросов с общими табличными выражениями;
  • WindowAgg ― для запросов с оконными функциями;
  • Recursive Union ― для рекурсивных запросов;
  • Table Function Scan ― для запросов к функциям JSON_TABLE, XMLTABLE (не путать с Function Scan).

В следующем примере данные для узла Materialize поместились в оперативной памяти. Смотрим на новую строку Storage:


EXPLAIN (costs off, analyze, timing off, summary off, buffers off)            
SELECT * FROM airports a1 CROSS JOIN airports a2;

                              QUERY PLAN                              
----------------------------------------------------------------------
 Nested Loop (actual rows=10816 loops=1)
   ->  Seq Scan on airports_data ml (actual rows=104 loops=1)
   ->  Materialize (actual rows=104 loops=104)
         Storage: Memory  Maximum Storage: 35kB
         ->  Seq Scan on airports_data ml_1 (actual rows=104 loops=1)
(5 rows) 

А вот для CTE в следующем примере строки не поместились в память, поэтому для их хранения использовались временные файлы:


EXPLAIN (analyze, costs off, summary off, timing off, buffers off)
WITH b AS MATERIALIZED (
  SELECT * FROM bookings
)
SELECT * FROM b;

                         QUERY PLAN                         
------------------------------------------------------------
 CTE Scan on b (actual rows=2111110 loops=1)
   Storage: Disk  Maximum Storage: 67856kB
   CTE b
     ->  Seq Scan on bookings (actual rows=2111110 loops=1)
(4 rows)

Для узла WindowAgg сделаны дополнительные оптимизации при переключении на следующий раздел (PARTITION BY). Чем больше переключений разделов ― тем больше эффект. Например следующий запрос в 18-й версии работает на 12% быстрее, чем в 17-й.


EXPLAIN (analyze, costs off, summary off, timing off, buffers off)
SELECT book_ref, count(*) OVER (PARTITION BY book_ref)
FROM tickets;

                                        QUERY PLAN                                         
-------------------------------------------------------------------------------------------
 WindowAgg (actual rows=2949857 loops=1)
   Storage: Memory  Maximum Storage: 17kB
   ->  Index Only Scan using tickets_book_ref_idx on tickets (actual rows=2949857 loops=1)
         Heap Fetches: 0
(4 rows)

План рекурсивного запроса также показывает, сколько и какой памяти используется в узлах Recursive Union и CTE Scan.


EXPLAIN (analyze, costs off, summary off, timing off, buffers off)
WITH RECURSIVE t(n) AS (
    VALUES (1)
  UNION ALL
    SELECT n+1 FROM t WHERE n < 100
)
SELECT sum(n) FROM t;

                           QUERY PLAN                            
-----------------------------------------------------------------
 Aggregate (actual rows=1 loops=1)
   CTE t
     ->  Recursive Union (actual rows=100 loops=1)
           Storage: Memory  Maximum Storage: 33kB
           ->  Result (actual rows=1 loops=1)
           ->  WorkTable Scan on t t_1 (actual rows=1 loops=100)
                 Filter: (n < 100)
                 Rows Removed by Filter: 0
   ->  CTE Scan on t (actual rows=100 loops=1)
         Storage: Memory  Maximum Storage: 20kB
(10 rows)

И, наконец, узел для обработки функций JSON_TABLE, XMLTABLE.


EXPLAIN (analyze, costs off, summary off, timing off, buffers off)
SELECT * FROM JSON_TABLE('{"a":1}'::jsonb, '$[*]' COLUMNS (a int));

                         QUERY PLAN                          
-------------------------------------------------------------
 Table Function Scan on "json_table" (actual rows=1 loops=1)
   Storage: Memory  Maximum Storage: 17kB
(2 rows)


Чтение буферов пр�� сканировании индекса в обратном направлении
commit: 3f44959f4, 1bd4bc85c, b5ee4e520, caca6d8d2, 4e6e375b0


При сканировании индекса в обратном направлении, без особой необходимости повторно блокировались ранее прочитанные буферы. Это приводило к дополнительному чтению буферов. Сравним количество буферов при прямом сканировании индекса в первом запросе и обратном сканировании индекса во втором запросе:


17=# EXPLAIN (analyze, buffers, costs off, summary off, timing off)
SELECT * FROM flights ORDER BY flight_id ASC;

                              QUERY PLAN                               
-----------------------------------------------------------------------
 Index Scan using flights_pkey on flights (actual rows=214867 loops=1)
   Buffers: shared hit=3499

17=# EXPLAIN (analyze, buffers, costs off, summary off, timing off)
SELECT * FROM flights ORDER BY flight_id DESC;

                                   QUERY PLAN                                   
--------------------------------------------------------------------------------
 Index Scan Backward using flights_pkey on flights (actual rows=214867 loops=1)
   Buffers: shared hit=4087

В 18-й версии сканирование индекса в любом направлении потребует минимального количества буферов. Для этого запроса ― 3499.



pg_upgrade: асинхронное выполнение операций в нескольких базах данных
commit: 40e2e5e92, 6d3d2e8e5, 7baa36de5, 46cad8b31, 6ab8f27bc, bbf83cab9, 9db3018cf, c34eabfbb, cf2f82a37, f93f5f7b9, c880cf258


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


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


Асинхронная обработка управляется параметром --jobs, ее эффективность возрастает вместе с количеством баз данных в обновляемом кластере.


Эта работа продолжает серию оптимизаций pg_upgrade, описанную в предыдущей статье.


Примечание. Следующие четыре оптимизации примечательны тем, что для них нет никаких настроек. Просто перечисленные операции будут быстрее работать.


Оптимизация соединения хешированием
commit: adf97c156


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



Оптимизация работы с текстовыми значениями в JSON
commit: ca6fde922


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



Оптимизация умножения чисел типа numeric
commit: ca481d3c9, c4e44224c, 8dc28d7eb


Числа типа numeric будут умножаться быстрее.



Оптимизация деления чисел типа numeric
commit: 9428c001f


Числа типа numeric будут делиться быстрее.



ANALYZE ONLY и VACUUM ONLY
commit: 62ddf7ee9


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


Но если вычищать мертвые строки в секционированной таблице не требуется, то регулярно собирать статистику может быть полезным для выполнения некоторых за��росов. Ведь часть сводной статистики собирается для самой секционированной таблицы. Обычная команда ANALYZE будет собирать статистику не только по секционированной таблице, но и по всем секциям, и это может занять много времени. А варианта с ONLY, как в командах DML, до 18-й версии не было.


Теперь сбор сводной статистики, без секций, можно выполнять так:


ANALYZE ONLY partitioned_table;

ANALYZE

То же самое для VACUUM.


VACUUM (analyze) ONLY partitioned_table;

WARNING:  VACUUM ONLY of partitioned table "partitioned_table" has no effect
VACUUM

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


Добавление ONLY в команды VACUUM и ANALYZE коснулось не только секционированных таблиц, но и родительских таблиц с наследниками. Причем изменение несовместимо с предыдущими версиями.


Раньше команда


ANALYZE parent_table;

собирала статистику только для родительской таблицы. А теперь будет обрабатывать еще и всех наследников. Для сбора статистики только по родительской таблице нужно указать ONLY:


ANALYZE ONLY parent_table;

Аналогично для VACUUM.



Уточненная статистика процесса контрольной точки
commit: 17cc5f666


Статистику работы процесса контрольной точки можно увидеть в появившемся в 17-й версии представлении pg_stat_checkpointer и журнале сервера.


Оказалось что количество записанных буферов в этих двух источниках информации отличается. Причина в том, что сообщение в журнале сервера включает как записанные буферы общего буферного кеша (shared_buffers), так и записанные буферы SLRU, тогда как в pg_stat_checkpointer.buffers_written учитываются только буферы shared_buffers.


Чтобы статистика совпадала, сделали следующее. В журнале сервера информацию о записанных буферах разделили: отдельно указывается, сколько записано буферов из shared_buffers, и сколько записано буферов SLRU.


LOG:  checkpoint complete: wrote 47 buffers (0.3%), wrote 0 SLRU buffers; …

А в представление pg_stat_checkpointer в дополнение к столбцу buffers_written добавили столбец slru_written.


Легко убедиться, что теперь информация в двух источниках совпадает.


CHECKPOINT;
SELECT pg_stat_reset_shared('checkpointer');

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


CREATE TABLE bookings_copy AS SELECT * FROM bookings;
CHECKPOINT;

Проверяем информацию в журнале сервера и pg_stat_checkpointer:


\! tail -n 1 logfile
2024-11-12 16:56:33.443 MSK [63957] LOG:  checkpoint complete: wrote 2016 buffers (12.3%), wrote 1 SLRU buffers; 0 WAL file(s) added, 5 removed, 5 recycled; write=0.044 s, sync=0.311 s, total=0.773 s; sync files=20, longest=0.226 s, average=0.016 s; distance=165531 kB, estimate=444398 kB; lsn=1/4A6A8E20, redo lsn=1/4A6A8DC8

SELECT buffers_written, slru_written FROM pg_stat_checkpointer;

 buffers_written | slru_written
-----------------+--------------
            2016 |            1
(1 row)


pg_stat_statements: нормализация команд SET
commit: ba90eac7a, dc6851596


Очередное продолжение работы над нормализацией команд. На этот раз речь о команде установки параметров SET. Команды с разным форматированием и разными значениями параметров будут приведены к одному виду, что поможет сократить количество хранимых запросов в pg_stat_statements.


SELECT pg_stat_statements_reset();

SET SEARCH_PATH = pg_catalog;
SET   search_path  =  public;
set search_path=bookings,public;

SET CUSTOM.PARAMETER = 1;
SET   CUSTOM.parameter  =  2;
set custom.parameter=42;

SELECT queryid, query, calls
FROM pg_stat_statements
WHERE query ILIKE 'SET%';

       queryid        |           query           | calls 
----------------------+---------------------------+-------
 -5443897209013767274 | SET CUSTOM.PARAMETER = $1 |     3
 -1735366501441382531 | SET SEARCH_PATH = $1      |     3
(2 rows)


postgres_fdw_get_connections и статус удаленного соединения
commit: c297a47c5, 857df3cef, 4f08ab554


Выполним в транзакции обращение к сторонней таблице.


BEGIN;
SELECT count(*) FROM remote_bookings.tickets;

  count  
---------
 2949857
(1 row)

Сейчас удаленное соединение должно быть активно, мы можем проверить статус всех соединений функцией postgres_fdw_get_connections. А помогут сделать это три новых столбца функции: user_name, user_in_xact и closed:


SELECT * FROM postgres_fdw_get_connections(check_conn => true);

 server_name | user_name | valid | used_in_xact | closed
-------------+-----------+-------+--------------+--------
 demo_srv    | postgres  | t     | t            | f
(1 row)

Столбец user_name ― это имя локального пользователя, used_in_xact ― используется ли соединение в текущей транзакции, а closed показывает статус соединения.


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



file_fdw: игнорирование ошибок преобразования форматов
commit: e7834a1a2, a1c4c8a9e


В 17-й версии у команды COPY появилась возможность игнорировать ошибки преобразования форматов при загрузке строк. Для этого в команду были добавлены параметры on_error и log_verbosity. Расширение file_fdw для чтения файлов использует именно COPY, поэтому вполне логичным было добавить в расширение соответствующие возможности.


Для примера создадим вот такой файл, в котором не все строки являются числами:


$ cat /tmp/t.txt

1
два
три
4

Создаем стороннюю таблицу для этого файла c целочисленным столбцом и указанием игнорировать ошибки преобразования формата:


CREATE EXTENSION file_fdw;
CREATE SERVER file_server
    FOREIGN DATA WRAPPER file_fdw;

CREATE FOREIGN TABLE ft (
    id integer
)
SERVER file_server
OPTIONS (
    filename '/tmp/t.txt',
    on_error 'ignore',
    log_verbosity 'verbose'
);

Запрос к таблице работает, показывая только успешно загруженные строки и предупреждения для остальных:


SELECT * FROM ft;

NOTICE:  skipping row due to data type incompatibility at line 2 for column "id": "два"
NOTICE:  skipping row due to data type incompatibility at line 3 for column "id": "три"
NOTICE:  2 rows were skipped due to data type incompatibility
 id
----
  1
  4
(2 rows)

Вывод предупреждений можно отключить при помощи нового значения silent параметра log_verbosity:


ALTER FOREIGN TABLE ft OPTIONS (SET log_verbosity 'silent');

SELECT * FROM ft;

 id
----
  1
  4
(2 rows)


Функция has_largeobject_privilege
commit: 4eada203a


Прибавление в семействе функций has_*_privilege. Теперь проверить права доступа к большим объектам можно с помощью функции has_largeobject_privilege.


\lo_import 'logfile'
lo_import 24578

GRANT SELECT ON LARGE OBJECT 24578 TO public;

SELECT has_largeobject_privilege('alice', 24578, 'SELECT');

 has_largeobject_privilege
---------------------------
 t
(1 row)

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


Примечание. Если сравнить список типов объектов, на которые можно выдавать привилегии, и список функций has_*_privilege, то можно заметить отсутствие функции has_domain_privilege. Всё дело в том, что домены не совсем самостоятельный тип объекта, по своей сути они являются типами и хранятся в системном каталоге pg_type. Поэтому вместо функции has_domain_privilege с до��енами прекрасно работает has_type_privilege. Но для создания доменов существует отдельная команда CREATE DOMAIN, привилегии на домены также выдаются отдельной командой GRANT… ON DOMAIN. Поэтому отсутствие функции has_domain_privilege может сбить с толку. Возможно это повод внести в документацию для функции has_type_privilege уточнение о работе с доменами.


Функции crc32 и crc32c
commit: 760162fed


Новые функции для вычисления контрольных сумм CRC-32 и CRC-32C:


SELECT crc32('42'::bytea), crc32c('42'::bytea);

   crc32   |  crc32c   
-----------+-----------
 841265288 | 779959755
(1 row)


Клиент-серверный протокол: информирование о search_path
commit: 28a1121fd, 0d06a7eac


Клиент-серверный протокол работает так, что сразу после подключения клиенты получают сообщение ParameterStatus о значениях некоторых параметров, влияющих на работу приложения, например client_encoding или DateStyle. При изменении этих параметров в сеансе (обычно командой SET) серверный процесс автоматически уведомляет клиента о новых значениях.


Список параметров фиксирован, но первым коммитом в него был добавлен параметр search_path.


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



psql: поддержка именованных подготовленных операторов
commit: d55322b0d


Поддержка расширенного протокола запросов в psql была добавлена в 16-й версии. Но поддерживались только неименованные подготовленные операторы.


С новыми командами \parse, \bind_named и \close появилась возможность более полноценно работать с подготовленными операторами. Вот как это выглядит:


SELECT $2||$1 AS str \parse q

SELECT * FROM pg_prepared_statements \gx

-[ RECORD 1 ]---+------------------------------
name            | q
statement       | SELECT $2||$1 AS str
prepare_time    | 2024-11-08 09:30:50.326934+03
parameter_types | {text,text}
result_types    | {text}
from_sql        | f
generic_plans   | 0
custom_plans    | 0

Первая команда объявляет подготовленный оператор с именем q и двумя текстовыми параметрами. Вторым запросом можно убедиться, что подготовленный оператор появился.


Для связывания параметров с фактическими значеними и выполнения подготовленного оператора используем команду \bind_named. Сначала указываем имя подготовленного оператора, затем значения двух параметров:


\bind_named q 42 'Answer: ' \g

    str     
------------
 Answer: 42
(1 row)

\bind_named q 'World!' 'Hello,' \g

     str      
--------------
 Hello,World!
(1 row)

И, наконец, закрытие подготовленного оператора (DEALLOCATE):


\close q


pg_verifybackup: проверка целостности копий в формате tar
commit: 8dfd31290


Утилита pg_verifybackup сможет проверять целостность копии кластера не только в формате plain, но и tar. Копия в формате tar может быть сжата:


$ pg_basebackup -D backup --format=tar --gzip
$ ls -l backup

total 569416
-rw------- 1 pal pal    190742 дек 24 18:57 backup_manifest
-rw------- 1 pal pal 582862489 дек 24 18:57 base.tar.gz
-rw------- 1 pal pal     17120 дек 24 18:57 pg_wal.tar.gz

Но есть ограничение. Утилита pg_verifybackup не умеет работать с файлами WAL, поэтому нужно явно указывать, что проверка WAL не требуется:


$ pg_verifybackup backup --format=tar --no-parse-wal

backup successfully verified




На этом пока всё. Впереди основные события ноябрьского коммитфеста.