Практическая работа. Курс: «Администрирование СУБД Tantor»

Оглавление

Раздел 1. Установка СУБД Tantor        7

Часть 1. Создание кластера        7

Часть 2. Создание кластера утилитой initdb        10

Часть 3. Режим одного пользователя        15

Часть 4.  Передача параметров экземпляру в командной строке        16

Часть 5. Локализация        16

Часть 6. Однобайтные кодировки        17

Часть 7. Использование утилит управления        19

Часть 8. Настройка терминального клиента psql        20

Часть 9. Использование терминального клиента psql        25

Часть 10. Восстановление сохраненного кластера        34

Раздел 2. Архитектура        35

Структура памяти        35

Часть 1. Транзакция в psql        35

Часть 2. Список фоновых процессов        36

Часть 3. Буферный кэш, команда EXPLAIN        38

Часть 4. Журнал предзаписи. Где хранится?        38

Часть 5. Контрольная точка        39

Часть 6. Восстановление после сбоя        40

Многоверсионность        41

Часть 1. Вставка, обновление и удаление строки        41

Часть 2. Видимость версии строки на различных уровнях изоляции        43

Часть 3. Состояние транзакции по CLOG        45

Часть 4. Блокировки таблицы        47

Часть 5. Блокировка строки        48

Регламентные работы        50

Часть 1. Обычная очистка таблицы        50

Часть 2. Анализ таблицы        53

Часть 3. Перестройка индекса        53

Часть 4. Полная очистка        54

Часть 5. Расширение HypoPG        54

Выполнение запросов        59

Часть 1. Создание объектов для запросов        59

Часть 2. Извлечение данных последовательно        59

Часть 3. Возвращение данных по индексу        60

Часть 4. Низкая селективность        61

Часть 5. Использование статистики        62

Часть 6. Представление pg_stat_statements        62

Расширяемость        65

Часть 1. Определение директории с файлами расширения        65

Часть 2. Просмотр установленных расширений        66

Часть 3. Просмотр доступных расширений        66

Часть 4. Установка и удаление произвольного обновления        67

Часть 5. Просмотр доступных версий расширений. Обновление до актуальной версии        67

Часть 6. Обертки внешних данных        69

Раздел 3. Конфигурирование        73

Часть 1. Обзор параметров конфигурации        73

Часть 2. Параметры конфигурации с единицей измерения        81

Часть 3. Параметры конфигурации логического типа        83

Часть 4. Конфигурационные параметры        85

Часть 5. Файл служб        90

Раздел 4. Базы данных        92

Логическая структура кластера        92

1. Установка параметров конфигурации на различных уровнях        92

2. Установка пути поиска в функциях и процедурах        92

Часть 1. Установка параметров конфигурации на различных уровнях        92

Часть 2. Установка пути поиска в функциях и процедурах        102

Физическая структура кластера        105

5. Перемещение таблицы в другое табличное пространство утилитой pg_repack        105

6. Использование утилиты pgcompacttable        105

7. Расширение ORC (Колоночно-ориентированный формат, Citus columnar)        105

a. Установка и использование        105

b. Сравнение алгоритмов сжатия        105

c. Функционал расширения        105

Часть 1. Создание соединения с базой данных        105

Часть 2. Содержимое табличного пространства        108

Часть 3. Файл объекта «последовательность»        112

Часть 4. Перемещение таблицы в другое табличное пространство        114

Часть 5. Перемещение таблицы в другое табличное пространство утилитой pg_repack        116

Часть 6. Использование утилиты pgcompacttable        117

Часть 7. Расширение ORC (Колоночно-ориентированный формат, Citus columnar)        121

Часть 7a. Установка и использование        121

Часть 7b. Сравнение алгоритмов сжатия        124

Часть 7c. Функционал расширения        126

Раздел 5. Журналирование        132

Журналирование        132

Часть 1. Какая информация попадает в журнал        132

Часть 2. Расположение журналов сервера        132

Часть 3. Как информация попадает в журнал        133

Часть 4. Добавление формата csv        134

Часть 5.  Включение коллектора сообщений        135

Раздел 6. Безопасность        136

Ролевая модель безопасности        136

Часть 1. Создание новой роли        136

Часть 2. Установка атрибутов        137

Часть 3. Создание групповой роли        137

Часть 4. Создание схемы и таблицы        138

Часть 5. Выдача роли доступа к таблице        139

Часть 6. Удаление созданных объектов        141

Подключение и аутентификация        142

Часть 1. Расположение файлов конфигурации        142

Часть 2. Просмотр правил аутентификации        143

Часть 3. Локальные изменения для аутентификации        143

Часть 4. Проверка корректности настройки        144

Часть 5. Очистка ненужных объектов        145

Раздел 7. Резервное копирование        146

Физическое копирование        146

Часть 1. Создание базовой резервной копии кластера        146

Часть 2. Запуск экземпляра на копии кластера        147

Часть 3. Файлы журнала        149

Часть 4. Проверка целостности резервной копии        151

Часть 5. Согласованная резервная копия        152

Часть 6. Удаление файлов журнала        154

Часть 7. Создание архива журнала утилитой pg_receivewal        156

Часть 8. Синхронная фиксация транзакций и pg_receivewal        159

Часть 9. Минимизация потерь данных транзакций        160

Логическое копирование        162

Часть 1. Использование утилиты pg_dump        162

Часть 2. Формат custom и утилита pg_restore        164

Часть 3. Формат directory        166

Часть 4. Сжатие и скорость резервирования        167

Часть 5. Команда COPY        168

Раздел 8. Репликация        171

Физическая репликация        171

Часть 1. Создание реплики        171

Часть 2. Слоты репликации        173

Часть 3. Изменение имени кластера        174

Часть 4. Создание второй реплики        177

Часть 5. Выбор реплики на роль мастера        181

Часть 6. Подготовка к переключению на реплику        185

Часть 7. Переключение на реплику        192

Часть 8. Включение обратной связи        195

Часть 9. Утилита pg_rewind        199

Логическая репликация        205

Часть 1. Репликация таблицы        205

Часть 2. Репликация без первичного ключа        212

Часть 3. Добавление таблицы в публикацию        216

Часть 4. Двунаправленная репликация        219

Часть 5. Удаление подписок и публикаций        223

Платформа «Tantor»        225

Обзор        225

Часть 1. Рабочие пространства        225

Часть 2. Обзор экземпляра        225

Часть 3. Настройка экземпляра        225

Часть 4. Профайлинг запросов        225

Часть 5. Текущие активности        226

Часть 6. Регламентные работы        226

Раздел 10. Дополнительные возможности и изменения СУБД Tantor по сравнению с PostgreSQL        227

Расширение orafce        227

Расширение pg_variables        229

Расширение page_repair        234

Часть 1. Подготовка реплики        234

Часть 2. Подготовка таблицы        235

Часть 3. Восстановление страницы с помощью page_repair        237

Часть 4. Обнуление страницы        240

Отладка подпрограмм        243

Часть 1. Установка расширения из исходных кодов на примере pldebugger        243

Часть 2. Отладка функции в pgAdmin        247

Часть 3. Отладка подпрограмм в DBeaver        249

Обработка строк большого размера - StringBuffer        258

Подготовлено:

Олег Иванов, Дмитрий Пронькин, Эмиль Школьник, Дарья Мишарина, Александр Горбачук

!

Последнее обновление: 30 мая 2024 г.
По всем вопросам и предложениям касательно обучения, пожалуйста, обращайтесь: [email protected]


Раздел 1. Установка СУБД Tantor

  1. Создание кластера
  2. Создание кластера утилитой initdb
  3. Режим одного пользователя
  4. Передача параметров экземпляру в командной строке
  5. Локализация
  6. Однобайтные кодировки
  7. Использование утилит управления
  8. Настройка терминального клиента psql 
  9. Использование терминального клиента psql
  10. Восстановление сохраненного кластера

Часть 1. Создание кластера

1) Откройте терминал с правами root:

astra@tantor:~$ sudo bash

2) Посмотрите сколько ядер процессора доступно в виртуальной машине (результат может отличаться от приведенных как пример значений):

root@tantor:/home/astra# cat /proc/cpuinfo | grep cores

cpu cores   : 2

cpu cores   : 2

Число строк по числу процессоров. Если выполнить команду без "| grep cores" будет видно, что выдаются детальные данные по каждому ядру процессора.

Сколько оперативной памяти имеется:

root@tantor:/home/astra# cat /proc/meminfo | grep Mem

MemTotal:        8130152 kB

MemFree:         3288668 kB

MemAvailable:    4781360 kB

3) Программное обеспечение СУБД Тантор установлено в директорию /opt/tantor/db 

Директория с файлами кластера /var/lib/postgresql.

Эти директории могут иметь отдельные точки монтирования, но в нашей операционной системе эти директории смонтированы в корне "/". Проверьте сколько осталось свободного места:

root@tantor:/home/astra# df -HT | grep /$

/dev/sda1      ext4           41G   22G   18G  56% /

Свободного места осталось 18Гб.

4) Скачайте инсталлятор:

root@tantor:/home/astra# wget https://public.tantorlabs.ru/db_installer.sh

https://public.tantorlabs.ru/db_installer.sh

Resolving public.tantorlabs.ru (public.tantorlabs.ru)... 84.201.157.208

Connecting to public.tantorlabs.ru (public.tantorlabs.ru)|84.201.157.208|:443... connected.

HTTP request sent, awaiting response... 200 OK

Length: 18312 (18K) [application/octet-stream]

Saving to: ‘db_installer.sh’

db_installer.sh               100%[=================================================>]  17,88K  --.-KB/s        in 0s          

 ‘db_installer.sh’ saved [18312/18312]

         5) Посмотрите разрешения на исполнение скрипта инсталляции:

root@tantor:/home/astra# ls -al db_installer.sh

-rw-r--r-- 1 root root 18353 db_installer.sh

         6) Если разрешений на исполнение файла нет, то дайте права на исполнение:

 root@tantor:/home/astra# chmod +x db_installer.sh

7) Проверьте версию инсталлятора и ознакомьтесь с параметрами:

 root@tantor:/home/astra# ./db_installer.sh --help

 ====================================================================

Usage: db_installer.sh [OPTIONS]

Installer version: 24.04.12 

This script will perform installation of the Tantor DB on current host.

If the Tantor DB is already installed, no actions will be taken.

Available options:

  --help                        Show this help message.

 

--------------------------------------------------------------------

  --edition=                    Set edition (be, se, se-1c, se-certified). "se" is default.

  --major-version=              Set major version (14, 15)

  --maintenance-version=        Set maintenance version (15.2.4).

                                By default latest version will be installed.

   --do-initdb                   After installation run initdb with checksums.

  --package=                    Set specific package (all, client, libpq5).

                                "all" is default.

 --------------------------------------------------------------------

  --from-file=                  Install package from local file (rpm, deb)

                                May be used with --do-initdb option

====================================================================

Example for commercial use

====================================================================

 export NEXUS_USER="user_name"

export NEXUS_USER_PASSWORD="user_password"

export NEXUS_URL="nexus.tantorlabs.ru"

./db_installer.sh \

        --do-initdb \

    --major-version=15 \

        --edition=se

====================================================================

Example for evaluation use (without login and password)

Only for Basic Edition

====================================================================

export NEXUS_URL="nexus-public.tantorlabs.ru"

./db_installer.sh \

        --do-initdb \

    --major-version=15 \

        --edition=be

====================================================================

Examples how to install from file

====================================================================

./db_installer.sh \

    --from-file=./packages/tantor-be-server-15_15.4.1.jammy_amd64.deb

./db_installer.sh \

        --do-initdb \ --from-file=/tmp/tantor-be-server-15_15.4.1.jammy_amd64.deb

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

8) Переустановите пароль пользователю postgres. Используйте пароль postgres

root@tantor:/home/astra# passwd postgres

New password: postgres

Retype new password: postgres

passwd: password updated successfully

9) Проверьте, что путь к исполняемым файлам был добавлен в файл профиля пользователя postgres. Переключитесь в пользователя postgres, который создаётся инсталлятором для запуска экземпляров кластеров. Параметр "-" заставляет исполнить файлы профилей того пользователя в которого идёт переключение.

 root@tantor:/home/astra# su - postgres

postgres@tantor:~$ cat .bash_profile

export PATH=/opt/tantor/db/16/bin:$PATH

export LANGUAGE=en_US.UTF-8

10) Добавьте путь к файлам кластера в переменную окружения, чтобы в будущем каждый раз не указывать ее параметром с названием "-D" утилитам. Команду вводить одной строкой, нужно использовать две угловые скобки:

postgres@tantor:~$ echo "export PGDATA=/var/lib/postgresql/tantor-se-16/data" >> .bash_profile

         11) Проверьте, что успешно и правильно добавили PGDATA в конец файла профиля.

postgres@tantor:~$ cat .bash_profile

export PATH=/opt/tantor/db/16/bin:$PATH

export LANGUAGE=en_US.UTF-8

export PGDATA=/var/lib/postgresql/tantor-se-16/data

12) Перечитайте файл профиля, который изменили:

postgres@tantor:~$ source .bash_profile

Часть 2. Создание кластера утилитой initdb

 1) Остановите два экземпляра кластера. Используйте утилиту pg_ctl.

postgres@tantor:~$ pg_ctl stop

waiting for server to shut down.... done

server stopped

postgres@tantor:~$ pg_ctl stop -D /var/lib/postgresql/tantor-se-16-replica/data

waiting for server to shut down.... done

server stopped

Можно было использовать команду управления службами запускаемыми инфраструктурой systemd: sudo systemctl stop tantor-se-server-16

При запуске командой systemctl сначала проверяется, что директория PGDATA «похожа» на директорию кластера утилитой postgresql-check-db-dir и потом используется pg_ctl start.

Откройте текстовым редактором (kate) файл /usr/lib/systemd/system/tantor-se-server-16.service или следующей командой найдите строки, где указаны утилиты, которые вызываются, при запуске, остановке или обновлении (reload) службы. Обновление - это не перезагрузка (restart).

2) Посмотрите содержимое файла описателя службы:

postgres@tantor:~$ cat /usr/lib/systemd/system/tantor-se-server-16.service | grep /opt

ExecStartPre=/opt/tantor/db/16/bin/postgresql-check-db-dir ${PGDATA}

ExecStart=/opt/tantor/db/16/bin/pg_ctl start -D ${PGDATA} -s -w -t ${PGSTARTTIMEOUT}

ExecStop=/opt/tantor/db/16/bin/pg_ctl stop -D ${PGDATA} -s -m fast

ExecReload=/opt/tantor/db/16/bin/pg_ctl reload -D ${PGDATA} -s

Режим остановки по умолчанию fast.

Если экземпляр запускался утилитой pg_ctl, а не через systemd, то systemctl не остановит экземпляр. При этом, pg_ctl останавливает экземпляр, запущенный любым способом. Поэтому рекомендуется останавливать экземпляр утилитой pg_ctl.

Запускать экземпляр лучше через systemctl. При запуске экземпляра через сетевое соединение (подсоединившись по ssh) утилитой pg_ctl, экземпляр принудительно остановится после закрытия сетевого соединения (по ssh). Также при запуске через pg_ctl нужно настроить вывод журнала сообщений в файл, а не на экран терминала.

3) Дайте ещё раз команду остановки экземпляра, если экземпляр запущен он становится, если не запущен утилита об этом сообщит:

 postgres@tantor:~$ pg_ctl stop

pg_ctl: PID file "/var/lib/postgresql/tantor-se-16/data/postmaster.pid" does not exist

Is server running?

4) Сохраните директорию кластера:

postgres@tantor:~$ mkdir $PGDATA/../data.SAVE

postgres@tantor:~$ mv $PGDATA/* $PGDATA/../data.SAVE

postgres@tantor:~$ chmod 750 $PGDATA/../data.SAVE 

5) Далее создайте кластер. Для создания кластера используется утилита initdb. Утилите передаются параметры и она реагирует на переменные окружения, в частности, относящиеся к локализации (но не только). Запустите утилиту без параметров (со значениями по умолчанию):

postgres@tantor:~$ initdb

The files belonging to this database system will be owned by user "postgres".

This user must also own the server process.

The database cluster will be initialized with locale "en_US.UTF-8".

The default database encoding has accordingly been set to "UTF8".

The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /var/lib/postgresql/tantor-se-16/data ... ok

creating subdirectories ... ok

selecting dynamic shared memory implementation ... posix

selecting default max_connections ... 100

selecting default shared_buffers ... 128MB

selecting default time zone ... Europe/Moscow

creating configuration files ... ok

running bootstrap script ... ok

performing post-bootstrap initialization ... ok

syncing data to disk ... ok

initdb: warning: enabling "trust" authentication for local connections

initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

pg_ctl -D /var/lib/postgresql/tantor-se-16/data -l logfile start

6) Прочитайте результат. Для этого можно использовать клавиши на клавиатуре <Shift+PgUp> <Shift+PgDown>. Обратите внимание на то, что по умолчанию подсчет контрольных сумм не включен.

Также выдаются параметры локализации с которыми создан кластер.

7) Проверьте утилитой pg_controldata, что подсчет контрольных сумм не включён:

postgres@tantor:~$ pg_controldata

pg_control version number:                1300

Catalog version number:                   202307071

Database system identifier:               7340951136757174317

Database cluster state:                   shut down

pg_control last modified:                 12:19:38

Latest checkpoint location:               0/1514AB0

Latest checkpoint's REDO location:        0/1514AB0

Latest checkpoint's REDO WAL file:        000000010000000000000001

Latest checkpoint's TimeLineID:           1

Latest checkpoint's PrevTimeLineID:   1

Latest checkpoint's full_page_writes: on

Latest checkpoint's NextXID:          731

Latest checkpoint's NextOID:              13602

Latest checkpoint's NextMultiXactId:  1

Latest checkpoint's NextMultiOffset:  0

Latest checkpoint's oldestXID:            723

Latest checkpoint's oldestXID's DB:   1

Latest checkpoint's oldestActiveXID:  0

Latest checkpoint's oldestMultiXid:   1

Latest checkpoint's oldestMulti's DB: 1

Latest checkpoint's oldestCommitTsXid:0

Latest checkpoint's newestCommitTsXid:0

Time of latest checkpoint:                12:19:38

Fake LSN counter for unlogged rels:   0/3E8

Minimum recovery ending location:         0/0

Min recovery ending loc's timeline:   0

Backup start location:                    0/0

Backup end location:                      0/0

End-of-backup record required:            no

wal_level setting:                        replica

wal_log_hints setting:                    off

max_connections setting:                  100

max_worker_processes setting:             8

max_wal_senders setting:                  10

max_prepared_xacts setting:               0

max_locks_per_xact setting:               64

track_commit_timestamp setting:           off

Maximum data alignment:                   8

Database block size:                      8192

Blocks per segment of large relation: 131072

WAL block size:                           8192

Bytes per WAL segment:                    16777216

Maximum length of identifiers:            64

Maximum columns in an index:              32

Maximum size of a TOAST chunk:            1996

Size of a large-object chunk:             2048

Date/time type storage:                   64-bit integers

Float8 argument passing:                  by value

Data page checksum version:               0

Mock authentication nonce:           0d18c599c7876e965a894cd059b60c1307f5e1a959703351495b0193f729174a

8) Найдите в результате информацию о том, что экземпляр кластера не был запущен или корректно погашен. Это строка:

Database cluster state:                   shut down

         9) Посмотрите параметры командной строки утилиты pg_checksum:

postgres@tantor:~$ pg_checksums --help

pg_checksums enables, disables, or verifies data checksums in a PostgreSQL database cluster.

Usage:

  pg_checksums [OPTION]... [DATADIR]

Options:

 [-D, --pgdata=]DATADIR        data directory

  -c, --check                  check data checksums (default)

  -d, --disable                disable data checksums

  -e, --enable                 enable data checksums

  -f, --filenode=FILENODE  check only relation with specified filenode

  -N, --no-sync                do not wait for changes to be written safely to disk

  -P, --progress               show progress information

  -v, --verbose                output verbose messages

  -V, --version                output version information, then exit

  -?, --help                   show this help, then exit

If no data directory (DATADIR) is specified, the environment variable PGDATA

is used.

         Утилита может включать подсчет контрольных сумм на кластере.

10) Включите подсчет контрольных сумм. Параметр -v использовать не стоит, так как с ним выводится список всех файлов кластера, а их много.

postgres@tantor:~$ pg_checksums -e

Checksum operation completed

Files scanned:   948

Blocks scanned:  2817

Files written:  780

Blocks written: 2817

pg_checksums: syncing data directory

pg_checksums: updating control file

Checksums enabled in cluster

11) Параметр -c проверяет блоки в существующих файлах данных на соответствие контрольным суммам, которые сохранены в их блоках.

Проверьте целостность файлов данных кластера:

postgres@tantor:~$ pg_checksums -c

Checksum operation completed

Files scanned:   948

Blocks scanned:  2817

Bad checksums:  0

Data checksum version: 1

12) Запустите экземпляр кластера:

postgres@tantor:~$ pg_ctl start

waiting for server to start....

LOG:  starting PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

LOG:  listening on IPv4 address "127.0.0.1", port 5432

LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"

LOG:  database system was shut down at 13:25:56 MSK

LOG:  database system is ready to accept connections

 done

server started

Экземпляр использует порт 5432 для UnixSockets и на локальном сетевом интерфейсе.

13) Также можно запустить с помощью команды sudo systemctl start tantor-se-server-16

Стоит использовать запуск с помощью systemctl. При запуске командой pg_ctl start, которую мы использовали сообщения выводятся в error output stream, по умолчанию направленный на терминал пользователя операционной системы postgres.

Проверьте это:

postgres@tantor:~$ psql -c "\dconfig log_destination"

List of configuration parameters

        Parameter        | Value

-----------------+--------

 log_destination | stderr

(1 row)

При запуске с помощью systemd значение параметра то же (log_destination=stderr), но error output stream направлен на журнал операционной системы или процесс syslog (текстовый файл /var/log/syslog куда собираются все сообщения от процессов операционной системы).

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


Часть 3. Режим одного пользователя

1) Посмотрим использование режима одного пользователя. Он используется в редких случаях.

Остановите экземпляр кластера:

postgres@tantor:~$ pg_ctl stop

waiting for server to shut down.... done

server stopped

2) Запустите один процесс, который будет принимать наши команды в одной сессии:

postgres@tantor:~$ postgres --single

PostgreSQL stand-alone backend 16.1

3) Появится промпт. Команды типа SELECT выдают результат не в обычном виде, а с диагностическими данными. Также команды необязательно завершать и посылать на выполнение символом ';'.

Дайте команду SELECT:

backend> select tantor_version()

            1: tantor_version        (typeid = 25, len = -1, typmod = -1, byval = f)

           ----

            1: tantor_version = "Tantor Special Edition 16.1.0"           (typeid = 25, len = -1, typmod = -1, byval = f)

           ----

4) Дайте команду VACUUM FULL:

backend> vacuum full

         5) Для выхода из сессии нужно набрать на клавиатуре комбинацию клавиш <Ctrl+D>. Команды psql (начинающиеся на обратный слэш, например команда выхода из psql "\q") и их синонимы (quit, exit котрые являются синонимами \q) не работают, так как мы работаем не в утилите psql.

Отсоединитесь от кластера, набрав комбинацию клавиш <Ctrl+D>:

backend> <Ctrl+D> LOG:  checkpoint starting: shutdown immediate

LOG:  checkpoint complete: wrote 145 buffers (0.9%); 0 WAL file(s) added, 0 removed, 1 recycled; write=0.007 s, sync=0.070 s, total=0.086 s; sync files=283, longest=0.012 s, average=0.001 s; distance=5719 kB, estimate=5719 kB; lsn=0/208C110, redo lsn=0/208C110

postgres@tantor:~$

6) Запустите экземпляр из-под пользователя root:

postgres@tantor:~$ su -

Password: root

root@tantor:~# systemctl start tantor-se-server-16

root@tantor:~#

7) Выйдите из терминала root (вместо exit можно набрать комбинацию клавиш <Ctrl+D>):

root@tantor:~# exit

logout

postgres@tantor:~$

8) Остановите экземпляр. Независимо от того как он запускался его можно остановить утилитой pg_ctl:

postgres@tantor:~$ pg_ctl stop

waiting for server to shut down.... done

server stopped

Часть 4.  Передача параметров экземпляру в командной строке

1) Посмотрим как передавать параметры конфигурации для запуска экземпляра в командной строке. Установим параметр work_mem в значение 8 мегабайт. Часть параметров конфигурации можно задать только передав их в командной строке.

Запустите следующую команду:

postgres@tantor:~$ pg_ctl start -o "--work_mem=8MB"

waiting for server to start....

[19479] LOG:  starting PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

[19479] LOG:  listening on IPv6 address "::1", port 5432

[19479] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"

[19482] LOG:  database system was shut down at 13:59:08 MSK

[19479] LOG:  database system is ready to accept connections

2) Проверьте, что параметр установился:

postgres@tantor:~$ psql -c "show work_mem"

 work_mem

----------

 8MB

(1 row)

Часть 5. Локализация 

1) После создания кластера проверим, удовлетворительно ли работает сортировка:

postgres=# SELECT n FROM unnest(ARRAY['а', 'е', 'ё', 'Ж', 'я', 'Ё','Е']) n ORDER BY n;

 n

---

 а

 е

 Е

 ё

 Ё

 Ж

 я

(7 rows) 

postgres=# SELECT n FROM unnest(ARRAY['а', 'е', 'ё', 'Ж', 'я', 'Ё','Е']) n ORDER BY n COLLATE "ru-x-icu";

 n

---

 а

 е

 Е

 ё

 Ё

 Ж

 я

(7 rows)

2)  Посмотрим, какие типы сортировок поддерживала операционная система при создании кластера:

postgres=# select collname from pg_collation where collname like '%ru%RU%';

  collname  

-------------

 ru_RU.utf8

 ru_RU

 ru-RU-x-icu

(3 rows)

Часть 6. Однобайтные кодировки

Дальше приводится пример если в операционной системе было установлено больше вариантов локализации. Команды приведенные ниже в этом пункте не нужно выполнять, с ними можно ознакомиться:

1) postgres=# select collname from pg_collation where collname like '%ru%RU%';

    collname        

----------------

 ru_RU

 ru_RU.cp1251

 ru_RU.iso88595

 ru_RU.utf8

 ru_RU

 ru_RU

 ru-RU-x-icu

(7 rows)

2) Создание базы данных с другим типом сортировки:

postgres=# create database lab01iso88595 LC_COLLATE = 'ru_RU.iso88595';

ERROR:  encoding "UTF8" does not match locale "ru_RU.iso88595"

DETAIL:  The chosen LC_COLLATE setting requires encoding "ISO_8859_5".

Ошибка указывает на то, что сортировка связана с кодировкой.

         3) Укажем кодировку:

postgres=# create database lab01iso88595 LC_COLLATE = 'ru_RU.iso88595' ENCODING='ISO_8859_5';

ERROR:  encoding "ISO_8859_5" does not match locale "en_US.UTF-8"

DETAIL:  The chosen LC_CTYPE setting requires encoding "UTF8".

 Ошибка указывает на то, что ctype также связан с кодировкой.

         4) Попробуем ещё:

postgres=# create database lab01iso88595 LC_COLLATE = 'ru_RU.iso88595' LC_CTYPE='ru_RU.iso88595';

ERROR:  encoding "UTF8" does not match locale "ru_RU.iso88595"

DETAIL:  The chosen LC_CTYPE setting requires encoding "ISO_8859_5".

Убеждаемся, что выбранный ctype требует задания кодировки для создаваемой базы данных.

         5) Укажем все три параметра:

postgres=# create database lab01iso88595 LC_COLLATE = 'ru_RU.iso88595' LC_CTYPE='ru_RU.iso88595' ENCODING='ISO_8859_5';

ERROR:  new encoding (ISO_8859_5) is incompatible with the encoding of the template database (UTF8)

HINT:  Use the same encoding as in the template database, or use template0 as template.

Ошибка указывает на то, что база данных template1 не может использоваться, единственный шаблон, который может использоваться это template0.

         6) Укажем имя шаблона:

postgres=# create database lab01iso88595 LC_COLLATE = 'ru_RU.iso88595' LC_CTYPE='ru_RU.iso88595' ENCODING='ISO_8859_5' TEMPLATE= template0;

CREATE DATABASE

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

7) Подключимся к новой базе данных и проверим правильно ли работает сортировка с однобайтной кодировкой. Зададим её явно, но можно было не указывать, так как для этой базы данных это значение сортировки используется по умолчанию:

postgres=# \c lab01iso88595

You are now connected to database "lab01iso88595" as user "postgres".

lab01iso88595=# SELECT n FROM unnest(ARRAY['а', 'е', 'ё', 'Ж', 'я', 'Ё','Е']) n ORDER BY n COLLATE "ru_RU.iso88595";

 n

---

 а

 е

 Е

 ё

 Ё

 Ж

 я

(7 rows)

Работает правильно, так же как и с кодировкой UTF-8.

Часть 7. Использование утилит управления

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

1) Посмотрите параметры утилиты создания баз данных. У утилит командной строки Линукс принято иметь параметр (ключ) с названием --help или -h с кратким описанием параметров.

 postgres@tantor:~$ createdb --help

Создайте базу данных с именем lab01database:

 postgres@tantor:~$ createdb lab01database

Ошибки не выдано, значит база данных создана.

2) Посмотрите список баз данных кластера и их табличных пространств по умолчанию утилитой oid2name. Проверьте, что база данных lab01database есть в списке:

postgres@tantor:~$ oid2name

All databases:

        Oid  Database Name  Tablespace

----------------------------------

  16798  lab01database  pg_default

  16797  lab01iso88595  pg_default

          5           postgres  pg_default

          4          template0  pg_default

          1          template1  pg_default

         3) Создайте пользователя с именем lab01user, с таким же паролем и с атрибутами позволяющими подсоединяться к базам данных кластера и атрибутом суперпользователя:

postgres@tantor:~$ createuser lab01user --login --superuser -P

Enter password for new role: lab01user

Enter it again: lab01user

postgres@tantor:~$ 

4) Запустите утилиту выгрузки данных из кластера и в режиме выгрузки глобальных объектов: Глобальные объекты - это общие объекты для всех баз данных кластера. По умолчанию утилита выводит создаваемые команды в stdout (на экран терминала).

postgres@tantor:~$ pg_dumpall -g

--

-- PostgreSQL database cluster dump

--

SET default_transaction_read_only = off;

SET client_encoding = 'UTF8';

SET standard_conforming_strings = on;

--

-- Roles

--

CREATE ROLE lab01user;

ALTER ROLE lab01user WITH SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN NOREPLICATION NOBYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:Z...9;;

CREATE ROLE postgres;

ALTER ROLE postgres WITH SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN REPLICATION BYPASSRLS; 

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

5) Выполните вакуумирование всех баз данных и заморозку строк

postgres@tantor:~$ vacuumdb -a -F

vacuumdb: vacuuming database "lab01database"

vacuumdb: vacuuming database "lab01iso88595"

vacuumdb: vacuuming database "postgres"

vacuumdb: vacuuming database "template1" 

6) Проверьте, что кластер запущен и принимает соединения 

postgres@tantor:~$ pg_isready

/var/run/postgresql:5432 - accepting connections

Часть 8. Настройка терминального клиента psql

1) Проверьте, что вы в терминале пользователя postgres посмотрев промпт терминала командной строки:

postgres@tantor:~$

2) Запустите psql и выйдите из интерактивного режима утилиты. Для выхода можно использовать команду \q либо комбинацию клавиш <Ctrl+D> либо quit либо exit. 

postgres@tantor:~$ psql

psql (16.1)

Type "help" for help.

postgres=# \q

postgres@tantor:~$ 

Обратите внимание на приглашения (prompt) psql и терминала, они отличаются. Это пригодится, чтобы не вводить SQL команды в терминале операционной системы и наоборот.

         3) Настройте редактор, который будет вызываться при редактировании процедур, функций, представлений в терминальном клиенте psql.

Выполните команду записи строки в .psqlrc лежащим в домашнем каталоге пользователя (тильда ~):

postgres@tantor:~$ echo "\setenv PAGER 'less -XS'" > ~/.psqlrc

postgres@tantor:~$ echo "\setenv PSQL_EDITOR /usr/bin/mcedit" >> ~/.psqlrc

4) Проверьте, что в файле появилась строка, которую вставили в предыдущем пункте: 

postgres@tantor:~$ cat ~/.psqlrc

\setenv PAGER 'less -XS'

\setenv PSQL_EDITOR /usr/bin/mcedit

postgres@tantor:~$

Возможно использование и графических редакторов. В AstraLinux стандартно установлен графический редактор kate. Однако, если вы используете утилиту su для переключения терминала в другого пользователя операционной системы, графический редактор не запустится. В этом случае можно вместо su использовать команды приведенные ниже. Команды в этом пункте приведены для справки и их не нужно выполнять.

 postgres@tantor:~$ exit

logout

root@tantor:/home/astra# exit

exit

astra@tantor:~$ ssh -X postgres@localhost

The authenticity of host 'localhost (::1)' can't be established.

ECDSA key fingerprint is SHA256:12VsUcC5hw5I1zr015AJ8C+xsN0m5h+IlU2M/xdNg6o.

Are you sure you want to continue connecting (yes/no/[fingerprint])? yes

Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.

postgres@localhost's password: postgres

/usr/bin/xauth:  file /var/lib/postgresql/.Xauthority does not exist

postgres@tantor:~$ export PSQL_EDITOR=kate

postgres@tantor:~$ pg_ctl stop

postgres@tantor:~$ sudo systemctl start tantor-se-server-16

Убрать из ~/.psqlrc строку \setenv PSQL_EDITOR /usr/bin/mcedit

         5) Запустите psql:

postgres@tantor:~$ psql

psql (16.1)

Type "help" for help.

 postgres=# 

Подключившись по ssh не стоит запускать экземпляр утилитой pg_ctl stat, так как после закрытия соединения ssh экземпляр остановится. Причина - родительский процесс, запустивший процесс postgres останавливается. При подсоединении по ssh запускать экземпляр стоит командой sudo systemctl start tantor-se-server-16. 

6) Посмотрите подсказку по командам psql набрав команду \? и промотайте нажатием клавиши <Enter> на клавиатуре до подраздела Query Buffer:

postgres=# \?

...

Query Buffer

  \e [FILE] [LINE]           edit the query buffer (or file) with external editor

  \ef [FUNCNAME [LINE]]  edit function definition with external editor

  \ev [VIEWNAME [LINE]]  edit view definition with external editor

  \p                         show the contents of the query buffer

  \r                         reset (clear) the query buffer

  \s [FILE]                  display history or save it to file

  \w FILE                    write query buffer to file

Можно пользоваться клавишами z - промотать экран вверх b - промотать экран вниз q - выйти.

Также можно проматывать буфер терминала клавишами <Shift+PgUp> <Shift+PgDn>.

7) Если удобнее читать подсказку на русском языке установите переменную окружения LC_MESSAGES, которой устанавливается язык сообщений утилит. Это можно сделать на уровне терминала настройка и будет действовать до тех пор пока не закроете терминал.

Нажмите на клавиатуре комбинацию клавиш <Ctrl+D> (или наберите команду \q и клавишу <Enter>). Удобно использовать <Ctrl+D>, так как она универсальна и более быстро набирается.

Наберите команду:

postgres@tantor:~$ export LC_MESSAGES=ru_RU.utf8

postgres@tantor:~$ unset LANGUAGE

8) Если вы хотите чтобы настройка действовала постоянно, то наберите команды:

postgres@tantor:~$ cp .bash_profile .bash_profile.OLD

postgres@tantor:~$ echo "export LC_MESSAGES=ru_RU.utf8" >> ~/.bash_profile

postgres@tantor:~$ echo "unset LANGUAGE" >> ~/.bash_profile

9) Если вместо символов ">>" набрать ">" то содержимое файла затрётся. Двойной символ добавляет строку в конец файла. В домашнем каталоге может присутствовать файл .profile  Этот файл неудобен тем, что если в домашнем каталоге присутствует файл .bash_profile или .bash_login то файл .profile не действует.

Запустите psql и повторите команду \?. История команд сохраняется и можно набирая на клавиатуре стрелку вверх или вниз выбирать команды из истории, а нажимая <Enter> их повторять. 

postgres@tantor:~$ psql

psql (16.1)

Введите "help", чтобы получить справку.

postgres=# \?

Буфер запроса

\e [ФАЙЛ] [СТРОКА]   править буфер запроса (или файл) во внешнем редакторе

\ef [ФУНКЦИЯ [СТРОКА]] править определение функции во внешнем редакторе

\ev [VIEWNAME [LINE]] править определение представления во внешнем редакторе

  \p                         вывести содержимое буфера запросов

  \r                         очистить буфер запроса

  \s [ФАЙЛ]                  вывести историю или сохранить её в файл

  \w ФАЙЛ                    записать буфер запроса в файл

:q 

Если вы хотите прервать отображение подсказки нажмите клавишу "q" 

10) Прочтите выделенный текст. Команды \p \r обычно забывают или не знают о них, но они полезны.

Как psql взаимодействует с программой редактора? Когда вы набираете команды \e \ef \ev запускается редактор и ему psql передает текст того, что хотите редактировать и путь к временному файлу который вы обычно не видите. В примере ниже название файла высвечивается на первой строке картинки как /tmp/psql/edit.6652.sql

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

Нюанс: если в конце команды когда вы находились в редакторе вы не поставили точку с запятой и переход на новую строку в конце набранной или редактируемой команды или уже находясь в psql вы её не набрали, то команда не пошлётся на выполнение и вы будете продолжать заполнять буфер. Этот нюанс может затруднять использование команд \e \ef и \ev склоняя к использованию графических средств типа pgAdmin.

         11) Вызовите редактор создания представления командой \ev наберите команду как показано ниже, нажмите клавишу F2 (сохранить) F10 (выйти). При желании, вы можете выбрать редактор который вам удобнее. В редакторе kate который возможно использовать а AstraLinux быстрые комбинации клавиш <Ctrl+S> - сохранить <Ctrl+Q> - выйти из редактора.

postgres=# \ev

CREATE VIEW

12) Командой \p посмотрите последнюю команду. Команда была получена psql из редактора.

После посылки команд на выполнение: 

postgres=# \p

CREATE VIEW lab01view  AS

SELECT now();

13) Также можно посмотреть определение представление или подпрограммы (routines к которым относятся процедуры и функции):

  \sf[+]  ИМЯ_ФУНКЦИИ        показать определение функции

  \sv[+]  ИМЯ_ПРЕДСТ         показать определение представления

         Наберите:

\sv l<TAB><ENTER>

Где <TAB> клавиша табуляции, <ENTER> тоже клавиша на клавиатуре.

После нажатия клавиши <TAB> psql дополнит название представления. Если представлений начинающихся на букву 'l' много (или вообще нет таких), то не дополнит. В этом случае второе нажатие клавиши <TAB> высветит список кандидатов. Вы сможете набрать еще несколько символов и снова нажать <TAB> и потом послать на выполнение то что набрали нажав клавишу <ENTER>.

postgres=# \sv lab01view

CREATE OR REPLACE VIEW public.lab01view AS

SELECT now() AS now

postgres=#

Обратите внимание, что точки с запятой в конце команды нет. При открытии редактора она также не появится в редакторе и ;<ENTER> нужно будет набирать вручную.

         Обратите внимание что ";" отсутствует.

Мы рассмотрели детали работы самого неочевидного функционала psql - взаимодействия с редактором. Остальная информация гораздо проще.

Часть 9. Использование терминального клиента psql

         1) Выполните команды:

postgres=# BEGIN TRANSACTION;

BEGIN

2) Мы начали транзакцию. Обратите внимание что промпт изменился - появился символ звездочки. В psql с промптом по умолчанию вы видите если ли открытая транзакция чтобы принять решение зафиксировать ли ее.

Дальше наберем команду в несколько строк.

Наберите SELECT:

postgres=*# select

3) Обратите внимание промпт опять изменился, вместо символа равенство появилось тире. Донаберите команду и завершите команду точкой с запятой:

postgres-*# tantor_version();

             tantor_version             

-------------------------------

 Tantor Special Edition 16.1.0

(1 row)

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

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

postgres=*# ffff;

ERROR:  syntax error at or near "ffff"

LINE 1: ffff;

Выдалась ошибка синтаксиса. Обратите внимание на то что вместо звёздочки означающей открытую транзакцию появился символ восклицательного знака. Это означает, что транзакция ещё открыта, но она перешла в состояние сбоя, а в таком состоянии транзакция не может зафиксироваться, а только откатиться. В состояние сбоя транзакции переходят нечасто, а только после некоторых ошибок, которые считаются серьезными настолько, что зафиксировать операторы, накопившиеся в транзакции нельзя. Например, ошибки сериализации доступа. Что опасного в команде "ffff"? Её получает серверный процесс и видит что это что-то совсем дикое, не может же программист написать такую команду. Серверный процесс рассчитывает что ему даёт команды приложение написанное программистом и оттестированное. Поэтом считает что нужно перевести транзакцию в состояние сбоя.

         5) Проверим, что если послать на выполнение правильную команду. Наберите:

postgres=!# select 1;

ERROR:  current transaction is aborted, commands ignored until end of transaction block

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

6)Завершаем транзакцию одной из двух команд завершения транзакций:

postgres=!# COMMIT;

ROLLBACK

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

Есть параметр ON_ERROR_ROLLBACK, он позволяет не терять результаты выполненных команд. Этот параметр заставляет psql устанавливать после каждой команды точку сохранения (SAVEPOINT), что нежелательно, так как увеличивает использование счетчика транзакций (xid). Если его устанавливать, то лучше в значение INTERACTIVE, тогда точки сохранения будут устанавливаться, если работать в psql интерактивно.

         7) Установите этот параметр:

postgres=# \set ON_ERROR_ROLLBACK INTERACTIVE 

8) Повторите команды из предыдущего примера:

postgres=# BEGIN;

BEGIN

postgres=*# select 1;

 ?column?

----------

            1

(1 строка)

postgres=*# ffff;

ERROR:  syntax error at or near "ffff"

СТРОКА 1: ffff;

              ^

postgres=*# COMMIT;

COMMIT

Транзакция была закрыта фиксацией, а не откатом.

9) Посмотрим как psql обрабатывает свои команды - что он посылает серверному процессу, чтобы вывести красивый результат. Посмотрим какие роли есть в кластере. На английском языке это бы звучало «describe user», сокращения по первым буквам слов «du». Добавим обратный слэш - общее начало всех команд утилиты psql. Если без обратного слэша, это команда SQL и посылается на выполнение серверному процессу как текст. Для посылки на выполнение используется точка с запятой ";" иначе как psql узнать что вы завершили набирать команду.

Наберите:

postgres=# \du

                                    Список ролей

 Имя роли  |                                Атрибуты                                

-----------+-------------------------------------------------------------------------

 lab01user | Суперпользователь, Создаёт роли, Создаёт БД

 postgres  | Суперпользователь, Создаёт роли, Создаёт БД, Репликация, Пропускать RLS

10) Установите параметр psql который покажет нам какую команду psql сам формирует и посылает на выполнение:

postgres=# \set ECHO_HIDDEN on

11) Повторите команду:

postgres=# \du

********* ЗАПРОС *********

SELECT r.rolname, r.rolsuper, r.rolinherit,

  r.rolcreaterole, r.rolcreatedb, r.rolcanlogin,

  r.rolconnlimit, r.rolvaliduntil

, r.rolreplication

, r.rolbypassrls

FROM pg_catalog.pg_roles r

WHERE r.rolname !~ '^pg_'

ORDER BY 1;

**************************

                                           Список ролей

 Имя роли  |                                Атрибуты                                

-----------+-------------------------------------------------------------------------

 lab01user | Суперпользователь, Создаёт роли, Создаёт БД

 postgres  | Суперпользователь, Создаёт роли, Создаёт БД, Репликация, Пропускать RLS

12) Скопируйте и вставьте (copy-paste) текст команды. Для этого можно использовать комбинации клавиш <Ctrl+Shift+c> <Ctrl+Shift+v>

 postgres=# SELECT r.rolname, r.rolsuper, r.rolinherit,

  r.rolcreaterole, r.rolcreatedb, r.rolcanlogin,

  r.rolconnlimit, r.rolvaliduntil

, r.rolreplication

, r.rolbypassrls

FROM pg_catalog.pg_roles r

WHERE r.rolname !~ '^pg_'

ORDER BY 1;

 rolname  | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolconnlimit | rolvaliduntil | rolreplication | rolbypassrls

-----------+----------+------------+---------------+-------------+-------------+--------------+---------------+----------------+--------------

 lab01user | t        | t          | t             | t           | t           |           -1 |               | f              | f

 postgres  | t        | t          | t             | t           | t           |           -1 |               | t              | t

(2 строки)

Видно, какую информацию получает psql и можно сравнить с тем, как он интеллектуально ее отображает: psql не отобразил атрибуты INHERIT и LOGIN. Почему? Потому что это значения по умолчанию при создании роли. Значения по умолчанию не отображаются. Будут отображаться их обратные значения «Не наследуется, Вход запрещён». Эта особенность интуитивна непонятна, поэтому мы остановились на ней подробно.

13) Посмотрите командой \? справку по команде \connect (сокращённая версия команды  )

Соединение:

  \c[onnect] {[БД|- ПОЛЬЗОВАТЕЛЬ|- СЕРВЕР|- ПОРТ|-] | conninfo}

                             подключиться к другой базе данных

                             (текущая: "postgres")

  \conninfo                  информация о текущем соединении

14) Попробуйте различные комбинации подсоединения. Клавиша табуляции позволяет заканчивать параметр, так как psql в текущем подсоединении имеет доступ к списку имён баз данных и пользователей. Цель этой последовательности соединений - запомнить порядок следования параметров команды \c: база пользователь хост порт. Если какой-то параметр хотите оставить прежним, то замените его на тире. <TAB><ENTER> - клавиши табуляции и возврат каретки (новая строка) на клавиатуре.

 postgres=# \c la<TAB><ENTER>

Вы подключены к базе данных "lab01database" как пользователь "postgres".

lab01database=# \c - la<TAB><ENTER>

Вы подключены к базе данных "lab01database" как пользователь "lab01user".

lab01database=# \c - - localhost

Сейчас вы подключены к базе данных "lab01database", как пользователь "lab01user" (сервер "localhost": адрес "127.0.0.1", порт "5432").

lab01database=# \c - - - 5432

Вы подключены к базе данных "lab01database" как пользователь "lab01user".

lab01database=# \c postgres p<TAB><ENTER>

Вы подключены к базе данных "postgres" как пользователь "postgres".

15) Посмотрим, как получать результат выборки в формате веб-страницы и просматривать ее в браузере. Откройте новое окно терминала.

16) Запустите psql:

postgres@education:~$ psql

psql (16.1)

Type "help" for help.

         17) Установите формат вывода HTML:

postgres=# \pset format html

Output format is html.

         18) Перенаправьте вывод в файл с названием file.html:

postgres=# \o file.html

         19) Дайте любую команду, результат которой неудобно читать в терминале:

postgres=# show all; 

20) Отключите вывод в файл:

postgres=# \o

         21) Запустите на выходя их psql окно браузера:

postgres=# \! xdg-open file.html

         22) Подождите пока не запустится окно браузера. Закройте psql:

postgres=# \q 

23) Закройте окно терминала:

postgres@tantor:~$ <CTRL+d>

24) Закройте окно браузера и вернитесь в окно psql. Посмотрим какие ещё есть форматы вывода. Наберите в psql:

 postgres=# \pset format aaa

\pset: allowed formats are aligned, asciidoc, csv, html, latex, latex-longtable, troff-ms, unaligned, wrapped

25) Выберите формат aligned, он используется по умолчанию:

postgres=# \pset format aligned

Output format is aligned.

         26) Выполните команду

postgres=# SHOW ALL;

В конце экрана высветится двоеточие. Нажмите на клавиатуре клавиши z z b q и посмотрите эффект.

z - следующая страница, b - предыдущая, q - завершить вывод и вернуть промпт.

27) Выполните команду:

postgres=# \pset format wrapped

Output format is wrapped.

28) Выполните команду

postgres=# SHOW ALL;

Нажмите на клавиатуре клавиши z z b h  Прочтите описание доступных клавиш. Закрепите навыки проматывания результата.

29) Сравните отличия. Возможно, формат wrapped (перенос по словам) будет удобнее aligned.

30) Проверим как можно не выходя из psql выполнять команды операционной системы. Команда Линукс "pwd" показывает текущую директорию.

Выполните команду "pwd" или "ls" (выдаёт список файлов) без выхода из psql:

postgres=# \! pwd

/var/lib/postgresql        

31) Установите цветной промпт, который будет высвечивать номер (pid) серверного процесса серым цветом:

\set PROMPT1 '%[%033[0;90m%][%p]%[%033[0m%] %[%033[0;31m%]%n%[%033[0m%]@%[%033[0;34m%]%m%[%033[0m%]:%[%033[0;32m%]%>%[%033[0m%] %[%033[0;36m%]%~%[%033[0m%] %[%033[0;33m%]%[%033[5m%]%x%[%033[0m%]%[%033[0m%]%R%# '

\set PROMPT2 '%[%033[0;90m%][%l]%[%033[0m%] %[%033[0;31m%]%n%[%033[0m%]@%[%033[0;34m%]%m%[%033[0m%]:%[%033[0;32m%]%>%[%033[0m%] %[%033[0;36m%]%~%[%033[0m%] %[%033[0;33m%]%[%033[5m%]%x%[%033[0m%]%[%033[0m%]%R%# '

Статус транзакции отображается мигающими символами * и ! привлекая внимание.

Справка что означают символы если захотите создать свой промпт:

%p номер серверного процесса

%n роль. (может изменяться во время сессии командой SET SESSION AUTHORIZATION;)

%m имя хоста или [local], если соединение осуществляется через Unix-сокет

%> номер порта экземпляра

%/ имя базы данных

%~ имя базы данных. Если это база данных по умолчанию, то вместо имени отображается ~

%# для суперпользователя символ #, для остальных ролей символ >%l номер строки в буфере ввода.

%R для PROMPT1 отображает = если сессия находится в неактивной ветви условного блока @ в режиме однострочного ввода ^ если сессия отключена от базы данных !

для PROMPT2 если команда не завершена &ndash;

если есть незакрытый комментарий * если есть незакрытая кавычка то '

если есть незавершенная двойная кавычка то "

если есть начатая, но незавершенная $строка$ (обычно при наборе текста функций) то $

если есть левая скобка а правая скобка не введена, то (

Символы, которые отображает PROMPT2 важны тем, что забыв набрать закрывающий апостроф сколько <ENTER> ; или \r не набирайте никакой реакции не будет, пока не наберёте апостроф:

Если нужно отобразить роль и базу:

\set PROMPT1 '%[%033[0;31m%]%n%[%033[0m%]@%[%033[0;36m%]%/%[%033[0m%] %[%033[0;33m%]%[%033[5m%]%x%[%033[0m%]%[%033[0m%]%R%# '

\set PROMPT2 '%[%033[0;31m%]%n%[%033[0m%]@%[%033[0;36m%]%/%[%033[0m%] %[%033[0;33m%]%[%033[5m%]%x%[%033[0m%]%[%033[0m%]%R%# '

         32) Посмотрите, как по умолчанию выводится результат запроса:

  usename   | usesysid | usecreatedb | usesuper | userepl | usebypassrls |  passwd  | valuntil | useconfig

------------+----------+-------------+----------+--------

 postgres   |       10 | t           | t        | t       | t            | ******** |          |

 replicator |    16388 | f           | f        | t       | f            | ******** |          |

 pma_user   |    16528 | f           | t        | f       | f            | ******** |          |

(3 строки)

33) Установите стиль отрисовки линий символами unicode:

postgres=# \pset linestyle unicode

Установлен стиль линий: unicode.

Повторим запрос (нажмите два раза на клу <ENTER>)

postgres=# select * from pg_user;

  usename   ¦ usesysid ¦ usecreatedb ¦ usesuper ¦ userepl ¦ usebypassrls ¦  passwd  ¦ valuntil ¦ useconfig

------------+----------+-------------+----------+---------+-

 postgres   ¦       10 ¦ t           ¦ t        ¦ t       ¦ t            ¦ ******** ¦          ¦

 replicator ¦    16388 ¦ f           ¦ f        ¦ t       ¦ f            ¦ ******** ¦          ¦

 pma_user   ¦    16528 ¦ f           ¦ t        ¦ f       ¦ f            ¦ ******** ¦          ¦

(3 строки)

          34) Поменяйте стиль отображения границ :

ostgres=# \pset border 0

Стиль границ: 0.

35) Повторите запрос:

postgres=# select * from pg_user;

 usename   usesysid usecreatedb usesuper userepl usebypassrls  passwd  valuntil useconfig

---------- -------- ----------- -------- ------- -----------

postgres         10 t           t        t       t            ********          

replicator    16388 f           f        t       f            ********          

pma_user      16528 f           t        f       f            ********          

(3 строки)

Отображение стало более компактным. 

36) Поменяйте стиль отображения границ:

postgres=# \pset border 2

Стиль границ: 2.

postgres=# select * from pg_user;

¦  usename   ¦ usesysid ¦ usecreatedb ¦ usesuper ¦ userepl ¦ usebypassrls ¦  passwd  ¦ valuntil ¦ useconfig ¦

+------------+----------+--------+

¦ postgres   ¦       10 ¦ t           ¦ t        ¦ t       ¦ t            ¦ ******** ¦          ¦           ¦

¦ replicator ¦    16388 ¦ f           ¦ f        ¦ t       ¦ f            ¦ ******** ¦          ¦           ¦

¦ pma_user   ¦    16528 ¦ f           ¦ t        ¦ f       ¦ f            ¦ ******** ¦          ¦           ¦

L------------+---------------

(3 строки)

Вы можете выбрать наиболее удобный для себя стиль вывода результатов выборки. Чтобы сделать его постоянным можно отредактировать файл ~/.psqlrc добавив в этот файл команды, которые мы рассмотрели.


Часть 10. Восстановление сохраненного кластера

В пункте 4 части 2 мы сохранили прежний кластер перед созданием нового кластера. Вернём кластер на место.

1) Остановите экземпляр:

postgres@tantor:~$ pg_ctl stop

2) Выполните команды:

postgres@tantor:~$ mkdir $PGDATA/../data.afterLAB1

postgres@tantor:~$ mv $PGDATA/* $PGDATA/../data.afterLAB1

postgres@tantor:~$ mv $PGDATA/../data.SAVE/* $PGDATA

         3) Запустите экземпляр:

postgres@tantor:~$ sudo systemctl start tantor-se-server-16

4) Проверьте работоспособность экземпляра:

postgres@tantor:~$ psql -c "select datname from pg_database;"

  datname

-----------

 postgres

 test_db

 template1

 template0

(4 строки)


Раздел 2. Архитектура

Структура памяти

  1. Транзакция в psql
  2. Список фоновых процессов
  3. Буферный кэш, команда EXPLAIN
  4. Журнал предзаписи. Где хранится?
  5. Контрольная точка  
  6. Восстановление после сбоя

Часть 1. Транзакция в psql

1) Откроем терминал Fly на рабочем столе:

astra@tantor:~$ psql

psql (16.1)

2) Введите "help", чтобы получить справку.

postgres=#

3) Создадим произвольную таблицу:

postgres=# CREATE TABLE a(id integer);

CREATE TABLE

4) Посмотрим что получилось:

postgres=# \dt a

        Список отношений

Схема  | Имя |   Тип   | Владелец  

-------+-----+---------+----------

public | a   | таблица | postgres

  1. строка)

5) Откроем транзакцию:

postgres=# BEGIN;

BEGIN

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

postgres=*# INSERT INTO a VALUES (1);

INSERT 0 1

Обратите внимание на появление звездочки в строке - это означает что идет транзакция.

7) Попробуем во втором терминале увидеть первую строчку таблицы. Откроем второй терминал:

8) Загрузим psql.

astra@tantor:~$ psql

psql (16.1)

Введите "help", чтобы получить справку.

postgres=#

9) Обратимся к таблице:

postgres=# SELECT * FROM a;

 id

----

(0 строк)

Убедились - пока мы не видим первой строчки. Видны только зафиксированные данные. Грязное чтение не допускается.

-------------------------------------------------

10) В первом терминале зафиксируем транзакцию.

postgres=*# COMMIT;

COMMIT

11) Во втором терминале обратимся к таблице еще раз.

postgres=# SELECT * FROM a;

 id

----

  1

(1 строка)

Теперь изменения таблицы зафиксированы.

Вывод - видны только те изменения, которые успешно зафиксированы.

Часть 2. Список фоновых процессов

1) Посмотрим где находится директория PGDATA, где находятся файлы кластера БД.

postgres=# SHOW data_directory;

            data_directory            

---------------------------------------

 /var/lib/postgresql/tantor-se-16/data

(1 строка)

2) Выйдите в первом терминале из psql.

postgres=# \q

        3) Чтобы посмотреть список процессов воспользуемся утилитой ps

astra@tantor:~$ sudo cat /var/lib/postgresql/tantor-se-16/data/postmaster.pid

466

/var/lib/postgresql/tantor-se-16/data

1713847705

5432

/var/run/postgresql

*

  1048641         0

ready  

4) Возьмем PID = 466 

astra@tantor:~$ sudo ps -o command --ppid 466

COMMAND

postgres: logger

postgres: checkpointer

postgres: background writer

postgres: walwriter

postgres: autovacuum launcher

postgres: logical replication launcher

postgres: walsender replicator ::1(34460) streaming 0/6DA71ED8

postgres: postgres postgres [local] idle

Жирным шрифтом показаны системные фоновые процессы, остальные пользовательские.

        Список процессов можно увидеть также через представление pg_stat_activity.

5) Сделайте во втором терминале.

postgres=# SELECT pid, backend_type, backend_start        

FROM pg_stat_activity;

  pid  |         backend_type         |         backend_start        

-------+------------------------------

   527 | autovacuum launcher          | 2024-04-23 07:48:25.435889+03

   528 | logical replication launcher | 2024-04-23 07:48:25.441432+03

   533 | walsender                    | 2024-04-23 07:48:25.472863+03

 25072 | client backend               | 2024-04-23 07:48:51.242631+03

 10977 | client backend               | 2024-04-23 08:06:17.871119+03

   520 | background writer            | 2024-04-23 07:48:25.403365+03

   519 | checkpointer                 | 2024-04-23 07:48:25.402941+03

   526 | walwriter                    | 2024-04-23 07:48:25.425135+03

(8 строк)

Часть 3. Буферный кэш, команда EXPLAIN

1) Во втором терминале добавим строки в таблицу «a».

postgres=#INSERT INTO a SELECT id FROM generate_series(1,10000) AS id;    

INSERT 0 10000

2) В первом терминале перезагрузите сервер:

astra@education:~$ sudo systemctl restart tantor-se-server-16

3) Во втором терминале сделать реконнект:

postgres=# \c

Вы подключены к базе данных «postgres», как пользователь «postgres».        

4) С помощью команды Explain посмотрите, откуда берется информация:

postgres=# EXPLAIN (analyze, buffers)

SELECT * FROM a;

                                            QUERY PLAN                                              

-------------------------------------------------

Seq Scan on a  (cost=0.00..145.00 rows=10000 width=4) (actual time=0.035..1.952 rows=10000 loops=1)

  Buffers: shared read=45

Planning:

  Buffers: shared hit=16 read=6 dirtied=3

Planning Time: 0.428 ms

Execution Time: 2.948 ms

(6 строк)

Обратите внимание на строку Buffers. Информация была взята с диска или через кэш операционной системы.

5) Сделайте эксперимент еще раз.

postgres=# EXPLAIN (analyze, buffers)

SELECT * FROM a;

                                             QUERY PLAN                                              

-------------------------------------------------------

 Seq Scan on a  (cost=0.00..145.00 rows=10000 width=4) (actual time=0.016..1.383 rows=10000 loops=1)

   Buffers: shared hit=45

 Planning Time: 0.063 ms

 Execution Time: 2.355 ms

(4 строки)

Информация изменилась. Теперь информация найдена в буферном кэше

Часть 4. Журнал предзаписи. Где хранится?

В первом терминале выполните команду:

astra@education:~$ sudo ls -l /var/lib/postgresql/tantor-se-16/data/pg_wal

итого 360452

-rw------- 1 postgres postgres 16777216 Apr 23 08:10 00000001000000000000006D

-rw-r----- 1 postgres postgres 16777216 Apr  3 14:28 00000001000000000000006E

-rw-r----- 1 postgres postgres 16777216 Apr  3 14:28 00000001000000000000006F

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 000000010000000000000070

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 000000010000000000000071

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 000000010000000000000072

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 000000010000000000000073

-rw------- 1 postgres postgres 16777216 Apr  3 14:42 000000010000000000000074

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 000000010000000000000075

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 000000010000000000000076

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 000000010000000000000077

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 000000010000000000000078

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 000000010000000000000079

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 00000001000000000000007A

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 00000001000000000000007B

-rw------- 1 postgres postgres 16777216 Apr  3 14:42 00000001000000000000007C

-rw------- 1 postgres postgres 16777216 Apr  3 14:42 00000001000000000000007D

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 00000001000000000000007E

-rw------- 1 postgres postgres 16777216 Apr  3 14:41 00000001000000000000007F

-rw------- 1 postgres postgres 16777216 Apr  3 14:42 000000010000000000000080

-rw-r----- 1 postgres postgres 16777216 Apr  3 14:42 000000010000000000000081

-rw------- 1 postgres postgres 16777216 Apr  3 14:42 000000010000000000000082

drwx------ 2 postgres postgres     4096 Apr  2 12:09 archive_status

Файлы журнала предзаписи находятся в директории pg_wal. Сегментами по 16 мегабайт.

Часть 5. Контрольная точка

1) Контрольная точка выполняется периодически, посмотрим во втором терминале, какой интервал установлен.

postgres=# SHOW checkpoint_timeout;

checkpoint_timeout  

--------------------

5min

(1 строка)

2) Контрольную точку можно запустить вручную.

postgres=# CHECKPOINT;

CHECKPOINT

3) В первом терминале посмотрим на файлы журнала предзаписи. Ненужные файлы удалены.

astra@education:~$ sudo ls -l /var/lib/postgresql/tantor-se-16/data/pg_wal

итого 802820

-rw------- 1 postgres postgres 16777216 Feb 14 07:40 0000000100000001000000A4

-rw------- 1 postgres postgres 16777216 Feb 14 07:40 0000000100000001000000A5

-rw------- 1 postgres postgres 16777216 Feb 14 07:40 0000000100000001000000A6

-rw------- 1 postgres postgres 16777216 Feb 14 07:40 0000000100000001000000A7

-rw------- 1 postgres postgres 16777216 Feb 14 07:40 0000000100000001000000A8

Часть 6. Восстановление после сбоя

1) Добавим во втором терминале новые строчки:

postgres=# INSERT INTO a SELECT id FROM generate_series(1,10000) AS id;

INSERT 0 10000

2) Остановите кластер БД в режиме системного сбоя. Для начала определим PID процесса postmaster.

astra@education:~$ sudo cat /var/lib/postgresql/tantor-se-16/data/postmaster.pid

12563

/var/lib/postgresql/tantor-se-16/data

1713849023

5432

/var/run/postgresql

*

  1048641        24

ready    

astra@education:~$ sudo kill -9 12563

3) Запустим экземпляр сервера.

astra@education:~$ sudo systemctl start tantor-se-server-16

Немного тормозит. Идет восстановление.

4) Во втором окне посмотрим, сохранились ли вставленные строки.

postgres=# \c

Вы подключены к базе данных «postgres» как пользователь «postgres».

postgres=# SELECT count(*) FROM a;

count

-------

 20000

(1 строка)

5) Очистим объекты во втором терминале.

postgres=# DROP TABLE a;

DROP TABLE

postgres=# \dt

Отношения не найдены.


Многоверсионность

  1. Вставка, обновление и удаление строки
  2. Видимость версии строки на различных уровнях изоляции
  3. Состояние транзакции по CLOG
  4. Блокировка таблицы
  5. Блокировка строки

Часть 1. Вставка, обновление и удаление строки

1) Загрузим psql:

astra@tantor:~$ psql

psql (16.1)

Введите "help", чтобы получить справку.

postgres=#

2) Создадим произвольную таблицу.

postgres=# CREATE TABLE a(id integer);

CREATE TABLE

3) Посмотрим, что получилось.

postgres=# \dt a

         Список отношений

 Схема  | Имя |   Тип   | Владелец

--------+-----+---------+----------

 public | a   | таблица | postgres

(1 строка)

4) Вставим первую строку в таблицу.

postgres=# INSERT INTO a VALUES(100);

INSERT 0 1

         5) Посмотрим, какой номер транзакции xmin.

postgres=# SELECT xmin, xmax, * FROM a;

xmin | xmax  | id  

------+------+-----

 1567 |    0 | 100

(1 строка)

Получился 1567 - это номер транзакции, в которой была создана первая версия строки.

 6) Начнем явную транзакцию.

postgres=# BEGIN;

BEGIN

7) Обновим первую строчку.

postgres=*# UPDATE a SET id = 200;

UPDATE 1

8) Обратимся и посмотрим, что получилось.

postgres=*# SELECT xmin, xmax, * FROM a;

xmin | xmax | id  

------+------+-----

 1569 |    0 | 200

(1 строка)

9) Убедились в том, что транзакция видит свои изменения.

Как вы думаете что будет если обратиться в параллельной транзакции?

id=100 или 200?

Во втором терминале обращаемся к таблице.
10) Загрузим
psql.

astra@tantor:~$ psql

psql (16.1)

Введите "help", чтобы получить справку.

postgres=#

postgres=# SELECT xmin, xmax, * FROM a;

 xmin | xmax | id  

------+------+-----

 1568 | 1569 | 100

(1 строка)

Обратите внимание, что xmax изменился - это значит, что уже существует вторая версия строки но она еще не зафиксирована.

11) В первом терминале фиксируем транзакцию.

postgres=*# COMMIT;

COMMIT

12) Во втором терминале теперь видим вторую строку.

postgres=# SELECT xmin, xmax, * FROM a;

 xmin | xmax | id  

------+------+-----

 1569 |    0 | 200

(1 строка)

13) Теперь посмотрим, как выглядит удаление. Откроем транзакцию в первом терминале.

postgres=# BEGIN;

BEGIN

14) Удаляем строчку.

postgres=*# DELETE FROM a;

DELETE 1

postgres=*# SELECT xmin, xmax, * FROM a;

xmin | xmax | id  

------+------+----

(0 rows)

Первая транзакция не видит строчку, она удалена, но изменение пока не зафиксировано.

15) Во втором терминале:

 postgres=# SELECT xmin, xmax, * FROM a;

xmin | xmax | id  

------+------+-----

 1569 | 1570 | 200

(1 строка)

Строка еще видна, но xmax опять изменился.

16) В первом терминале фиксируем транзакцию:

postgres=*# COMMIT;

COMMIT

17) Во втором терминале теперь видим изменение:

postgres=# SELECT xmin, xmax, * FROM a;

 xmin | xmax | id

------+------+----

(0 rows)

Часть 2. Видимость версии строки на различных уровнях изоляции

1) Откроем первую транзакцию и вставим строку.

postgres=# BEGIN;

BEGIN

2) Посмотрим уровень изоляции.

postgres=*# SHOW transaction_isolation;

transaction_isolation  

-----------------------

read committed

(1 строка)

postgres=*# INSERT INTO a VALUES(100);  

INSERT 0 1

postgres=*# SELECT xmin, xmax, * FROM a;

             

xmin | xmax | id  

------+------+-----

 1571 |    0 | 100

(1 строка)

3) Начнем вторую транзакцию во втором терминале и обратимся к таблице.

postgres=# BEGIN;

BEGIN

postgres=*# SELECT xmin, xmax, * FROM a;

xmin | xmax | id  

------+------+----

(0 строк)

4) Посмотрим уровень изоляции.
postgres=*# SHOW transaction_isolation;

transaction_isolation  

-----------------------

read committed

(1 строка)

5) Пока новая строка не видна. Зафиксируем первую транзакцию

postgres=*# COMMIT;

COMMIT

6) Во втором окне повторно обратимся к таблице. Что увидим?

postgres=*# SELECT xmin, xmax, * FROM a;

xmin | xmax | id  

------+------+-----

 1571 |    0 | 100

(1 строка)

7) Зафиксируем вторую транзакцию.

postgres=*# COMMIT;

COMMIT

Изменения стали видны. Это и есть аномалия неповторяющегося чтения.  

Теперь в первом окне начнем транзакцию на уровне repeatable read.

8) Вставим еще одну строку.

postgres=# BEGIN ISOLATION LEVEL REPEATABLE READ;

BEGIN

postgres=*# INSERT INTO a VALUES(200);

INSERT 0 1

postgres=*# SELECT xmin, xmax, * FROM a;

 xmin | xmax | id  

------+------+-----

 1571 |    0 | 100

 1572 |    0 | 200

(2 строки)

9) Во второй транзакции обратимся к таблице в новой транзакции на том же уровне.

postgres=# BEGIN ISOLATION LEVEL REPEATABLE READ;

BEGIN

postgres=*# SELECT xmin, xmax, * FROM a;

xmin | xmax | id  

------+------+-----

 1571 |    0 | 100

(1 строка)

10) Теперь фиксируем первую транзакцию.

postgres=*# COMMIT;

COMMIT

11) Обратимся ко второй транзакции еще раз.

postgres=*# SELECT xmin, xmax, * FROM a;

xmin | xmax | id  

-----+------+-----

1571 |    0 | 100

(1 строка)

Изменения не видны. на этом уровне операторы транзакции работают только с одним снимком данных.

12) Зафиксируем вторую транзакцию.

postgres=*# COMMIT;

COMMIT

Часть 3. Состояние транзакции по CLOG

1) Откроем первую транзакцию и посмотрим после вставки состояние.

postgres=# BEGIN;

BEGIN

postgres=*# INSERT INTO a VALUES(300);   

INSERT 0 1

postgres=*# SELECT xmin, xmax, * FROM a;

xmin | xmax | id  

------+------+-----

 1571 |    0 | 100

 1572 |    0 | 200

 1573 |    0 | 300

(3 строки)

2) Видим вставку третьей строки. Посмотрим статус транзакции:

postgres=*# SELECT pg_xact_status('1573');

pg_xact_status  

----------------

in progress

(1 строка)

3) Зафиксируем транзакцию и посмотрим статус.

postgres=*# COMMIT;

COMMIT

postgres=# SELECT pg_xact_status('1573');

 pg_xact_status

----------------

 committed

(1 строка)

4) Теперь посмотрим, как поведет себя CLOG при откате транзакции.

postgres=# BEGIN;

BEGIN

postgres=*# INSERT INTO a VALUES(400);   

INSERT 0 1

postgres=*# SELECT xmin, xmax, * FROM a;

 xmin | xmax | id  

------+------+-----

 1571 |    0 | 100

 1572 |    0 | 200

 1573 |    0 | 300

 1574 |    0 | 400

(4 строки)

postgres=*# SELECT pg_xact_status('1574');

pg_xact_status  

----------------

in progress

(1 строка)

postgres=*# ROLLBACK;

ROLLBACK

postgres=# SELECT pg_xact_status('1574');

pg_xact_status  

----------------

aborted

(1 строка)

postgres=*# SELECT xmin, xmax, * FROM a;

xmin | xmax | id  

------+------+-----

 1571 |    0 | 100

 1572 |    0 | 200

 1573 |    0 | 300

(3 строки)

Часть 4. Блокировки таблицы

1) В первой транзакции вставим новую строку и посмотрим блокировки с помощью pg_locks, для этого нам нужен pid обслуживающего процесса.

postgres=# SELECT pg_backend_pid(); 

pg_backend_pid

----------------

          12193

(1 строка)

2) Откроем транзакцию и обратимся к таблице.

postgres=# BEGIN;

BEGIN

postgres=*# UPDATE a SET id = id + 1;                                              

UPDATE 3

postgres=*# SELECT locktype, transactionid, mode, relation::regclass as obj FROM pg_locks where pid = 12193;

  locktype     | transactionid |       mode       |   obj    

---------------+---------------+------------------

 relation      |               | AccessShareLock  | pg_locks

 relation      |               | RowExclusiveLock | a

 virtualxid    |               | ExclusiveLock    |

 transactionid |          1577 | ExclusiveLock    |

(4 строки)

Появилась блокировка на уровне таблицы RowExclusiveLock - накладывается в случае обновления строк.

3) Во втором окне построим индекс по таблице, предварительно посмотрим pid процесса.

postgres=# SELECT pg_backend_pid();

pg_backend_pid

----------------

          17210

(1 строка)

postgres=# CREATE INDEX ON a (id);

4) Транзакция подвисла. В первом терминале посмотрим, что происходит во втором процессе.

postgres=*# SELECT locktype, transactionid, mode, relation::regclass as obj FROM pg_locks where pid = 17210;  

 locktype  | transactionid |     mode      | obj

------------+---------------+---------------+-----

 virtualxid |               | ExclusiveLock |

 relation   |               | ShareLock     | a

(2 строки)

Появилась блокировка ShareLock она не совместима RowExclusiveLock возникла блокировочная ситуация.

5) Зафиксируем первую транзакцию.

postgres=*# COMMIT;

COMMIT

6) Тут же срабатывает команда во втором окне.

CREATE INDEX

Часть 5. Блокировка строки

1) Начнем первую транзакцию:

postgres=# BEGIN;

BEGIN

postgres=*# UPDATE a SET id = id + 1 WHERE id=101;                                 

UPDATE 1

2) Начнем вторую транзакцию:

postgres=# BEGIN;

BEGIN

postgres=*# UPDATE a SET id = id + 1 WHERE id=101;

Транзакция подвисла, сработала блокировка.

3) Зафиксируем первую транзакцию:

postgres=*# COMMIT;

COMMIT

Тут же срабатывает вторая.

UPDATE 0

postgres=*# COMMIT;

COMMIT

Обратите внимание обновление не произошло, теперь такой версии строки нет для обновления.

4) В первом терминале обратимся к таблице

postgres=# SELECT xmin, xmax, * FROM a;

xmin | xmax | id  

------+------+-----

 1577 |    0 | 201

 1577 |    0 | 301

 1579 | 1580 | 102

(3 строки)

5) Удалим таблицу. 

postgres=# DROP TABLE a;

DROP TABLE

Задание выполнено.


Регламентные работы

  1. Обычная очистка таблицы
  2. Анализ таблицы
  3. Перестройка индекса
  4. Полная очистка
  5. Расширение HypoPG

Часть 1. Обычная очистка таблицы


        1) Загрузим
psql

astra@tantor:~$ psql

psql (16.1)

Введите "help", чтобы получить справку.

postgres=#

2) Создадим произвольную таблицу:

postgres=# CREATE TABLE a
        (id integer primary key generated always as identity,
         t  char(2000)) WITH (autovacuum_enabled = off);

CREATE TABLE

postgres=# INSERT INTO a(t) SELECT to_char(generate_series(1,10000),'9999');

INSERT 0 10000

3) Посмотрим что получилось.

postgres=# \d a  

                               Table "public.a"

Column |      Type       | Collation | Nullable |           Default            

--------+-----------------+-----------+----------

id     | integer         |           | not null | generated always as identity

t      | character(2000) |           |          |  

Indexes:

   "a_pkey" PRIMARY KEY, btree (id)

Обратите внимание создан первичный ключ и индекс.

4) Узнаем размер таблицы и индекса в байтах:

postgres=# SELECT pg_table_size('a');

pg_table_size  

---------------

     20512768

(1 строка)

postgres=# SELECT pg_table_size('a_pkey');

pg_table_size  

---------------

       245760

(1 строка)

5) Обновим 50% строк:

postgres=# UPDATE a set t= t || 'a' where id > 5000;

UPDATE 5000

6) Посмотрим размеры объектов.

postgres=# SELECT pg_table_size('a');               

pg_table_size  

---------------

     30752768

(1 строка)

postgres=# SELECT pg_table_size('a_pkey');

           

pg_table_size  

---------------

       360448

(1 строка)

7) Они также увеличились. Очистим таблицу и индекс:

postgres=# VACUUM a;

VACUUM

postgres=# SELECT pg_table_size('a'); SELECT pg_table_size('a_pkey');

pg_table_size  

---------------

     30760960

(1 строка)

pg_table_size  

---------------

       360448

(1 строка)

8) Размер остался таким же. Еще раз обновим строки.

postgres=# UPDATE a set t= t || 'a' where id > 5000;

UPDATE 5000

postgres=# SELECT pg_table_size('a'); SELECT pg_table_size('a_pkey');

pg_table_size  

---------------

     30760960

(1 строка)

pg_table_size  

---------------

       360448

(1 строка)

Опять размер не изменился. Произошло это потому что было использовано очищенное пространство.

9) К примеру предположим, что пропущен цикл очистки.

postgres=# UPDATE a set t= t || 'a' where id > 5000;                 

UPDATE 5000

postgres=# UPDATE a set t= t || 'a' where id > 5000;

UPDATE 5000

postgres=# SELECT pg_table_size('a'); SELECT pg_table_size('a_pkey');

pg_table_size  

---------------

     51249152

(1 строка)

pg_table_size  

---------------

       466944

(1 строка)

10) Размер объектов опять вырос.

postgres=# VACUUM a;

VACUUM

postgres=# SELECT pg_table_size('a'); SELECT pg_table_size('a_pkey');

pg_table_size  

---------------

     51249152

(1 строка)

pg_table_size  

---------------

       466944

(1 строка)

Даже после очистки размер не уменьшается.


Часть 2. Анализ таблицы

1) Так как произошло несколько циклов обновлений, посмотрим насколько актуальна осталась статистика. Сначала обратимся к системному каталогу.

postgres=# SELECT reltuples FROM pg_class WHERE relname='a';

reltuples  

-----------

     8333

(1 строка)

Получили, что в таблице у нас содержится 8333 строки.

2) Теперь обратимся к таблице.

postgres=# SELECT count(*) FROM a;

count  

-------

10000

(1 строка)

3) Оказалось, что строк больше. Статистика всегда приблизительна. Вызовем вторую фазу анализа.

postgres=# ANALYZE a;

ANALYZE

4) Теперь статистика стала более точной.

postgres=# SELECT reltuples FROM pg_class WHERE relname='a';

 reltuples

-----------

     10000

(1 строка)

Часть 3. Перестройка индекса

1) Посмотрим, какой размер объектов:

postgres=# SELECT pg_table_size('a'); SELECT pg_table_size('a_pkey');

pg_table_size  

---------------

     51249152

(1 строка)

pg_table_size  

---------------

       466944

(1 строка)

2) Сейчас в таблице а один только индекс. Перестроим его.

postgres=# REINDEX TABLE a;

REINDEX

postgres=# SELECT pg_table_size('a'); SELECT pg_table_size('a_pkey');

pg_table_size  

---------------

     51249152

(1 строка)

pg_table_size  

---------------

       245760

(1 строка)

3) Размер индекса уменьшился, размер таблицы остался неизменным.

Часть 4. Полная очистка

postgres=# VACUUM FULL a;

VACUUM

1) Посмотрим размер объектов.

postgres=# SELECT pg_table_size('a'); SELECT pg_table_size('a_pkey');

pg_table_size  

---------------

     20488192

(1 строка)

pg_table_size  

---------------

       245760

(1 строка)

Размер таблицы уменьшился.

2) Удалим таблицу.

postgres=# DROP TABLE a;

DROP TABLE

Задание выполнено.

Часть 5. Расширение HypoPG

1) Установите расширение hypopg:

postgres=# CREATE EXTENSION hypopg;

CREATE EXTENSION

2) Создайте таблицу с тестовыми данными:

postgres=# CREATE TABLE hypo AS SELECT id, 'line ' || id AS val FROM generate_series(1,10000) id;

SELECT 10000

3) План выполнения выборки одной строки - последовательное сканирование (Seq Scan). Индексных методов доступа нет, так как нет индексов:

postgres=# EXPLAIN SELECT * FROM hypo WHERE id = 1;

                       QUERY PLAN                      

--------------------------------------------------------

 Seq Scan on hypo  (cost=0.00..165.60 rows=41  width=36)

   Filter: (id = 1)

(2 строки)

Почему ожидаемое количество строк 41, а не 1? Нет статистики.

4) Соберите статистику:

postgres=# vacuum analyze hypo;

VACUUM

postgres=# EXPLAIN SELECT * FROM hypo WHERE id = 1;

                      QUERY PLAN                      

-------------------------------------------------------

 Seq Scan on hypo  (cost=0.00..180.00 rows=1 width=13)

   Filter: (id = 1)

(2 строки)

Ожидаемое количество строк 1.

Дана задача оптимизировать выполнение этого запроса. Предполагаем, что индекс по столбцу id ускорит выполнение запроса. Нужно убедиться, что планировщик будет использовать индекс. Если планировщик не будет использовать индекс, то предположение неверно и индекс создавать не нужно. Создание индекса трудоемко и долго, он занимает место. Перед созданием индекса мы хотим проверить гипотезу о том, что планировщик его будет использовать при выполнении оптимизируемого запроса.

5) Для проверки гипотезы создайте гипотетический индекс:

postgres=# SELECT * FROM hypopg_create_index('CREATE INDEX hypo_idx ON hypo (id)');

 indexrelid |      indexname      

------------+----------------------

      13495 | <13495>btree_hypo_id

(1 строка)

Имя гипотетического индекса генерируется автоматически, это нормально.

Реальный индекс не создаётся, команда выполняется моментально.

6) Посмотрите список гипотетических индексов:

postgres=# SELECT * FROM hypopg_list_indexes;

 indexrelid |      index_name      | schema_name | table_name | am_name

------------+----------------------+-------------

      13495 | <13495>btree_hypo_id | public      | hypo       | btree

(1 строка)

Какой план выполнения сейчас?

7) Выполните команду:

postgres=# EXPLAIN SELECT * FROM hypo WHERE id = 1;

                                     QUERY PLAN                                    

--------------------------------------------------------------------------

 Index Scan using "<13495>btree_hypo_id" on hypo  (cost=0.04..8.05 rows=1 width=13)

   Index Cond: (id = 1)

(2 строки)

План показывает, что индекс будет использоваться.

Реального индекса нет, поэтому план реального выполнения использует сканирование таблицы:

postgres=# EXPLAIN (analyze) SELECT * FROM hypo WHERE id = 1;

                                           QUERY PLAN                                            

----------------------------------------------------------------------------

 Seq Scan on hypo  (cost=0.00..180.00 rows=1 width=13) (actual time=0.025..0.875 rows=1 loops=1)

   Filter: (id = 1)

   Rows Removed by Filter: 9999

 Planning Time: 0.077 ms

 Execution Time: 1.074 ms

(5 строк)

8) Создайте реальный индекс:

postgres=# CREATE UNIQUE INDEX hypo_id ON hypo(id);

CREATE INDEX

План выполнения остался прежним:

postgres=# EXPLAIN SELECT * FROM hypo WHERE id = 1;

                                     QUERY PLAN                                    

------------------------------------------------------------------------

 Index Scan using "<13495>btree_hypo_id" on hypo  (cost=0.04..8.05 rows=1 width=13)

   Index Cond: (id = 1)

(2 строки)

9) Уберите побочные эффекты:

postgres=# SELECT hypopg_reset();

 hypopg_reset

--------------

 

(1 строка)

Планировщик стал использовать созданный индекс:

postgres=# EXPLAIN SELECT * FROM hypo WHERE id = 1;

                             QUERY PLAN                              

---------------------------------------------------------------------

 Index Scan using hypo_id on hypo  (cost=0.29..8.30 rows=1 width=13)

   Index Cond: (id = 1)

(2 строки)

Расширение позволяет скрывать от планировщика реальные индексы:

postgres=# SELECT hypopg_hide_index('hypo_id'::regclass);

 hypopg_hide_index

-------------------

 t

(1 строка)

Скрытие действует только в пределах сессии и на работу других сессий не оказывает влияние.

Гипотетические индексы также существуют только в рамках сессии.

Гипотетические индексы при этом исчезают:

postgres=# SELECT * FROM hypopg_list_indexes;

 indexrelid | index_name | schema_name | table_name | am_name

------------+------------+-------------

(0 строк)

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

postgres=# EXPLAIN SELECT * FROM hypo WHERE id = 1;

                      QUERY PLAN                      

-------------------------------------------------------

 Seq Scan on hypo  (cost=0.00..180.00 rows=1 width=13)

   Filter: (id = 1)

(2 строки)

Есть представление со списком скрытых в данной сессии индексов:

postgres=# SELECT * FROM hypopg_hidden_indexes;

 indexrelid | index_name | schema_name | table_name | am_name | is_hypo

------------+------------+-------------

      17402 | hypo_id    | public      | hypo       | btree   | f

(1 строка)

10) Убедитесь, что скрытие индексов и гипотетические индексы существуют только в рамках сессии:

postgres=# SELECT * FROM hypopg_create_index('CREATE INDEX hypo_idx ON hypo (id)');

 indexrelid |      indexname      

------------+----------------------

      13495 | <13495>btree_hypo_id

(1 строка)

postgres=# SELECT * FROM hypopg_list_indexes;

 indexrelid |      index_name      | schema_name | table_name | am_name

------------+----------------------+-------------

      13495 | <13495>btree_hypo_id | public      | hypo       | btree

(1 строка)

postgres=# \q

postgres@tantor:~$ psql

psql (16.1)

11) Введите «help», чтобы получить справку.

postgres=# SELECT * FROM hypopg_list_indexes;

 indexrelid | index_name | schema_name | table_name | am_name

------------+------------+-------------

(0 строк)

postgres=# SELECT * FROM hypopg_hidden_indexes;

 indexrelid | index_name | schema_name | table_name | am_name | is_hypo

------------+------------+-------------

(0 строк)


Выполнение запросов

  1. Создание объектов для запросов
  2. Извлечение данных последовательно
  3. Возвращение данных по индексу
  4. Низкая селективность
  5. Использование статистики
  6. Представление pg_stat_statements

Часть 1. Создание объектов для запросов

1) Загрузим psql:

astra@tantor:~$ psql

psql (16.1)

Введите "help", чтобы получить справку.

postgres=#

2) Создадим новую таблицу и заполним данными.

postgres=# CREATE TABLE test (col1 integer, col2 integer, name text);

CREATE TABLE

postgres=# INSERT INTO test VALUES (1,2,'test1');

INSERT 0 1

postgres=# INSERT INTO test VALUES (3,4,'test2');

INSERT 0 1

3) Создадим представление над таблицей.


postgres=# CREATE VIEW v_table AS  

SELECT * FROM test;

CREATE VIEW

postgres=# SELECT col1, col2 FROM v_table WHERE name='test1'::text ;

 col1 | col2

------+------

    1 |    2

(1 строка)

Часть 2. Извлечение данных последовательно

1) C помощью команды Explain посмотрим план выполнения запроса.

postgres=# EXPLAIN  

         SELECT col1, col2 FROM v_table WHERE name='test1'::text

                    QUERY PLAN                      

-----------------------------------------------------

Seq Scan on test  (cost=0.00..25.00 rows=6 width=8)

  Filter: (name = 'test1'::text)

(2 строки)

Видим, что использовалось последовательное чтение таблицы test. то есть представление было раскрыто, данные извлечены непосредственно с таблицы.

2) Применим параметры analyze и buffers. Они показывают что запрос был выполнен реально и какое количество страниц было затронуто.

postgres=# EXPLAIN(analyze, buffers, costs off, timing off) 

SELECT col1, col2 FROM v_table WHERE name='test1'::text ;

               QUERY PLAN                

------------------------------------------

Seq Scan on test (actual rows=1 loops=1)

  Filter: (name = 'test1'::text)

  Rows Removed by Filter: 1

  Buffers: shared read=1

Planning Time: 0.063 ms

Execution Time: 9.569 ms

(6 строк)

Часть 3. Возвращение данных по индексу

1) Создадим индекс по столбцу col1.

postgres=# CREATE INDEX ON test (col1);

CREATE INDEX

postgres=# \d test

                           Таблица "public.test"

 Столбец |   Тип   | Правило сортировки | Допустимость NULL | По умолчанию

---------+---------+--------------------

 col1    | integer |                    |                   |

 col2    | integer |                    |                   |

 name    | text    |                    |                   |

Индексы:

   "test_col1_idx" btree (col1)

2) Можно убедится, что формирование имени индекса производится автоматически,

добавим информации к таблице.

postgres=# INSERT INTO test(col1,col2)

SELECT generate_series(3,1003), generate_series(4,1004);

INSERT 0 1001

3) Посмотрим, что получится если будем выбирать малое количество строк. То есть, случай когда будет высокая селективность и маленькая кардинальность.

postgres=# EXPLAIN(analyze, buffers, costs off, timing off)  

SELECT col1, col2 FROM test WHERE col1<20;

                          QUERY PLAN                            

-----------------------------------------------------------------

Index Scan using test_col1_idx on test (actual rows=19 loops=1)

   Index Cond: (col1 < 20)

   Buffers: shared hit=3

 Planning:

   Buffers: shared hit=17

 Planning Time: 0.179 ms

 Execution Time: 0.117 ms

(7 строк)

Убедились, что используется индексный доступ.

Часть 4. Низкая селективность

Теперь отберем большое количество строк.

postgres=# SELECT count(*) FROM test;

count  

-------

 1003

(1 строка)

Всего строк 1003

postgres=# EXPLAIN(analyze, buffers, costs off, timing off)  

SELECT col1, col2 FROM test WHERE col1>20;

                QUERY PLAN                

--------------------------------------------

 Seq Scan on test (actual rows=983 loops=1)

   Filter: (col1 > 20)

   Rows Removed by Filter: 20

   Buffers: shared hit=5

 Planning:

   Buffers: shared hit=3

 Planning Time: 0.157 ms

 Execution Time: 0.201 ms

(8 строк)

Отобрано 983 строки. Что означает низкая селективность и высокая кардинальность.

Убедились, что в этом случае индексный доступ становится дорогим и СУБД переходит к последовательному доступу.

Часть 5. Использование статистики

К примеру, при заполнении таблицы test третий столбец был не заполнен. Давайте посмотрим какой процент будет иметь значение NULL

Пересоберем статистику.

postgres=# ANALYZE test;

ANALYZE

postgres=# SELECT stanullfrac  FROM pg_statistic WHERE starelid = 'test'::regclass AND staattnum = 3;  

stanullfrac  

-------------

  0.9981884

(1 row)

Как видно из таблицы pg_statistic 99%

Часть 6. Представление pg_stat_statements

1) Убедимся что представление установлено.

postgres=# \dx pg_stat_statements

                                        Список установленных расширений

      Имя         | Версия | Схема  |                                Описание                                

--------------------+--------+--------+-----

pg_stat_statements| 1.10  | public | track planning and execution statistics of all SQL statements executed

(1 строка)

2) Посмотрим, какие столбцы есть в представлении.

postgres=# \d pg_stat_statements

                            Представление "public.pg_stat_statements"

        Столбец         |       Тип        | Правило сортировки | Допустимость NULL | По умолчанию

------------------------+------------------+--------------------

 userid                 | oid              |                    |                   |

 dbid                   | oid              |                    |                   |

 toplevel               | boolean          |                    |                   |

 queryid                | bigint           |                    |                   |

 query                  | text             |                    |                   |

 plans                  | bigint           |                    |                   |

 total_plan_time        | double precision |                    |                   |

 min_plan_time          | double precision |                    |                   |

 max_plan_time          | double precision |                    |                   |

 mean_plan_time         | double precision |                    |                   |

 stddev_plan_time       | double precision |                    |                   |

 calls                  | bigint           |                    |                   |

 total_exec_time        | double precision |                    |                   |

 min_exec_time          | double precision |                    |                   |

 max_exec_time          | double precision |                    |                   |

 mean_exec_time         | double precision |                    |                   |

 stddev_exec_time       | double precision |                    |                   |

 rows                   | bigint           |                    |                   |

 shared_blks_hit        | bigint           |                    |                   |

 shared_blks_read       | bigint           |                    |                   |

 shared_blks_dirtied    | bigint           |                    |                   |

 shared_blks_written    | bigint           |                    |                   |

 local_blks_hit         | bigint           |                    |                   |

 local_blks_read        | bigint           |                    |                   |

 local_blks_dirtied     | bigint           |                    |                   |

 local_blks_written     | bigint           |                    |                   |

 temp_blks_read         | bigint           |                    |                   |

 temp_blks_written      | bigint           |                    |                   |

 blk_read_time          | double precision |                    |                   |

 blk_write_time         | double precision |                    |                   |

 temp_blk_read_time     | double precision |                    |                   |

 temp_blk_write_time    | double precision |                    |                   |

 wal_records            | bigint           |                    |                   |

 wal_fpi                | bigint           |                    |                   |

 wal_bytes              | numeric          |                    |                   |

 jit_functions          | bigint           |                    |                   |

 jit_generation_time    | double precision |                    |                   |

 jit_inlining_count     | bigint           |                    |                   |

 jit_inlining_time      | double precision |                    |                   |

 jit_optimization_count | bigint           |                    |                   |

 jit_optimization_time  | double precision |                    |                   |

 jit_emission_count     | bigint           |                    |                   |

 jit_emission_time      | double precision |                    |                   |

3) Сбросим статистику применения представления.

postgres=# SELECT pg_stat_statements_reset();

pg_stat_statements_reset  

--------------------------

 

(1 строка)

4) Обратимся к таблице test.

postgres=# EXPLAIN (analyze)                        

SELECT col1, col2 FROM test WHERE col1>20;

                                           QUERY PLAN                  

                         

--------------------------------------------------------------------------------------------

 Seq Scan on test  (cost=0.00..17.54 rows=984 width=8) (actual time=0.022..0.132 rows=983 loops=1)

   Filter: (col1 > 20)

   Rows Removed by Filter: 20

 Planning Time: 0.190 ms

 Execution Time: 0.234 ms

(5 строк)

        

5) С помощью представления pg_stat_statements посмотрим сколько времени заняло выполнение запроса и сколько страниц было задействовано.

postgres=# SELECT queryid, substring(query FOR 100) as query, total_exec_time as ms, shared_blks_hit as blocks

from pg_stat_statements

WHERE query LIKE '%col1, col2%';

      queryid         |                   query                   |    ms    | blocks

----------------------+----------------------------

 -3250261183448805182 | EXPLAIN (analyze)                        +| 0.491265 |     11

                      | SELECT col1, col2 FROM test WHERE col1>$1 |          |

(1 строка)


Расширяемость

  1. Определение директории с файлами расширения
  2. Просмотр установленных расширений
  3. Просмотр доступных расширений
  4. Установка и удаление произвольного обновления
  5. Просмотр доступных версий расширений. Обновление до актуальной версии
  6. Обертки внешних данных

Часть 1. Определение директории с файлами расширения

1) Перейдем под пользователя postgres.


astra@education:~$ sudo su - postgres

2) В командной строке воспользуемся утилитой pg_config.

postgres@education:~$ pg_config --sharedir

/opt/tantor/db/16/share/postgresql

3) Добавим к получившемуся пути extension.

postgres@education:~$ ls -l /opt/tantor/db/16/share/postgresql/extension/

итого 9768

-rw-r--r-- 1 root root    274 Apr 18  2023 adminpack--1.0--1.1.sql

-rw-r--r-- 1 root root   1535 Apr 18  2023 adminpack--1.0.sql

-rw-r--r-- 1 root root   1682 Apr 18  2023 adminpack--1.1--2.0.sql

-rw-r--r-- 1 root root    595  Apr 18  2023 adminpack--2.0--2.1.sql

-rw-r--r-- 1 root root    176 Apr 18  2023 adminpack.control

&hellip;&hellip;&hellip;&hellip;

&hellip;&hellip;&hellip;

4) Загрузим psql.

postgres@tantor:~$ psql

psql (16.1)

Введите "help", чтобы получить справку.

postgres=#

5) Определим путь расширения с помощью функции pg_config().

postgres=# SELECT setting FROM pg_config()  

where name = 'SHAREDIR';

             setting                

------------------------------------

/opt/tantor/db/16/share/postgresql

(1 row)

Часть 2. Просмотр установленных расширений

postgres=# \dx

                                          Список установленных расширений

        Имя         | Версия |   Схема    |                                Описание                                

--------------------+--------+------------

 pg_stat_statements | 1.10   | public     | track planning and execution statistics of all SQL statements executed

 pg_store_plans     | 1.6.4  | public     | track plan statistics of all SQL statements executed

 plpgsql            | 1.0    | pg_catalog | PL/pgSQL procedural language

(3 строки)

Часть 3. Просмотр доступных расширений

Воспользуемся расширением pg_available_extensions.

postgres=# SELECT * from pg_available_extensions;

        name        | default_version | installed_version |                                            comment                                            

--------------------+-----------------+-------------------

 plpgsql            | 1.0             | 1.0               | PL/pgSQL procedural language

 page_repair        | 1.0             |                   | Individual page reparing

 pg_hint_plan       | 1.6.0           |                   |

 dblink             | 1.2             |                   | connect to other PostgreSQL databases from within a database

 tcn                | 1.0             |                   | Triggered change notifications

 pg_trgm            | 1.6             |                   | text similarity measurement and index searching based on trigrams

 pg_buffercache     | 1.4             |                   | examine the shared buffer cache

-----------------

 dict_xsyn          | 1.0             |                   | text search dictionary template for extended synonym processing

 pg_variables       | 1.2             |                   | session variables with various types

 old_snapshot       | 1.0             |                   | utilities in support of old_snapshot_threshold

 pgcrypto           | 1.3             |                   | cryptographic functions

 file_fdw           | 1.0             |                   | foreign-data wrapper for flat file access

 amcheck            | 1.3             |                   | functions for verifying relation integrity

 seg                | 1.4             |                   | data type for representing line segments or floating-point intervals

 pg_background      | 1.2             |                   | Run SQL queries in the background

(69 строк)

На данный момент установлено 69 расширений в текущем кластере БД. Их можно установить в любой БД.

Часть 4. Установка и удаление произвольного обновления

1) Например, установим расширение pg_surgery.

postgres=# CREATE EXTENSION pg_surgery;

CREATE EXTENSION

postgres=# \dx

                                    Список установленных расширений

        Имя         | Версия |   Схема    |                       Описание                                

--------------------+--------+------------

 pg_stat_statements | 1.10   | public     | track planning and execution statistics of all SQL statements executed

 pg_store_plans     | 1.6.4  | public     | track plan statistics of all SQL statements executed

 pg_surgery         | 1.0    | public     | extension to perform surgery on a damaged relation

 plpgsql            | 1.0    | pg_catalog | PL/pgSQL procedural language

(4 строки)


        2)
Посмотрим содержимое расширения.

postgres=# \dx+ pg_surgery

     Объекты в расширении "pg_surgery"

             Описание объекта              

-------------------------------------------

 функция heap_force_freeze(regclass,tid[])

 функция heap_force_kill(regclass,tid[])

(2 строки)

3) Удалим расширение.

postgres=# DROP EXTENSION pg_surgery;

DROP EXTENSION

Часть 5. Просмотр доступных версий расширений. Обновление до актуальной версии

1) Воспользуемся представлением pg_available_extension_versions

postgres=# SELECT name, version FROM pg_available_extension_versions WHERE name = 'adminpack';

  name    | version

-----------+---------

 adminpack | 1.0

 adminpack | 1.1

 adminpack | 2.0

 adminpack | 2.1

(4 строки)

2) Для начала установим версию 1.0

postgres=# CREATE EXTENSION adminpack VERSION '1.0';

CREATE EXTENSION

postgres=# \dx adminpack  

                    Список установленных расширений

    Имя    | Версия |   Схема    |                Описание                

-----------+--------+------------+-----------------------------------------

 adminpack | 1.0    | pg_catalog | administrative functions for PostgreSQL

(1 строка)

3) Посмотрим содержимое расширения.

postgres=# \dx+ adminpack  

    Объекты в расширении "adminpack"

             Описание объекта            

------------------------------------------

 функция pg_file_length(text)

 функция pg_file_read(text,bigint,bigint)

 функция pg_file_rename(text,text)

 функция pg_file_rename(text,text,text)

 функция pg_file_unlink(text)

 функция pg_file_write(text,text,boolean)

 функция pg_logdir_ls()

 функция pg_logfile_rotate()

(8 строк)

4) Посмотрим, можно ли расширение обновить до версии 2.1. Воспользуемся функцией pg_extension_update_paths.

postgres=# SELECT * FROM pg_extension_update_paths('adminpack');                  

source | target |        path        

--------+--------+--------------------

 1.0    | 1.1    | 1.0--1.1

 1.0    | 2.0    | 1.0--1.1--2.0

 1.0    | 2.1    | 1.0--1.1--2.0--2.1

 1.1    | 1.0    |

 1.1    | 2.0    | 1.1--2.0

 1.1    | 2.1    | 1.1--2.0--2.1

 2.0    | 1.0    |

 2.0    | 1.1    |

 2.0    | 2.1    | 2.0--2.1

 2.1    | 1.0    |

 2.1    | 1.1    |

 2.1    | 2.0    |

(12 строк)

5) Обновим расширение до версии 2.1.

postgres=# ALTER EXTENSION adminpack UPDATE;             

ALTER EXTENSION

postgres=# \dx adminpack                    

                     Список установленных расширений

    Имя    | Версия |   Схема    |                Описание                

-----------+--------+------------+-----------------------------------------

 adminpack | 2.1    | pg_catalog | administrative functions for PostgreSQL

(1 строка)

postgres=# \dx+ adminpack                    

    Объекты в расширении "adminpack"

             Описание объекта            

------------------------------------------

 функция pg_file_rename(text,text)

 функция pg_file_rename(text,text,text)

 функция pg_file_sync(text)

 функция pg_file_unlink(text)

 функция pg_file_write(text,text,boolean)

 функция pg_logdir_ls()

(6 строк)

Как видите, содержимое расширения изменилось.

6) Удалим расширение.

postgres=# DROP EXTENSION adminpack;

DROP EXTENSION

Часть 6. Обертки внешних данных

1) Посмотрим, какие есть обертки внешних данных (FDW).

postgres=# SELECT * FROM pg_available_extensions              

WHERE name LIKE '%fdw%';

    name     | default_version | installed_version |                      comment                      

--------------+-----------------+-------------------

 postgres_fdw | 1.1             |                   | foreign-data wrapper for remote PostgreSQL servers

 file_fdw     | 1.0             |                   | foreign-data wrapper for flat file access

(2 строки)

2) Воспользуемся оберткой внешней данных для подключения к СУБД PostgreSQL.

postgres=# CREATE EXTENSION postgres_fdw;

CREATE EXTENSION

postgres=# \dx postgres_fdw  

                          Список установленных расширений

     Имя      | Версия | Схема  |                      Описание                      

--------------+--------+--------+----------------------------------------------------

 postgres_fdw | 1.1    | public | foreign-data wrapper for remote PostgreSQL servers

(1 строка)

3) Посмотрим какие есть базы данных.

postgres=# \l

                                                        Список баз данных

    Имя    | Владелец | Кодировка | Провайдер локали | LC_COLLATE  |  LC_CTYPE   | локаль ICU | Правила ICU |     Права доступа    

-----------+----------+-----------+------------------+-------------+-------------+------------+-------------+-----------------------

 postgres  | postgres | UTF8      | libc             | ru_RU.UTF-8 | ru_RU.UTF-8 |            |             |

 template0 | postgres | UTF8      | libc             | ru_RU.UTF-8 | ru_RU.UTF-8 |            |             | =c/postgres          +

           |          |           |                  |             |             |            |             | postgres=CTc/postgres

 template1 | postgres | UTF8      | libc             | ru_RU.UTF-8 | ru_RU.UTF-8 |            |             | =c/postgres          +

           |          |           |                  |             |             |            |             | postgres=CTc/postgres

 test_db   | postgres | UTF8      | libc             | ru_RU.UTF-8 | ru_RU.UTF-8 |            |             |

(4 строки)

4) Подключимся и вернем информацию с БД test_db. Для начала создадим объект удаленного сервера.

postgres=# CREATE SERVER test FOREIGN DATA WRAPPER postgres_fdw  

                 OPTIONS (host 'localhost', port '5432', dbname 'test_db');

CREATE SERVER

postgres=# \des

         Список сторонних серверов

 Имя  | Владелец | Обёртка сторонних данных

------+----------+--------------------------

 test | postgres | postgres_fdw

(1 строка)

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

postgres=# CREATE USER MAPPING FOR postgres SERVER test  

OPTIONS ( user 'postgres', password 'postgres' );

CREATE USER MAPPING

postgres=# \deu

Список сопоставлений пользователей

 Сервер | Имя пользователя

--------+------------------

 test   | postgres

(1 строка)

6) После, создадим таблицу, к которой можно будет подключится.

postgres=# CREATE FOREIGN TABLE order_remote  

( id bigint, name varchar(32))

server test  

OPTIONS ( schema_name 'public', table_name 'order_items_1'  

);

CREATE FOREIGN TABLE

postgres=# \det

   Список сторонних таблиц

 Схема  |   Таблица    | Сервер

--------+--------------+--------

 public | order_remote | test

(1 строка)

7) Обращаемся к этой таблице, как к обычной таблице.

postgres=# SELECT * FROM order_remote LIMIT 10;

id | name  

----+------

 0 |  

 1 |  

 2 |  

 3 |  

 4 |  

 5 |  

 6 |  

 7 |  

 8 |  

 9 |  

(10 строк)

8) Описание удаленной таблицы можно получить, как обычной.

postgres=# \d order_remote  

                                Сторонняя таблица "public.order_remote"

 Столбец |          Тип          | Правило сортировки | Допустимость NULL | По умолчанию | Параметры ОСД

---------+-----------------------+--------------------+-------------------+--------------+---------------

 id      | bigint                |                    |                   |              |

 name    | character varying(32) |                    |                   |              |

Сервер: test

Параметр ОСД: (schema_name 'public', table_name 'order_items_1')

9) Посмотрим откуда приходят данные.

postgres=# EXPALIN SELECT * FROM order_remote LIMIT 10;

                             QUERY PLAN                              

----------------------------------------------------------------------

Foreign Scan on order_remote  (cost=100.00..100.42 rows=10 width=90)

(1 строка)


10) Очистим базу данных.

postgres=# DROP FOREIGN TABLE order_remote;

DROP FOREIGN TABLE

postgres=# DROP USER MAPPING FOR postgres server test;

DROP USER MAPPING

postgres=# DROP SERVER test;

DROP SERVER

postgres=# DROP EXTENSION postgres_fdw;

DROP EXTENSION


Раздел 3. Конфигурирование

  1. Обзор параметров конфигурации
  2. Параметры конфигурации с единицей измерения
  3. Параметры конфигурации логического типа
  4. Конфигурационные параметры
  5. Файл служб

Часть 1. Обзор параметров конфигурации

1) Сколько параметров конфигурации имеется?

postgres=# select count(*) from pg_settings;

 count

-------

   404

(1 строка)

2) Сколько имеется системных параметров? Выполните запрос:

postgres=# select count(name) from pg_settings where name not like '%.%';

 count

-------

   371

(1 строка)

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

3) Какие модули были загружены?

postgres=# show shared_preload_libraries;

            shared_preload_libraries            

------------------------------------------------

 pg_store_plans,pg_stat_statements,auto_explain

(1 строка)

Было загружено три модуля.

4) Сколько параметров модулей (расширений, библиотек) и приложений есть? В названии таких параметров есть точка. Выполните запрос:

postgres=# select distinct split_part(name,'.', 1), count(name) from pg_settings where name like '%.%' group by split_part(name,'.', 1) order by 1;

     split_part     | count

--------------------+-------

 auto_explain       |    13

 pg_stat_statements |     5

 pg_store_plans     |    15

(3 строк)

5) Какие максимальные значения есть у параметров? Это интересно чтобы сопоставить название типа параметра с его размерностью (сколько байт занимает значение). Выполните запрос:

postgres=# select vartype, min_val, max_val, count(name) from pg_settings group by vartype, min_val, max_val order by length(max_val) desc, vartype;

 vartype |   min_val   |       max_val       | count

---------+-------------+---------------------+-------

 bool    |             |                     |   122

 enum    |             |                     |    44

 string  |             |                     |    68

 int64   | 10000       | 9223372036854775807 |     1

 int64   | 100000      | 9223372036854775807 |     1

 int64   | 0           | 9223372036854775807 |     4

 real    | -1          | 1.79769e+308        |     3

 real    | 0           | 1.79769e+308        |     7

 int64   | 0           | 2100000000          |     2

 integer | 100         | 1073741823          |     2

 integer | -1          | 2147483647          |    13

 integer | 1           | 2147483647          |     6

 integer | -2147483648 | 2147483647          |     1

 integer | -1          | 1073741823          |     2

...

Для продолжения вывода можно нажать клавишу <z>.

Максимальное значение типа с названием int64: 9223372036854775807 = 2 в степени 63 минус 1, что соответствует максимуму для целочисленного знакового типа размерностью 64 бита.

У типов с названием integer максимальное значение 2147483647, что соответствует максимуму для целочисленного знакового типа размерностью 32 бита.

6) Контекст указывает можно ли изменить значение параметра, если можно, то каким способом. Какие контексты параметров есть и сколько параметров в каждом контексте?


postgres=# select context, count(name) from pg_settings where name not like '%.%' group by context order by 1;

      context      | count

-------------------+-------

 backend           |     2

 internal          |    18

 postmaster        |    64

 sighup            |    96

 superuser         |    44

 superuser-backend |     4

 user              |   143

(7 строк)

Больше всего параметров контекста user. Изменения параметров контекста postmaster потребуют рестарт экземпляра. Параметры контекста internal только для чтения (не могут меняться командами SET, ALTER SYSTEM, путем установки значения в файлах параметров конфигурации) и их нет смысла указывать в файлах параметров конфигурации. Могут ли поменяться значения параметров контекста internal? Могут. Способ изменения зависит от параметра. Например, значение параметра wal_segment_size может меняться утилитой pg_resetwal, параметра data_checksums утилитой pg_checksums.

7) Посмотрите какие категории параметров есть:

postgres=# select category, count(*) from pg_settings group by category order by 2 desc;

                             category                              | count

-------------------------------------------------------------------+-------

 Customized Options                                                |    33

 Client Connection Defaults / Statement Behavior                   |    31

 Developer Options                                                 |    25

 Resource Usage / Memory                                           |    22

 Query Tuning / Planner Method Configuration                       |    22

 Reporting and Logging / What to Log                               |    21

 Preset Options                                                    |    18

 Write-Ahead Log / Settings                                        |    15

 Connections and Authentication / SSL                              |    14

 Query Tuning / Planner Cost Constants                             |    13

 Reporting and Logging / Where to Log                              |    13

 Autovacuum                                                        |    13

 Client Connection Defaults / Locale and Formatting                |    12

 Connections and Authentication / Connection Settings              |    11

 Replication / Standby Servers                                     |    11

 Resource Usage / Asynchronous Behavior                            |     9

 Write-Ahead Log / Recovery Target                                 |     8

 Query Tuning / Other Planner Options                              |     8

 Statistics / Cumulative Query and Index Statistics                |     7

 Query Tuning / Genetic Query Optimizer                            |     7

 Reporting and Logging / When to Log                               |     7

 Connections and Authentication / Authentication                   |     7

 Version and Platform Compatibility / Previous PostgreSQL Versions |     7

 Replication / Sending Servers                                     |     6

 Write-Ahead Log / Checkpoints                                     |     6

 Lock Management                                                   |     5

 Statistics / Monitoring                                           |     5

 File Locations                                                    |     5

 Connections and Authentication / TCP Settings                     |     5

 Resource Usage / Cost-Based Vacuum Delay                          |     5

 Error Handling                                                    |     4

 Client Connection Defaults / Shared Library Preloading            |     4

 Resource Usage / Background Writer                                |     4

 Write-Ahead Log / Archiving                                       |     4

 Client Connection Defaults / Other Defaults                       |     3

 Write-Ahead Log / Archive Recovery                                |     3

 Replication / Subscribers                                         |     3

 Write-Ahead Log / Recovery                                        |     2

 Reporting and Logging / Process Title                             |     2

 Version and Platform Compatibility / Other Platforms and Clients  |     1

 Resource Usage / Kernel Resources                                 |     1

 Replication / Primary Server                                      |     1

 Resource Usage / Disk                                             |     1

(43 строки)

Для продолжения вывода (вместо промпта команда показывает двоеточие) последовательно нажмите на клавиатуре клавиши <z><q>.

Категория Customized Options - это параметры расширений и приложений.

8) Сколько параметров установлено в файлах параметров конфигурации?

postgres=# select sourcefile, count(*) from pg_settings group by sourcefile;

                             sourcefile                              | count

---------------------------------------------------------------------+-------

                                                                     |   384

 /var/lib/postgresql/tantor-se-16-replica/data1/postgresql.conf      |    14

 /var/lib/postgresql/tantor-se-16-replica/data1/postgresql.auto.conf |     6

(3 строки)

В файлах параметров конфигурации установлено 14+6=20 параметров.

9) В файле postgresql.conf большое количество параметров закомментированы и откомментированы. Комментарии - это короткая, качественная, удобная (под рукой) справка.

Какие параметры конфигурации были считаны из основного файла параметров при запуске экземпляра?

postgres=# select name, setting, sourceline from pg_settings where sourcefile like '%l.conf' order by sourceline;

            name            |                    setting                     | sourceline

----------------------------+------------------------------------------------+------------

 max_connections            | 100                                            |         65

 shared_buffers             | 16384                                          |        131

 dynamic_shared_memory_type | posix                                          |        154

 min_wal_size               | 80                                             |        258

 log_timezone               | Europe/Moscow                                  |        613

 DateStyle                  | ISO, DMY                                       |        727

 TimeZone                   | Europe/Moscow                                  |        729

 lc_messages                | ru_RU.UTF-8                                    |        743

 lc_monetary                | ru_RU.UTF-8                                    |        745

 lc_numeric                 | ru_RU.UTF-8                                    |        746

 lc_time                    | ru_RU.UTF-8                                    |        747

 default_text_search_config | pg_catalog.russian                             |        753

 shared_preload_libraries   | pg_stat_statements,pg_store_plans,auto_explain |        834

 logging_collector          | on                                             |        835

(14 строк)

sourceline - номер строки от начала файла. Номер строки удобен для поиска параметра и его редактирования.

Ту же информацию можно посмотреть информацию в представлении pg_file_settings.

10) Выполните команду:

postgres=# select name, setting, sourceline, applied from pg_file_settings where sourcefile like '%l.conf';

            name            |                    setting                     | sourceline | applied 

----------------------------+------------------------------------------------+------------+--

 max_connections            | 100                                            |         65 | t

 shared_buffers             | 128MB                                          |        131 | t

 dynamic_shared_memory_type | posix                                          |        154 | t

 max_wal_size               | 1GB                                            |        257 | f

 min_wal_size               | 80MB                                           |        258 | t

 log_timezone               | Europe/Moscow                                  |        613 | t

 datestyle                  | iso, dmy                                       |        727 | t

 timezone                   | Europe/Moscow                                  |        729 | t

 lc_messages                | ru_RU.UTF-8                                    |        743 | t

 lc_monetary                | ru_RU.UTF-8                                    |        745 | t

 lc_numeric                 | ru_RU.UTF-8                                    |        746 | t

 lc_time                    | ru_RU.UTF-8                                    |        747 | t

 default_text_search_config | pg_catalog.russian                             |        753 | t

 listen_addresses           | *                                              |        833 | f

 shared_preload_libraries   | pg_stat_statements,pg_store_plans,auto_explain |        834 | t

 logging_collector          | on                                             |        835 | t

(15 rows)

По какой причине может быть расхождение в количестве строк, в приведенном примере 14 и 15?

В pg_file_settings присутствует параметр max_wal_size. У вас расхождения может не быть или быть в других параметрах. Параметр в примере установлен в файле postgresql.auto.conf.

11) Пример содержимого файлов:

postgres=# \! cat $PGDATA/postgresql.conf | grep max_wal_size

max_wal_size = 1GB

postgres=# \! cat $PGDATA/postgresql.auto.conf

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

listen_addresses = '*'

max_wal_size = '512MB'

max_slot_wal_keep_size = '1024MB'

В обоих файлах присутствует параметр max_wal_size. Представление pg_file_settings выводит все незакомментированные параметры из всех файлов. В столбце applied для 257 строки стоит "f" это означает, что данная строка переопределяется последующей строкой с тем же именем параметра (или строкой в файле postgresql.auto.conf содержимое которого переопределяет значения из postgresql.conf).В примере строку 257 перекрыла строка 4 из файла postgresql.auto.conf В запросах мы не выводили содержимое этого (postgresql.auto.conf) файла (предикат sourcefile like '%l.conf').

12) В представлениях бывает, что имеется много столбцов и при построчном выводе они не помещаются в терминале. Можно воспользоваться расширенным режимом вывода. Выполните команды:

postgres=# select * from pg_settings where name = 'max_wal_size'\gx

-[ RECORD 1 ]---+--------------------------------------------------

name            | max_wal_size

setting         | 512

unit            | MB

category        | Write-Ahead Log / Checkpoints

short_desc      | Sets the WAL size that triggers a checkpoint.

extra_desc      |

context         | sighup

vartype         | integer

source          | configuration file

min_val         | 2

max_val         | 2147483647

enumvals        |

boot_val        | 1024

reset_val       | 512

sourcefile      | /var/lib/postgresql/tantor-se-16/data/postgresql.auto.conf

sourceline      | 4

pending_restart | f

В этом примере видны все детали параметра: категория, короткое описание, контекст.

Значение параметра было применено из 4 строки файла postgresql.auto.conf

        


        13) Пример вывода значений без предиката (фильтра):

postgres=# select name, setting, substring(sourcefile, 39) file, sourceline, applied from pg_file_settings where name='max_wal_size';

     name     | setting |         file         | sourceline | applied

--------------+---------+----------------------+------------+---------

 max_wal_size | 1GB     | postgresql.conf      |        257 | f

 max_wal_size | 512MB   | postgresql.auto.conf |          4 | t

(2 строки)

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

        При запуске экземпляра (и перечитывании файлов, если значение параметра может быть изменено без рестарта экземпляра) применяется значение из файла postgresql.auto.conf которое идёт самым последним. В этом файле также могут быть повторения, они появляются при редактировании файла вручную, а также в результате работы утилит (например, pg_basebackup), которые просто добавляют строки в конец файла, зная что установленное в конец файла будет превалировать.

        Если в файле postgresql.auto.conf параметр отсутствует, то применяется значение, которое ближе к концу файла postgresql.conf.

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

В столбце pending_restart значение "t" появится, если значение параметра было изменено в файле параметров конфигурации, файлы был перечитаны (без перечитывания содержимое pg_settings не меняется) и после перечитывания требуется перезапуск экземпляра (то есть у параметра context=postmaster). Во всех остальных случаях значение pending_restart="f".

        В отличие от pg_settings представление pg_file_settings показывает текущее содержимое файлов параметров и в столбце error можно посмотреть нет ли ошибок после редактирования файлов, что приведёт к невозможности запуска экземпляра.

        14) Ошибок в этих двух файлах параметров конфигурации нет, если запрос типа
select sourcefile, sourceline, error from pg_file_settings where error is not null не выдаст ни одной строки.

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

Примеры (выполнять команды этого пункта не нужно):

ошибка в значении параметра:

postgres=# \! cat $PGDATA/postgresql.auto.conf | grep max_wal

max_wal_size = '512mB'

postgres=# select substring(sourcefile, 39) file, sourceline, error from pg_file_settings where error is not null;

         file         | sourceline |            error            

----------------------+------------+------------------------------

 postgresql.auto.conf |          4 | setting could not be applied

(1 строка)

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

postgres=# \! cat $PGDATA/postgresql.conf | grep 512MB

max_wol_size = 512MB

postgres=# select substring(sourcefile, 39) file, sourceline, error from pg_file_settings where error is not null;

      file       | sourceline |                error                

-----------------+------------+--------------------------------------

 postgresql.conf |        836 | unrecognized configuration parameter

(1 строка)

Не исправив предыдущие ошибки добавлена ошибка в синтаксисе строки:

postgres=# \! cat $PGDATA/postgresql.conf | grep max_wol

max_wol_size = 512MB

max_wol_size - 512MB

postgres=# select substring(sourcefile, 39) file, sourceline, error from pg_file_settings where error is not null;

      file       | sourceline |    error    

-----------------+------------+--------------

 postgresql.conf |        837 | syntax error

(1 строка)

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

postgres@tantor:~$ sudo systemctl restart tantor-se-server-16

Job for tantor-se-server-16.service failed because the control process exited with error code.

See "systemctl status tantor-se-server-16.service" and "journalctl -xe" for details.

Ошибки "setting could not be applied" не всегда означают невозможность запуска экземпляра.

Часть 2. Параметры конфигурации с единицей измерения

1) Посмотрим как менять значение параметров с единицей измерения.

Посмотрите свойства параметра shared_buffers

postgres=# select * from pg_settings where name = 'shared_buffers' \gx

-[ RECORD 1 ]---+-----------------------------------------------------

name            | shared_buffers

setting         | 16384

unit            | 8kB

category        | Resource Usage / Memory

short_desc      | Sets the number of shared memory buffers used by the server.

extra_desc      |

context         | postmaster

vartype         | integer

source          | configuration file

min_val         | 16

max_val         | 1073741823

enumvals        |

boot_val        | 16384

reset_val       | 16384

sourcefile      | /var/lib/postgresql/tantor-se-16/data/postgresql.conf

sourceline      | 131

pending_restart | f

Значение измеряется в блоках размером 8Кб. Параметр целочисленный.

2) Установите значение для этого параметра 12800:

        

postgres=# alter system set shared_buffers = 12800;

ALTER SYSTEM

3) Проверьте что было записано в файл параметров:

postgres=# \! cat $PGDATA/postgresql.auto.conf

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

...

shared_buffers = '12800'

Значение было введено без апострофов, в файле было установлено в апострофах.

4) Посмотрите нет ли ошибок в установленном значении параметра:

postgres=# select substring(sourcefile, 39) file, sourceline, error from pg_file_settings where error is not null;

         file         | sourceline |            error            

----------------------+------------+------------------------------

 postgresql.auto.conf |          6 | setting could not be applied

(1 строка)

Ошибка означает, что лучше использовать значения с единицами измерения, например '100MB'.

Экземпляр при этом успешно перезапустится и ошибка исчезнет.

4) Параметр имеет контекст postmaster, значит для изменения значения требует рестарт экземпляра. Перезапустите экземпляр:

postgres=# \q

postgres@tantor:~$ sudo systemctl restart tantor-se-server-16

[sudo] password for postgres: postgres

postgres@tantor:~$ psql

5) Посмотрите какое значение у параметра после рестарта экземпляра:

postgres=# show shared_buffers;

 shared_buffers

----------------

 100MB

(1 строка)

Значение выдаётся в мегабайтах. В файле параметров установлено значение '12800'.

12800 * 8192 (8Кб) = 104857600. 104857600 / 1024 / 1024 = 100. 12800 блоков - это ровно 100МБ.

6) Без единиц измерения этот параметр измеряется в блоках.

Установим значение в мегабайтах. Выполните команду:

postgres=# alter system set shared_buffers = 100mb;

ERROR:  trailing junk after numeric literal at or near "100m"

СТРОКА 1: alter system set shared_buffers = 100mb;

                                            ^

Не получается. Для единиц измерения важен регистр. Выполните команду:

postgres=# alter system set shared_buffers = 100MB;

ERROR:  trailing junk after numeric literal at or near "100M"

СТРОКА 1: alter system set shared_buffers = 100MB;

                                            ^

Не получается. Поставьте апострофы:

postgres=# alter system set shared_buffers = '100MB';

ALTER SYSTEM

Получилось.

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

Между числом и единицей измерения могут быть пробелы и это не вызовет ошибок. Например (выполнять не нужно):

postgres=# alter system set shared_buffers = '100       MB';

postgres=# \! cat $PGDATA/postgresql.auto.conf

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

listen_addresses = '*'

max_wal_size = '512MB'

max_slot_wal_keep_size = '1024MB'

shared_buffers = '100       MB'

Пробелы ухудшают читаемость.

7) Посмотрите, что было записано в файл при вводе значения с единицей измерения и в апострофах:

postgres=# \! cat $PGDATA/postgresql.auto.conf

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

..

shared_buffers = '100MB'

8) Уберите из postgresql.auto.conf параметр shared_buffers:

postgres=# alter system reset shared_buffers;

ALTER SYSTEM

postgres=# \! cat $PGDATA/postgresql.auto.conf

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

&hellip;

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

В этом пункте мы изучили, как убирать параметры из postgresql.auto.conf.

Часть 3. Параметры конфигурации логического типа

1) Посмотрим параметр логического типа (bool).

postgres=# select * from pg_settings where name = 'hot_standby_feedback'\gx

-[ RECORD 1 ]---+---------------------------------------------------------

name            | hot_standby_feedback

setting         | off

unit            |

category        | Replication / Standby Servers

short_desc      | Allows feedback from a hot standby to the primary that will avoid query conflicts.

extra_desc      |

context         | sighup

vartype         | bool

source          | default

min_val         |

max_val         |

enumvals        |

boot_val        | off

reset_val       | off

sourcefile      |

sourceline      |

pending_restart | f

Контекст sighup означает, что для применения нового значения достаточно перечитать файлы конфигурации.

2) «Включим» параметр, то есть установим значение в true:

postgres=# alter system set hot_standby_feedback = o;

ERROR:  parameter "hot_standby_feedback" requires a Boolean value

Ошибка означает, что нельзя сократить значение, так как есть неоднозначность. В качестве значения допускаются on и off.

postgres=# alter system set hot_standby_feedback = on;

ALTER SYSTEM

Значение on допустимо для логических параметров. Проверьте, что допустимы и другие значения:

postgres=# alter system set hot_standby_feedback = 1;

ALTER SYSTEM

postgres=# alter system set hot_standby_feedback = '1';

ALTER SYSTEM

Единица тоже допустима.

postgres=# alter system set hot_standby_feedback = tr;

ALTER SYSTEM

Допускаются сокращения значений, но только если нет неоднозначности.

Неоднозначность была с сокращением до одной буквы "o".

3) Посмотрите что было записано в файл параметров:

postgres=# \! cat $PGDATA/postgresql.auto.conf

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

..

hot_standby_feedback = 'tr'

Значение в сокращенном виде было записано в апострофах.

Сокращения неудобны для чтения. Для логических параметров лучше использовать канонические значения on, off.

4) Перечитайте файлы параметров, чтобы новое значение начало действовать:

postgres=# select pg_reload_conf();

 pg_reload_conf

----------------

 t

(1 строка)

postgres=# show hot_standby_feedback;

 hot_standby_feedback

----------------------

 on

(1 строка)

Значение установилось правильно.

Часть 4. Конфигурационные параметры

«Параметры конфигурации» (settings) и «Конфигурационные параметры» (config) созвучны. В этой части практики рассмотрим «Конфигурационные параметры».

Есть три способа посмотреть конфигурационные параметры: утилита командной строки pg_config, представление pg_config и функция pg_config().

1) Посмотрите, какие параметры конфигурации существуют, утилитой pg_config:

postgres@tantor:~$ pg_config --help

pg_config предоставляет информацию об установленной версии PostgreSQL.

Использование:

  pg_config [ПАРАМЕТР]...

Параметры:

  --bindir            показать расположение исполняемых файлов

  --docdir            показать расположение файлов документации

  --htmldir           показать расположение HTML-файлов документации

  --includedir        показать расположение файлов-заголовков (.h) для

                      клиентских интерфейсов на языке C

  --pkgincludedir     показать расположение других файлов-заголовков (.h)

  --includedir-server показать расположение файлов-заголовков (.h) для сервера

  --libdir            показать расположение библиотек объектного кода

  --pkglibdir         показать расположение динамически загружаемых модулей

  --localedir         показать расположение файлов описания локалей

  --mandir            показать расположение справочных страниц

  --sharedir          показать расположение платформенно-независимых файлов

  --sysconfdir        показать расположение общесистемных файлов конфигурации

  --pgxs              показать расположение makefile для расширений

  --configure         показать параметры скрипта "configure", с которыми

                      был собран PostgreSQL

  --cc                показать, с каким значением CC собран PostgreSQL

  --cppflags          показать, с каким значением CPPFLAGS собран PostgreSQL

  --cflags            показать, с какими флагами C собран PostgreSQL

  --cflags_sl         показать, с каким значением CFLAGS_SL собран PostgreSQL

  --ldflags           показать, с каким значением LDFLAGS собран PostgreSQL

  --ldflags_ex        показать, с каким значением LDFLAGS_EX собран PostgreSQL

  --ldflags_sl        показать, с каким значением LDFLAGS_SL собран PostgreSQL

  --libs              показать, с каким значением LIBS собран PostgreSQL

  --version           показать версию PostgreSQL

  -?, --help          показать эту справку и выйти

При запуске без аргументов выводятся все известные значения.

Эти параметры устанавливаются при сборке СУБД Тантор и не меняются. Они одинаковы для сборок BE, SE, SE1C. Поскольку названия директорий длинные и запомнить сложно, то польза от утилиты pg_config в том, что можно зная название утилиты и название вида директории, получить путь в файловой системе к нужной директории.

2) Запустите утилиту без параметров, утилита выдаст значения всех параметров:

postgres@tantor:~$ pg_config

BINDIR = /opt/tantor/db/16/bin

DOCDIR = /opt/tantor/db/16/share/doc/postgresql

HTMLDIR = /opt/tantor/db/16/share/doc/postgresql

INCLUDEDIR = /opt/tantor/db/16/include

PKGINCLUDEDIR = /opt/tantor/db/16/include/postgresql

INCLUDEDIR-SERVER = /opt/tantor/db/16/include/postgresql/server

LIBDIR = /opt/tantor/db/16/lib

PKGLIBDIR = /opt/tantor/db/16/lib/postgresql

LOCALEDIR = /opt/tantor/db/16/share/locale

MANDIR = /opt/tantor/db/16/share/man

SHAREDIR = /opt/tantor/db/16/share/postgresql

SYSCONFDIR = /opt/tantor/db/16/etc/postgresql

PGXS = /opt/tantor/db/16/lib/postgresql/pgxs/src/makefiles/pgxs.mk

CONFIGURE =  '--prefix=/opt/tantor/db/16' '--enable-tap-tests' '--enable-nls=en ru' '--with-python' '--with-icu' '--with-lz4' '--with-zstd' '--with-ssl=openssl' '--with-ldap' '--with-pam' '--with-uuid=e2fs' '--with-libxml' '--with-libxslt' '--with-gssapi' '--with-selinux' '--with-systemd' '--with-llvm' 'CFLAGS=-O2 -pipe -Wno-missing-braces' 'LLVM_CONFIG=/usr/bin/llvm-config-11' 'CLANG=/usr/bin/clang-11' 'PYTHON=/usr/bin/python3'

CC = gcc

CPPFLAGS = -D_GNU_SOURCE -I/usr/include/libxml2

CFLAGS = -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -O2 -pipe -Wno-missing-braces

CFLAGS_SL = -fPIC

LDFLAGS = -L/usr/lib/llvm-11/lib -Wl,--as-needed -Wl,-rpath,'/opt/tantor/db/16/lib',--enable-new-dtags

LDFLAGS_EX =

LDFLAGS_SL =

LIBS = -lpgcommon -lpgport -lselinux -lzstd -llz4 -lxslt -lxml2 -lpam -lssl -lcrypto -lgssapi_krb5 -lz -lreadline -lpthread -lrt -ldl -lm

VERSION = PostgreSQL 16.1

Местоположение директории с внешними библиотеками (подгружаемыми модулями, PKGLIBDIR) показывает параметр --pkglibdir.

3) Библиотеки загружаются при запуске экземпляра параметром конфигурации shared_preload_libraries или, если библиотека может загружаться не только при запуске экземпляра, но и динамически серверным процессом, то командой LOAD 'имя_библиотеки';

Посмотрите какие библиотеки доступны:

postgres@tantor:~$ ls $(pg_config --pkglibdir)

adminpack.so            latin_and_mic.so       pg_visibility.so

amcheck.so              libpqwalreceiver.so    pg_wait_sampling.so

auth_delay.so           llvmjit.so             pg_walinspect.so

auto_explain.so         llvmjit_types.bc       pgxml.so

autoinc.so              lo.so                  pgxs

basebackup_to_shell.so  ltree_plpython3.so     plpgsql.so

basic_archive.so        ltree.so               plpython3.so

bitcode                 moddatetime.so         postgres_fdw.so

bloom.so                old_snapshot.so        refint.so

btree_gin.so            orafce.so              seg.so

btree_gist.so           pageinspect.so         sepgsql.so

citext.so               page_repair.so         sslinfo.so

credcheck.so            passwordcheck.so       tablefunc.so

cube.so                 pgauditlogtofile.so    tcn.so

cyrillic_and_mic.so     pgaudit.so             test_decoding.so

dblink.so               pg_background.so       tsm_system_rows.so

dict_int.so             pg_buffercache.so      tsm_system_time.so

dict_snowball.so        pg_columnar.so         unaccent.so

dict_xsyn.so            pg_cron.so             utf8_and_big5.so

earthdistance.so        pgcrypto.so            utf8_and_cyrillic.so

euc2004_sjis2004.so     pg_freespacemap.so     utf8_and_euc2004.so

euc_cn_and_mic.so       pg_hint_plan.so        utf8_and_euc_cn.so

euc_jp_and_sjis.so      pgoutput.so            utf8_and_euc_jp.so

euc_kr_and_mic.so       pg_partman_bgw.so      utf8_and_euc_kr.so

euc_tw_and_big5.so      pg_prewarm.so          utf8_and_euc_tw.so

file_fdw.so             pgq_lowlevel.so        utf8_and_gb18030.so

fuzzystrmatch.so        pgq_triggers.so        utf8_and_gbk.so

hstore_plpython3.so     pg_qualstats.so        utf8_and_iso8859_1.so

hstore.so               pg_repack.so           utf8_and_iso8859.so

http.so                 pgrowlocks.so          utf8_and_johab.so

hypopg.so               pg_stat_statements.so  utf8_and_sjis2004.so

insert_username.so      pgstattuple.so         utf8_and_sjis.so

_int.so                 pg_store_plans.so      utf8_and_uhc.so

isn.so                  pg_surgery.so          utf8_and_win.so

jsonb_plpython3.so      pg_trgm.so             uuid-ossp.so

latin2_and_win1250.so   pg_variables.so

4) Проверим, что некоторые разделяемые библиотеки можно загружать динамически. Загрузите модуль pg_hint_plan:

postgres=# show pg_hint_plan.enable_hint;

ERROR:  unrecognized configuration parameter "pg_hint_plan.enable_hint"

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

postgres=# LOAD 'pg_hint_plan';

LOAD

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

5) В частности, теперь в сессии доступны параметры конфигурации модуля. При наборе текста можно использовать клавишу табуляции на клавиатуре <TAB>, psql продолжит за вас набор текста если других вариаций нет, а при двойном нажатии клавиши покажет список возможных значений.

Наберите show pg_hint<TAB>.<TAB>:

postgres=# show pg_hint_plan.enable_hint;

 pg_hint_plan.enable_hint

--------------------------

 on

(1 строка)

6) Воспользуемся и другим вариантом просмотра параметров конфигурации:

postgres=# \dconfig pg_hint_plan.*

      Список параметров конфигурации

            Параметр            | Значение

--------------------------------+----------

 pg_hint_plan.debug_print       | off

 pg_hint_plan.enable_hint       | on

 pg_hint_plan.enable_hint_table | off

 pg_hint_plan.hints_anywhere    | off

 pg_hint_plan.message_level     | log

 pg_hint_plan.parse_messages    | info

(6 строк)

При инсталляции расширений в директорию PKGLIBDIR скопированы динамически линкуемые библиотеки (*.so), если расширение содержит разделяемые библиотеки.

Вторая полезная директория при администрировании расширений это SHAREDIR. В эту директорию копируются файлы расширений, которые затем устанавливаются командой CREATE EXTENSION.

7) Расширения не являются общим объектом кластера и устанавливаются на уровне баз данных.

Посмотрите какие расширения готовы к установке в базы данных:

postgres@tantor:~$ ls $(pg_config --sharedir)/extension | grep .control

adminpack.control

amcheck.control

...

xml2.control

8) Список тех же самых расширений можно посмотреть в представлении pg_available_extensions:

postgres=# select count(*) from pg_available_extensions;

 count

-------

    69

9) Посмотрите определение представления:

postgres=# \sv pg_available_extensions

CREATE OR REPLACE VIEW pg_catalog.pg_available_extensions AS

 SELECT e.name,

    e.default_version,

    x.extversion AS installed_version,

    e.comment

   FROM pg_available_extensions() e(name, default_version, comment)

     LEFT JOIN pg_extension x ON e.name = x.extname

Представление использует функцию pg_available_extensions(), которая читает содержимое файлов *.control в директории SHAREDIR.

9) Посмотрите определение функции:

postgres=# \sf pg_available_extensions()

CREATE OR REPLACE FUNCTION pg_catalog.pg_available_extensions(OUT name name, OUT default_version text, OUT comment text)

 RETURNS SETOF record

 LANGUAGE internal

 STABLE PARALLEL SAFE STRICT COST 10 ROWS 100

AS $function$pg_available_extensions$function$

Командой \sv вы можете смотреть тексты представлений: Командой \sf тексты подпрограмм, в том числе системного каталога.

Часть 5. Файл служб

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

1) Посмотрите на какую директорию указывает параметр SYSCONFDIR. В этой директории находятся файлы по умолчанию.

postgres@tantor:~$ pg_config --sysconfdir

/opt/tantor/db/16/etc/postgresql

2) Создайте директорию:

postgres@tantor:~$ sudo mkdir /opt/tantor/db/16/etc

postgres@tantor:~$ sudo chown postgres.postgres /opt/tantor/db/16/etc

postgres@tantor:~$ mkdir /opt/tantor/db/16/etc/postgresql

3) Скопируйте файл примера в эту директорию (команда одной строкой):

postgres@tantor:~$ cp $(pg_config --sharedir)/pg_service.conf.sample $(pg_config --sysconfdir)/pg_service.conf

4) Посмотрите содержимое файла служб:

postgres@tantor:~$ cat $(pg_config --sysconfdir)/pg_service.conf

#

#       Connection configuration file

#

# A service is a set of named connection parameters.  You may specify

# multiple services in this file.  Each starts with a service name in

# brackets.  Subsequent lines have connection configuration parameters of

# the pattern "param=value" or LDAP URLs starting with "ldap://"

# to look up such parameters.  A sample configuration for postgres is

# included in this file.  Lines beginning with '#' are comments.

#

# Copy this to your sysconf directory (typically /usr/local/pgsql/etc) and

# rename it pg_service.conf.

#

#

#[postgres]

#dbname=postgres

#user=postgres

5) Отредактируйте файл /opt/tantor/db/16/etc/postgresql/pg_service.conf:

postgres@tantor:~$ mcedit /opt/tantor/db/16/etc/postgresql/pg_service.conf

6) Вставьте в файл строки:

[postgres]

dbname=postgres

user=postgres

host=/var/run/postgresql

port=5432

Теперь имеется определение службы с названием «postgres». Можно указать несколько служб в этом файле. В параметре host можно указывать IP-адрес или имя хоста. При указании директории используется локальное соединение через Unix-сокет.

7) Воспользуемся этим именем службы для соединения с базой данных. Выполните команду:

postgres@tantor:~$ psql service=postgres

psql (16.1)

Введите "help", чтобы получить справку.

postgres=# \conninfo

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5432"

Если в файле служб допустить ошибку, например, указать порт как 5435, то выдастся ошибка:

postgres@tantor:~$ psql service=postgres

psql: ошибка: подключиться к серверу через сокет "/var/run/postgresql/.s.PGSQL.5435" не удалось: Нет такого файла или каталога

Сервер действительно работает локально и принимает подключения через этот сокет?

8) Файл служб также может находиться в домашнем каталоге пользователя операционной системы (~/.pg_service.conf) Точка в начале названия файла нужна.

Директория SYSCONFDIR также используется для файла с именем "psqlrc". При запуске без параметра -X утилита psql после подключения к базе данных читает и выполняет команды из "psqlrc", а затем из файла ~/.psqlrc (если эти файлы есть). Этими файлами можно воспользоваться для настройки свойств сессии psql.


Раздел 4. Базы данных

Логическая структура кластера

  1. Установка параметров конфигурации на различных уровнях

  2. Установка пути поиска в функциях и процедурах

Часть 1. Установка параметров конфигурации на различных уровнях

Цель этой части изучить, как устанавливать параметры конфигурации на различных уровнях, и какие уровни превалируют.

1) Установите промпт, который будет показывать пользователя и базу данных с которой создана сессия (текст после \set вводится одной строкой):

postgres=# \set PROMPT1 '%[%033[0;31m%]%n%[%033[0m%]@%[%033[0;36m%]%/%[%033[0m%] %[%033[0;33m%]%[%033[5m%]%x%[%033[0m%]%[%033[0m%]%R%# '

postgres=# \set PROMPT2 '%[%033[0;31m%]%n%[%033[0m%]@%[%033[0;36m%]%/%[%033[0m%] %[%033[0;33m%]%[%033[5m%]%x%[%033[0m%]%[%033[0m%]%R%# '

2) Добавьте в конец файла postgresql.conf параметр:

postgres=# \! echo "my.level = 'Pgconf'" >> $PGDATA/postgresql.conf

Обязательно проверьте, что используете две угловые скоби, а не одну: >> иначе затрёте содержимое файла.

Параметр my.level это «параметр приложения», имя которому мы сами придумали. В имени обязательно должна быть точка, иначе экземпляр не запустится.

Если не добавить параметр в postgresql.conf и не перечитать файл параметров, то команда ALTER SYSTEM SET my.level = 'строка'; выдаст ошибку:

ERROR:  unrecognized configuration parameter "my.level"

Такая ошибка выдаётся, если с момента запуска экземпляра не была загружена разделяемая библиотека, которая бы зарегистрировала бы параметры конфигурации. Загрузка осуществляется параметром shared_preload_libraries или командой LOAD.

3) Проверьте, что строка добавилась:

postgres=# \! tail -n 1 $PGDATA/postgresql.conf

my.level = 'Pgconf'

5) Перечитайте файлы параметров:

postgres=# select pg_reload_conf();

 pg_reload_conf

----------------

 t

(1 строка)

6) Посмотрите, какие типы (context) параметров есть:

postgres=# select distinct context from pg_settings;

      context      

-------------------

 postmaster

 superuser-backend

 user

 internal

 backend

 sighup

 superuser

(7 строк)

Большинство параметров типа "user" можно устанавливать на всех уровнях. Однако, могут быть нюансы. Например, параметр application_name устанавливает клиентское приложение после создания сессии. Для утилиты psql это значение psql. Поэтому устанавливать значение этого параметра на уровне кластера, базы, роли, роли в базе бессмысленно, так как установка на уровне сессии перекрывает эти значения. Его можно устанавливать на уровне сессии, транзакции, функции.

Параметр temp_tablespaces можно устанавливать на любом уровне, но он имеет особенность: при создании подпрограммы на языке plpgsql (у этого языка есть функция-«обёртка», проверяющая тело подпрограммы в момент создания) проверяется наличие табличных пространств и если их нет, то подпрограмма не создаётся.

Параметры типа internal не меняются.

Параметры типа postmaster меняются с перезапуском экземпляра и их можно менять командой ALTER SYSTEM.

Параметры типа sighup меняются командой ALTER SYSTEM, но требуют перечитывания файлов параметров.

7) Создайте объекты следующими командами:

drop database IF EXISTS bob;

drop ROLE IF EXISTS bob;

drop database IF EXISTS rob;

drop user IF EXISTS rob;

CREATE USER bob SUPERUSER LOGIN;

CREATE ROLE rob SUPERUSER LOGIN;

CREATE DATABASE bob OWNER bob STRATEGY WAL_LOG;

CREATE DATABASE rob OWNER rob STRATEGY FILE_COPY;

\c bob bob

CREATE SCHEMA IF NOT EXISTS bob AUTHORIZATION bob;

CREATE SCHEMA IF NOT EXISTS rob AUTHORIZATION rob;

\dconfig my.level

alter system set my.level = 'System';

select pg_reload_conf();

alter database bob set my.level = 'Database';

alter role bob set my.level = 'Role';

alter role bob in database bob set my.level = 'RoleInDatabase';

CREATE OR REPLACE FUNCTION bob.bob()

 RETURNS text

 LANGUAGE plpgsql

 SET my.level TO 'Function'

AS $function$

 BEGIN

  RAISE NOTICE 'my.level %', current_setting('my.level');

  RAISE NOTICE 'search_path %', current_schemas(true);

  RETURN current_setting('my.level');

 END;

$function$

;

CREATE OR REPLACE FUNCTION bob.bobdef()

 RETURNS text

 LANGUAGE plpgsql

 SECURITY DEFINER

AS $function$

 BEGIN

  RAISE NOTICE 'my.level %', current_setting('my.level');

  RAISE NOTICE 'search_path %', current_schemas(true);

  RAISE NOTICE 'current_user %', current_user;

  RAISE NOTICE 'session_user %', session_user;

  RAISE NOTICE 'user %', user;

  RETURN current_setting('my.level');

 END;

$function$

;

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

Уровень функции перекрывает все уровни.

Следующий уровень, который перекрывает остальные (кроме функции) это уровень транзакции SET LOCAL.

Следующий уровень - сессии. Если вызывать функции SECURITY DEFINER, которые работают справами владельца, то уровень сессии вызывающего перекроет значения сессии владельца.

А если на сессии не устанавливать значение, то чьё значение будет действовать - роли-владельца (DEFINER)?

Нет, будет действовать значение параметра, установившееся на уровне сессии того, кто вызывает функцию. Если параметр был установлен на «роль в базе», то он и установится в сессии. Если не был установлен, то установится «на роль». Дальше «на базу». Это важно знать. Для функций и процедур особенно важно значение параметра search_path которое будет действовать в теле функции или процедуры. Функции и процедуры в постгрес называются подпрограммами.

Вторая проблема. Значение по умолчанию у search_path="$user", public.

Значение $user в теле подпрограммы у SECURITY DEFINER имя роли-владельца. Поэтому со значением $user путь поиска у подпрограмм с DEFINER и INVOKER различны. При этом вызывающий подпрограмму может установить в своей сессии search_path без $user путь поиска станет другим в теле подпрограммы.

Поэтому с SECURITY DEFINER подпрограммами лучше не полагаться на путь поиска, а всегда устанавливать путь поиска в определении подпрограммы. Можно было бы использовать префикс с названием схем перед каждым объектом в теле подпрограммы, но тогда нужно ставить префикс и в теле всех подпрограмм, которые она вызывает, в том числе подпрограммы системного каталога. Иначе вызывающий может установить search_path = myschema, public, pg_catalog и заменить любую подпрограмму системного каталога на свою в схеме myschema. Также вызывающий может создать временную таблицу и она перекроет любые таблицы, поэтому при создании SECURITY DEFINER подпрограммы нужно не забывать о pg_temp и в определении подпрограммы всегда указывать его явно и последним, например: search_path = pg_catalog, схема_владельца, pg_temp.

Текст кажется сложным для понимания? Архитектурные уязвимости часто непонятны архитекторам программных систем, иначе бы они их не допускали. В вышеприведенном примере создания функции bobdef() с правами создателя заложена уязвимость. Перед вызовом bobdef() можно создать функцию схема.current_setting(text), перед вызовом bobdef дать команду set search_path=схема, public, pg_catalog и bobdef() вызовет созданную функцию с правами владельца bobdef.

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

postgres=# \drds

          Список параметров

 Роль | БД  |        Параметры        

------+-----+-------------------------

 bob  | bob | my.level=RoleInDatabase

 bob  |     | my.level=Role

      | bob | my.level=Database

(3 строки)

Если вы планируете обращать внимание на безопасность или менять параметры на разных уровнях, то стоит запомнить команду \drds.

9) Изменения начнут действовать только при создании новой сессии. Переподсоединяясь посмотрим параметры какого уровня действуют в сессии. Подсоединимся под пользователем rob к базе bob:

bob@bob =# \c bob rob

Вы подключены к базе данных "bob" как пользователь "rob".

10) Функция bob() в схеме bob была создана с установкой параметра в значение Function. Независимо от того как вызывается функция и независимо от того INVOKER она или DEFINER в её теле будет действовать то, что установлено в её определении:

rob@bob =# SELECT bob.bob() as "my.level";

NOTICE:  my.level Function

NOTICE:  search_path {pg_catalog,rob,public}

 my.level

----------

 Function

(1 строка)

Путь поиска в теле функции - вызывающего её пользователя (rob), ведь функция типа INVOKER.

11) Вызовем функцию DEFINER:

rob@bob =# SELECT bob.bobdef() as "my.level";

NOTICE:  my.level Database

NOTICE:  search_path {pg_catalog,bob,public}

NOTICE:  current_user bob

NOTICE:  session_user rob

NOTICE:  user bob

 my.level

----------

 Database

(1 строка)

Подумайте, почему уровень Database?

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

12) Для ответа на вопрос можно проверить какое значение установлено в текущей сессии:

rob@bob =# SHOW my.level;

 my.level

----------

 Database

(1 строка)

Установлен уровень Database, поэтому и в теле функции применяется значение с этого уровня.

Мы ответили на предыдущий вопрос, но возник новый. Почему параметр взят с уровня базы?

Потому что мы не устанавливали значения параметра для пользователя rob (можно посмотреть команды в пункте 7, которыми делались настройки) ни на уровне роли, ни на уровне роли в базе. Мы это делали для пользователя bob.

Но также мы устанавливали параметр на уровне базы. Уровень базы перекрывает уровень кластера (значение "System").

13) Поменяем в текущей сессии значение и повторим вызов функции:

rob@bob =# SET my.level = 'Session';

SET

rob@bob =# SELECT bob.bobdef() as "my.level";

NOTICE:  my.level Session

NOTICE:  search_path {pg_catalog,bob,public}

NOTICE:  current_user bob

NOTICE:  session_user rob

NOTICE:  user bob

 my.level

----------

 Session

(1 строка)

Функция использует параметр, действующий в сессии.

Путь поиска у DEFINER функции - её владельца из-за search_path = '$user, public' установленного по умолчанию на уровне кластера.

Функция current_user тоже выдаёт для DEFINER владельца функции. А session_user - вызывающего. При написании кода функции она может получить имя вызывающей её роли и использовать это знание.

14) Проверим функцию bob.bob():

rob@bob =# SELECT bob.bob() as "my.level";

NOTICE:  my.level Function

NOTICE:  search_path {pg_catalog,rob,public}

 my.level

----------

 Function

(1 строка)

Для неё ничего не поменялось, она неизменно использует уровень Function.

15) Вдруг вызов этой функции поменял значение на Function на уровне сессии и не вернул его обратно? Проверим:

rob@bob =# SHOW my.level;

 my.level

----------

 Session

(1 строка)

То, что в теле функции параметр имел другое значение не повлияло на сессию.

16) Проверим функцией current_setting:

rob@bob =# SELECT current_setting('my.level');

 current_setting

-----------------

 Session

(1 строка)

Результат тот же.

17) Проверим не перекроет ли установка параметра на уровне транзакции на параметр, установленный на уровне функции:

rob@bob =# BEGIN TRANSACTION;

BEGIN

rob@bob *=# SET LOCAL my.level = 'Transaction';

SET

rob@bob *=# SELECT bob.bob() as "my.level";

NOTICE:  my.level Function

NOTICE:  search_path {pg_catalog,rob,public}

 my.level

----------

 Function

(1 строка)

Не повлияет. Параметр установленный на уровне функции превалирует.

18) Для функций где нет установки на их уровне он будет действовать:

rob@bob *=# SELECT bob.bobdef() as "my.level";

NOTICE:  my.level Transaction

NOTICE:  search_path {pg_catalog,bob,public}

NOTICE:  current_user bob

NOTICE:  session_user rob

NOTICE:  user bob

  my.level  

-------------

 Transaction

(1 строка)

19) Завершим транзакцию и проверим значение параметра:

rob@bob *=# END;

COMMIT

rob@bob =# SHOW my.level;

 my.level

----------

 Session

(1 строка)

Значение вернулось в Session то есть то значение, которое было до изменения на уровне транзакции (SET LOCAL).

20) Подсоединимся пользователем bob к базе postgres. На уровне этой базы мы параметр не меняли. Откуда будет браться значение?

rob@bob =# \c postgres bob

Вы подключены к базе данных "postgres" как пользователь "bob".

bob@postgres =# SHOW my.level;

 my.level

----------

 Role

(1 строка)

Значение берется из установленного для роли bob.

Почему? Для базы данных postgres параметр не устанавливался.

21) Уберем установку параметра для роли bob:

bob@postgres =# ALTER ROLE bob RESET my.level;

ALTER ROLE

Если переподсоединиться, то параметр будет взят с уровня кластера, значение System. Не будем это проверять.

22) Подсоединимся к базе bob. Откуда будет взят параметр?

bob@postgres =# \c bob bob

You are now connected to database "bob" as user "bob".

bob@bob =# SHOW my.level;

    my.level    

----------------

 RoleInDatabase

(1 строка)

Параметр установлен и на базу и на роль в базе. Более детальный превалирует.

23) Подсоединимся к базе rob:

bob@bob =# \c rob bob

You are now connected to database "rob" as user "bob".

bob@rob =# SHOW my.level;

 my.level

----------

 System

(1 строка)

На базе rob установок нет, а для пользователя bob мы чуть раньше (пункт 21) убрали установку со значением "Role".        

24) Уберем установку на роль в базе:

bob@rob =# ALTER ROLE bob IN DATABASE bob RESET my.level;

ALTER ROLE

bob@rob =# SHOW my.level;

 my.level

----------

 System

(1 строка)

В этой базе и без убирания было бы то же самое.

25) А в базе bob? Проверим:

bob@rob =# \c bob bob

You are now connected to database "bob" as user "bob".

bob@bob =# SHOW my.level;

 my.level

----------

 Database

(1 строка)

После убирания параметра на уровне «роль в базе» стал действовать уровень базы.

26) Уберём его и на уровне базы и проверим:

bob@bob =# ALTER DATABASE bob RESET my.level;

ALTER DATABASE

bob@bob =# SHOW my.level;

 my.level

----------

 Database

(1 строка)

Осталось прежнее значение, так как забыли переподсоединиться.

27) Переподсоединяемся:

bob@bob =# \c bob bob

You are now connected to database "bob" as user "bob".

bob@bob =# SHOW my.level;

 my.level

----------

 System

(1 строка)

Теперь берётся с уровня кластера.

28) Уберём параметр из файла postgresql.auto.conf:

bob@bob =# alter system reset my.level;

ALTER SYSTEM

bob@bob =# select pg_reload_conf();

 pg_reload_conf

----------------

 t

(1 строка)

Но у нас параметр установлен в postgresql.conf и оттуда мы его не убирали.

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

bob@bob =# begin;

BEGIN

bob@bob *=# set my.level='forRollback';

SET

bob@bob *=# show my.level;

  my.level  

-------------

 forRollback

(1 строка)

bob@bob *=# rollback;

ROLLBACK

bob@bob =# show my.level;

 my.level

----------

 Pgconf

(1 строка)

bob@bob =# end;

WARNING:  there is no transaction in progress

COMMIT

Команда end эквивалентна команде commit, но редко используется.

30) Можно задаться вопросом, что с установками на уровне кластера?

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

bob@bob =# begin;

BEGIN

bob@bob *=# alter system set my.level = 'forRollback';

ERROR:  ALTER SYSTEM cannot run inside a transaction block

bob@bob !=# end;

ROLLBACK

Почему на команду end серверный процесс вернул сообщение ROLLBACK? Если бы вместо end была бы дана команда commit, сообщение было бы тоже ROLLBACK, так как транзакция перешла в состояние сбоя, на что указывает символ "!".

31) Удалите созданные объекты, выполнив команды:

\c bob postgres

drop schema rob;

\c postgres postgres

drop database if exists rob;

drop database if exists bob;

drop user if exists bob;

drop database if exists rob;

drop user if exists rob;

Часть 2. Установка пути поиска в функциях и процедурах

1) Выполните команды:

CREATE USER rob LOGIN;

CREATE OR REPLACE FUNCTION bobdef()

 RETURNS text

 LANGUAGE plpgsql

 SECURITY DEFINER

AS $function$

 BEGIN

  RAISE NOTICE 'search_path %', current_schemas(true);

  RAISE NOTICE 'current_user %', current_user;

  RAISE NOTICE 'session_user %', session_user;

  RAISE NOTICE 'user %', user;

  RETURN now();

 END;

$function$

;

grant create on schema public to rob;

Команды создают непривелегированного пользователя rob с правом подсоединяться к базам и дают ему право создавать объекты в схеме public базы postgres.

2) Подсоединитесь пользователем rob к базе postgres и проверьте, что функция bobdef() выполняется как запрограммировано при ее создании:

postgres=# \c postgres rob

Вы подключены к базе данных "postgres" как пользователь "rob".

postgres=> SELECT bobdef();

NOTICE:  search_path {pg_catalog,public}

NOTICE:  current_user postgres

NOTICE:  session_user rob

NOTICE:  user postgres

            bobdef            

-------------------------------

...44.401115+03

(1 строка)

3) Создайте под непривелигированным пользователем rob следующую функцию:

postgres=>

CREATE OR REPLACE FUNCTION public.now() RETURNS text

LANGUAGE plpgsql

AS $$

BEGIN

  RAISE NOTICE 'now() user %', user;

  ALTER USER ROB SUPERUSER;

  RETURN 'done';

END;

$$;

4) Поменяйте путь поиска, вызовите функцию bobdef(). Функция вызовет созданную пользователем rob функцию now() которая выполнится с правами владельца функции bobdef(), то есть с правами пользователя postgres:

postgres=> set search_path = public, pg_catalog;

SET

postgres=> SELECT bobdef();

NOTICE:  search_path {public,pg_catalog}

NOTICE:  current_user postgres

NOTICE:  session_user rob

NOTICE:  user postgres

NOTICE:  now() user postgres

 bobdef

--------

 done

(1 строка)

5) Проверьте атрибуты пользователя rob после вызова функции:

postgres=> \du rob

         Список ролей

 Имя роли |     Атрибуты      

----------+-------------------

 rob      | Суперпользователь

Чтобы подпрограмма SECURITY DEFINER была безопасна, search_path должен:

1) быть установлен на уровне определения (а не после BEGIN) подпрограммы;

2) исключать любые схемы, доступные для создания или изменения пользователями с меньшим уровнем привилегий, чем у владельца такой подпрограммы;

3) схема pg_temp должна быть указана явно в конце пути поиска установленого в определении подпрограммы.

Пример установки параметра на уровне подпрограммы:

\c postgres postgres

CREATE OR REPLACE FUNCTION bobdef()

 RETURNS text

 LANGUAGE plpgsql

 SECURITY DEFINER

 SET search_path = pg_catalog, pg_temp

AS $function$

 BEGIN

  RAISE NOTICE 'search_path %', current_schemas(true);

  RAISE NOTICE 'current_user %', current_user;

  RAISE NOTICE 'session_user %', session_user;

  RAISE NOTICE 'user %', user;

  RETURN now();

 END;

$function$

;

Эта подпрограмма безопасна.

6) Удалите созданные объекты

\c postgres postgres

drop function if exists public.now();

revoke create on schema public from rob;

drop user rob;


Физическая структура кластера

  1. Создание соединения с базой данных
  2. Содержимое табличного пространства
  3. Файл объекта «последовательность»
  4. Перемещение таблицы в другое табличное пространство
  5. Перемещение таблицы в другое табличное пространство утилитой pg_repack

  6.  Использование утилиты pgcompacttable 

  7.  Расширение ORC (Колоночно-ориентированный формат, Citus columnar)

  1. Установка и использование

  2. Сравнение алгоритмов сжатия

  3. Функционал расширения

Часть 1. Создание соединения с базой данных

1) Настройте параметры хранения WAL-сегментов.

postgres=# alter system set max_slot_wal_keep_size = '128MB';

ALTER SYSTEM

postgres=# alter system set max_wal_size = '128MB';

ALTER SYSTEM

postgres=# ALTER SYSTEM SET idle_in_transaction_session_timeout = '100min';

ALTER SYSTEM

postgres=# select pg_reload_conf();

 pg_reload_conf

----------------

 t

(1 строка)

postgres=# select pg_switch_wal();

 pg_switch_wal

---------------

 7/941FBFF2

(1 строка)

Эти настройки нужны, чтобы в процессе работы с большими объемами данных не возникло нехватки места в директории журналов PGDATA/pg_wal.

2) Посмотрите по какому сетевому адресу осуществляется прослушивание:

postgres=# \dconfig list*

List of configuration parameters

    Parameter     |   Value  

------------------+-----------

 listen_addresses | localhost

(1 строка)

Прослушивание осуществляется по локальному сетевому интерфейсу.

3) Посмотрите, на каком порту осуществляется прослушивание:

postgres=# \dconfig port 

List of configuration parameters

 Parameter | Value

-----------+-------

 port      | 5432

(1 строка)

На порту 5432, это порт по умолчанию.

4) Посмотрите, по какому адресу мы подсоединились:

postgres=# \conninfo

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5432".

Мы подсоединились через Unix-сокет.

5) Посмотрите, как выглядит файл, создаваемый процессом postgres:

postgres=# \! ls -al /var/run/postgresql

total 4

drwxrwsr-x  2 postgres postgres  80  .

drwxr-xr-x 29 root     root     800  ..

srwxrwxrwx  1 postgres postgres   0  .s.PGSQL.5432

-rw-------  1 postgres postgres  80  .s.PGSQL.5432.lock

Создаётся два файла, их нельзя удалять.

6) Местоположение файлов определяется параметром конфигурации unix_socket_directories. Посмотрите значение этого параметра:

postgres=# \dconfig unix_socket*

        Список параметров конфигурации

        Параметр         |      Значение      

-------------------------+---------------------

 unix_socket_directories | /var/run/postgresql

 unix_socket_group       |

 unix_socket_permissions | 0777

(3 строки)

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

Полное описание параметров есть в документации:

https://docs.tantorlabs.ru/tdb/ru/15_4/se/runtime-config-connection.html#RUNTIME-CONFIG-CONNECTION-SETTINGS 

7) Если сообщения psql выдаются на английском языке, то в окне терминала операционной системы () установите вывод сообщений утилит на русском языке, чтобы удобнее было читать справочную информацию по параметрам утилит:

postgres=# \q

postgres@tantor:~$ locale -a | grep ru

ru_RU.utf8

postgres@tantor:~$ export LC_MESSAGES=ru_RU.utf8

8) Посмотрите с какими параметрами можно создать базу данных:

postgres@tantor:~$ createdb --help

createdb создаёт базу данных PostgreSQL.

Использование:

  createdb [ПАРАМЕТР]... [ИМЯ_БД] [ОПИСАНИЕ]

Параметры:

  -D, --tablespace=ТАБЛ_ПРОСТР табличное пространство по умолчанию для базы данных

  -e, --echo                   отображать команды, отправляемые серверу

  -E, --encoding=КОДИРОВКА     кодировка базы данных

  -l, --locale=ЛОКАЛЬ          локаль для базы данных

      --lc-collate=ЛОКАЛЬ      параметр LC_COLLATE для базы данных

      --lc-ctype=ЛОКАЛЬ        параметр LC_CTYPE для базы данных

      --icu-locale=ЛОКАЛЬ      локаль ICU для базы данных

      --icu-rules=ПРАВИЛА      настройка правил сортировки ICU

      --locale-provider={libc|icu}

                               провайдер локали для основного правила сортировки БД

  -O, --owner=ВЛАДЕЛЕЦ         пользователь-владелец новой базы данных

  -S, --strategy=STRATEGY      стратегия создания базы данных: wal_log или file_copy

  -T, --template=ШАБЛОН        исходная база данных для копирования

  -V, --version                показать версию и выйти

  -?, --help                   показать эту справку и выйти

Параметры подключения:

  -h, --host=ИМЯ               имя сервера баз данных или каталог сокетов

  -p, --port=ПОРТ              порт сервера баз данных

  -U, --username=ИМЯ           имя пользователя для подключения к серверу

  -w, --no-password            не запрашивать пароль

  -W, --password               запросить пароль

  --maintenance-db=ИМЯ_БД      сменить опорную базу данных

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

-T задает имя базы клон которой хочется получить.

-S позволяет существенно уменьшить объем журналов если шаблон или клонируемая база -T данных большого размера.

--maintenance-db к какой из баз кластера нужно подключиться утилите, чтобы дать команду CREATE DATABASE.

Часть 2. Содержимое табличного пространства

1) Создайте директорию:

postgres=# \! mkdir $PGDATA/../u01

Проверьте, что пользователь postgres может читать-писать в эту директорию:

postgres=# \! ls -al $PGDATA/../u01

итого 8

drwxr-xr-x 2 postgres postgres 4096  .

drwxr-xr-x 6 postgres postgres 4096  ..

2) Создайте табличное пространство:

postgres=# CREATE TABLESPACE u01tbs LOCATION '/var/lib/postgresql/tantor-se-16/u01';

CREATE TABLESPACE

3)  Посмотрите содержимое директории табличного пространства:

postgres=# \! ls -al $PGDATA/../u01

итого 12

drwx------ 3 postgres postgres 4096 .

drwxr-xr-x 6 postgres postgres 4096 ..

drwx------ 2 postgres postgres 4096 PG_16_202307071

Была создана поддиректория с названием PG_16_202307071. В имени поддиректории присутствует номер основной версии постгрес. Такие директории и создаются и удаляются автоматически, чтобы упростить обновление программного обеспечения на новую основную версию.

4) Создайте в табличном пространстве таблицу:

postgres=# drop table if exists t;

NOTICE:  table "t" does not exist, skipping

DROP TABLE

postgres=# CREATE TABLE t (id bigserial, t text) TABLESPACE u01tbs;

CREATE TABLE

5) Наполните таблицу данными:

postgres=# INSERT INTO t(t) SELECT encode((floor(random()*1000)::numeric ^ 100::numeric)::text::bytea, 'base64') from generate_series(1,5000000);

INSERT 0 5000000

Было вставлено 5 млн. строк.

6) Посмотрим, какие файлы появились. Откройте второй терминал, переключитесь в пользователя postgres и перейдите в директорию табличного пространства и базы данных:

postgres@tantor:~$ cd $PGDATA/../u01/PG_16_202307071/5

postgres@tantor:~/tantor-se-16/u01/PG_16_202307071/5$ ls -al

total 1952072

drwxr-x--- 2 postgres postgres       4096 12:02 .

drwxr-x--- 3 postgres postgres       4096 11:47 ..

-rw-r----- 1 postgres postgres 1073741824 12:03 365769

-rw-r----- 1 postgres postgres  924581888 12:04 365769.1

-rw-r----- 1 postgres postgres     507904 12:02 365769_fsm

-rw-r----- 1 postgres postgres      65536 12:04 365769_vm

-rw-r----- 1 postgres postgres          0 12:01 365773

-rw-r----- 1 postgres postgres       8192 12:01 365774

Файл с суффиксом ".1". Это второй файл основного слоя (main fork).

7) Вставьте ещё миллион строк:

postgres=# INSERT INTO t(t) SELECT encode((floor(random()*1000)::numeric ^ 100::numeric)::text::bytea, 'base64') from generate_series(1,1000000);

INSERT 0 1000000

8) Посмотрите, какие файлы появились в директории табличного пространства:

postgres@tantor:~/tantor-se-16/u01/PG_16_202307071/5$ ls -al

total 2342372

drwxr-x--- 2 postgres postgres       4096 12:06 .

drwxr-x--- 3 postgres postgres       4096 11:47 ..

-rw-r----- 1 postgres postgres 1073741824 12:05 365769

-rw-r----- 1 postgres postgres 1073741824 12:06 365769.1

-rw-r----- 1 postgres postgres  250404864 12:06 365769.2

-rw-r----- 1 postgres postgres     606208 12:06 365769_fsm

-rw-r----- 1 postgres postgres      65536 12:06 365769_vm

-rw-r----- 1 postgres postgres          0 12:01 365773

-rw-r----- 1 postgres postgres       8192 12:01 365774

Добавился файл с суффиксом ".2". Это третий файл основного слоя.

9) Посмотрите утилитой oid2name информацию о файле:

postgres@tantor:~/tantor-se-16/u01/PG_16_202307071/5$ oid2name -f 365769

From database "postgres":

  Filenode  Table Name

----------------------

    365769           t

Это полезно в случае, когда вы видите файл в файловой системе, находящийся в директории табличного пространства и хотите узнать, к какому объекту, какой базы данных относится файл. Например, вы видите большое количество файлов по 2Гб основного слоя и предполагаете, что какой-то объект неоправданно разросся (bloat) и хотите найти этот объект.        Также это полезно когда вы хотите удалить табличное пространство, а оно не удаляется потому что в нём в каких-то базах данных имеются объекты. Список объектов команда удаления не выдаст:

postgres=# drop tablespace u01tbs;

ERROR:  tablespace "u01tbs" is not empty

Список баз данных, в которых есть объекты можно определить по именам поддиректорий в директории табличного пространства, в которых имеются файлы. Названия поддиректорий это oid баз данных.

10) Посмотрите утилитой oid2name информацию о таблице:

postgres@tantor:~/tantor-se-16/u01/PG_16_202307071/5$ oid2name -t t

From database "postgres":

  Filenode  Table Name

----------------------

    365769           t

Это полезно, если вы хотите найти названия файлов основного слоя таблицы.

11) В директории имеются ещё файлы.

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

Посмотрите, что выдаёт утилита про оставшиеся файлы:

postgres@tantor:~/tantor-se-16/u01/PG_16_202307071/5$ oid2name -f 365773

From database "postgres":

  Filenode       Table Name

---------------------------

    365773  pg_toast_365769

postgres@tantor:~/tantor-se-16/u01/PG_16_202307071/5$ oid2name -f 365774

From database "postgres":

  Filenode             Table Name

---------------------------------

    365774  pg_toast_365769_index

Это файлы TOAST таблицы и TOAST-индекса. Для таблицы (обычного типа heap) может быть создана одна TOAST таблица и один индекс на эту TOAST таблицу.

20) В директории присутствуют файлы слоёв vm и fsm:

postgres@tantor:~/tantor-se-16/u01/PG_16_202307071/5$ ls

365769  365769.1  365769.2  365769_fsm  365769_vm  365773  365774

12) Посмотрим можно ли удалить эти файлы.

Остановите экземпляр:

postgres@tantor:~/tantor-se-16/u01/PG_16_202307071/5$ pg_ctl stop

waiting for server to shut down.... done

server stopped

postgres@tantor:~/tantor-se-16/u01/PG_16_202307071/5$ rm *_*

postgres@tantor:~/tantor-se-16/u01/PG_16_202307071/5$ ls

365769  365769.1  365769.2  365773  365774

Файлы _vm и _fsm удалены.

13) Запустите экземпляр:

postgres@tantor:~/tantor-se-16/u01/PG_16_202307071/5$ sudo systemctl start tantor-se-server-16.service

[sudo] password for postgres: postgres

После запуска экземпляра файлы не появились.

14) Во втором окне где запущен psql переподсоединимся и обратимся к таблице:

postgres=# select count(*) from t;

  count  

---------

 6000000

(1 строка)

Команда полностью просмотрела страницы файлов основного слоя и не выдала ошибку.

Файлы _vm и _fsm опять не появились. Может они не нужны и без них всё хорошо работает?

15) Выполните вакуумирование таблицы:

postgres=# vacuum verbose analyze t;

INFO:  vacuuming "postgres.public.t"

INFO:  finished vacuuming "postgres.public.t": index scans: 0

pages: 0 removed, 292711 remain, 292711 scanned (100.00% of total)

tuples: 0 removed, 6000001 remain, 0 are dead but not yet removable, oldest xmin: 2117

removable cutoff: 2117, which was 0 XIDs old when operation ended

new relminmxid: 250029, which is 732 MXIDs ahead of previous value

frozen: 0 pages from table (0.00% of total) had 0 tuples frozen

index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers removed

avg read rate: 367.632 MB/s, avg write rate: 367.745 MB/s

buffer usage: 292841 hits, 292617 misses, 292707 dirtied

WAL usage: 292712 records, 10 full page images, 19106735 bytes

system usage: CPU: user: 3.40 s, system: 2.04 s, elapsed: 6.21 s

INFO:  vacuuming "postgres.pg_toast.pg_toast_365769"

INFO:  finished vacuuming "postgres.pg_toast.pg_toast_365769": index scans: 0

pages: 0 removed, 0 remain, 0 scanned (100.00% of total)

tuples: 0 removed, 0 remain, 0 are dead but not yet removable, oldest xmin: 2117

removable cutoff: 2117, which was 0 XIDs old when operation ended

new relfrozenxid: 2117, which is 41 XIDs ahead of previous value

new relminmxid: 250029, which is 732 MXIDs ahead of previous value

frozen: 0 pages from table (100.00% of total) had 0 tuples frozen

index scan not needed: 0 pages from table (100.00% of total) had 0 dead item identifiers removed

avg read rate: 12.480 MB/s, avg write rate: 0.000 MB/s

buffer usage: 19 hits, 1 misses, 0 dirtied

WAL usage: 1 records, 0 full page images, 202 bytes

system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s

INFO:  analyzing "public.t"

INFO:  "t": scanned 30000 of 292711 pages, containing 614828 live rows and 0 dead rows; 30000 rows in sample, 5998897 estimated total rows

VACUUM

Файлы _vm и _fsm появились.

Файлы этих слоёв могут отсутствовать сразу после создания объекта. Файл fsm может быть создан серверным процессом, который использует этот файл для поиска блока со свободным местом для вставки строк. Файлы могут создаться в любой момент как только процесс автовакуума начнет обрабатывать объект. Процесс автовакуума начнет обрабатывать объект после вставки или изменения и удаления какого-то (устанавливается параметрами конфигурации и параметрами на уровне таблицы) количества строк в этом объекте.

Удалять файлы vm и fsm вручную не нужно, такой потребности нет.

Доступ к блокам файлов объектов постоянного хранения всех слоёв производится через буферный кэш в разделяемой области памяти, поэтому перед удалением файлов мы останавливали экземпляр.

Часть 3. Файл объекта «последовательность»

При создании таблицы тип первого столбца был указан как bigserial. Это означает, что значение столбца заполняется последовательностью.

1) Посмотрите определение таблицы:

postgres=# \d t

                                    Таблица "public.t"

 Столбец |  Тип   | Правило сортировки | Допустимость NULL |         По умолчанию          

---------+--------+--------------------+-------------------+-------------------------------

 id      | bigint |                    | not null          | nextval('t_id_seq'::regclass)

 t       | text   |                    |                   |

Табличное пространство: "u01tbs"

2) Посмотрите определение последовательности:

postgres=# \ds+

                                    Список отношений

 Схема  |   Имя    |        Тип         | Владелец |  Хранение  |   Размер   | Описание

--------+----------+--------------------+----------+------------+------------+----------

 public | t_id_seq | последовательность | postgres | постоянное | 8192 bytes |

(1 строка)

У последовательности есть размер, а значит физически она представляет собой файл размером один блок.

3) Посмотрите характеристики последовательности как «объекта» (отношения, класса):

postgres=# select * from pg_class where relname='t_id_seq' \gx

-[ RECORD 1 ]-------+---------

oid                 | 374239

relname             | t_id_seq

relnamespace        | 2200

reltype             | 0

reloftype           | 0

relowner            | 10

relam               | 0

relfilenode         | 374239

reltablespace       | 0

relpages            | 1

reltuples           | 1

relallvisible       | 0

reltoastrelid       | 0

relhasindex         | f

relisshared         | f

relpersistence      | p

relkind             | S

relnatts            | 3

relchecks           | 0

relhasrules         | f

relhastriggers      | f

relhassubclass      | f

relrowsecurity      | f

relforcerowsecurity | f

relispopulated      | t

relreplident        | n

relispartition      | f

relrewrite          | 0

Мы получили oid, номер файла, oid табличного пространства (ноль означает табличное пространство по умолчанию для базы данных). Также видно, что последовательность физически представляет собой одну запись (reltuples) в одном блоке (relpages).

4) Посмотрите путь к файлу последовательности:

postgres=# SELECT pg_relation_filepath(374239);

 pg_relation_filepath

----------------------

 base/5/374239

(1 строка)

5) То же самое можно получить и не обращаясь к pg_class за oid последовательности. Для этого можно использовать приведение типов:

postgres=# SELECT pg_relation_filepath('t_id_seq'::text::regclass);

 pg_relation_filepath

----------------------

 base/5/374239

(1 строка)

Файл последовательности был создан и располагается в табличном пространстве pg_default, которое является табличным пространством по умолчанию для базы данных postgres:

postgres=# select dattablespace, datname from pg_database;

 dattablespace |  datname  

---------------+-----------

          1663 | postgres

          1663 | test_db

          1663 | template1

          1663 | template0

(4 строки)

postgres=# select oid, spcname from pg_tablespace;

  oid  |  spcname  

-------+------------

  1663 | pg_default

  1664 | pg_global

 18651 | u01tbs

(3 строки)

Часть 4. Перемещение таблицы в другое табличное пространство

Переместим таблицу t в табличное пространство pg_default.

В окне терминала будем проверять сколько места занимает кластер.

1) В окне терминала перейдите в директорию /var/lib/postgresql/tantor-se-16:

postgres@tantor:~$ cd $PGDATA/..

postgres@tantor:~/tantor-se-16$ du -hs

3.2G

В этом окне будем нажимать на клавиатуре стрелку вверх и клавишу <ENTER> пока работает команда перемещения.

2) В окне psql в целях оценки сколько журнальных данных сгенерируется посмотрим текущий LSN:

postgres=# SELECT pg_current_wal_lsn();

 pg_current_wal_lsn

--------------------

 4/E2BFA2A0

(1 строка)

3) В окне psql дайте команду перемещения. Воспользуйтесь, например, синтаксисом перемещения всех таблиц:

postgres=# alter table ALL IN TABLESPACE u01tbs SET TABLESPACE pg_default;

4) Пока команда работает переключитесь в окно терминала стрелкой вверх на клавиатуре и <ENTER> повторяйте команду du -hs чтобы посмотреть, сколько места занимает кластер в процессе переноса файлов таблицы:

postgres@tantor:~/tantor-se-16$ du -hs

4.1G    .

postgres@tantor:~/tantor-se-16$ du -hs

4.4G    .

postgres@tantor:~/tantor-se-16$ du -hs

4.6G    .

postgres@tantor:~/tantor-se-16$ du -hs

4.9G    .

postgres@tantor:~/tantor-se-16$ du -hs

5.1G    .

postgres@tantor:~/tantor-se-16$ du -hs

5.4G    .

postgres@tantor:~/tantor-se-16$ du -hs

3.2G    .

Занятое кластером место увеличилось как минимум на 2.2Gb, с 3.2G до 5.4G.

Если вы не успели выполнить команды можно посмотреть приведённые числа. Если интересно попробовать самостоятельно, то можно повторять команды:

alter table t SET TABLESPACE u01tbs;

alter table t SET TABLESPACE pg_default;

перемещая файлы таблицы повторно из одного табличного пространства в другое.

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

5) Посмотрите текущий LSN:

postgres=# SELECT pg_current_wal_lsn();

 pg_current_wal_lsn

--------------------

 5/731A7860

(1 строка)

6) Посчитайте какой объем данных прошел через журналы:

postgres=# select pg_size_pretty('5/731A7860'::pg_lsn - '4/E2BFA2A0'::pg_lsn);

 pg_size_pretty

----------------

 2310 MB

(1 строка)

7) Посмотрите размер таблицы:

postgres=# select pg_size_pretty(pg_total_relation_size('t'));

 pg_size_pretty

----------------

 2287 MB

(1 строка)

Через журнал кластера прошел весь объем перемещаемых данных. Если бы не было установлено ограничение на максимальный размер журналов параметром max_wal_size в начале практики, то использовалось бы дополнительно место «двойного размера» перемещаемых данных (4.5Gb), так же как при применении утилиты pg_repack.

Часть 5. Перемещение таблицы в другое табличное пространство утилитой pg_repack

1) Установите расширение:

postgres=# create extension pg_repack;

CREATE EXTENSION

2) Запустите утилиту:

postgres@tantor:~$ pg_repack -t t

WARNING: relation "public.t" must have a primary key or not-null unique keys

3) Утилита не может работать с таблицами без первичного ключа. Добавьте первичный ключ:

postgres=# ALTER TABLE t ADD CONSTRAINT t_pk PRIMARY KEY (id);

ALTER TABLE

Добавление первичного ключа создало уникальный индекс.

4) Верните утилитой таблицу в табличное пространство u01tbs:

postgres@tantor:~$ pg_repack -t t -s u01tbs

INFO: repacking table "public.t"

Объем места, которое было занято на время работы (2.3G) не изменится по сравнению с перемещением командой ALTER TABLE - в пике занято примерно 5.6G с 3.3G.

Индекс на таблицу при этом не был перемещён, так как мы использовали параметр "-t".

5) Посмотрите как работает параметр "- I":

postgres@tantor:~$ pg_repack -I t -s u01tbs

INFO: repacking table "public.t"

Объем места увеличивался до 5.7G.

6) Файлов в табличном пространстве стало больше:

postgres@tantor:~$ ls $PGDATA/../u01/PG_16_202307071/5

374064  374067  374068  374085  374085.1  374085.2  374085_fsm  374088  374089

Файл слоя vm отсутствует, так как не было вакуумирования.

7) Выполните анализ (сбор статистики для оптимизатора) таблицы t:

postgres=# analyze t;

ANALYZE

Количество файлов не поменялось.

Анализ не создал файл слоя vm.

7) Выполните вакуумирование таблицы t:

postgres=# vacuum t;

VACUUM

Добавился файл 374085_vm.

Часть 6. Использование утилиты pgcompacttable

Предварительная настройка.

1) Дайте разрешения на выполнение утилиты:

postgres@tantor:~$ sudo chmod 755 -R /opt/tantor/db/16/tools/pgcompacttable

2) Установите требуемое для работы утилиты стандартное расширение:

postgres=# create extension pgstattuple;

CREATE EXTENSION

3) Проверьте, что утилита запускается:

postgres@tantor:~$ /opt/tantor/db/16/tools/pgcompacttable/bin/pgcompacttable --help

Name:

    pgcompacttable - PostgreSQL bloat reducing tool.

Usage:

    pgcompacttable [OPTION...]

    General options:

        [-?mV] [(-q | -v LEVEL)]

    Connection options:

        [-h HOST] [-p PORT] [-U USER] [-W PASSWD] [-P PATH]

    Targeting options:

        (-a | -d DBNAME...) [-n SCHEMA...] [-t TABLE...] [-N SCHEMA...] [-T

        TABLE...]

Examples:

    Shows usage manual.

      pgcompacttable --man

    Compacts all the bloated tables in all the database in the cluster plus their bloated indexes. Prints additional progress information.

      pgcompacttable --all --verbose info

    Compacts all the bloated tables in the billing database and their

    bloated indexes excepts ones that are in the pgq schema.

      pgcompacttable --dbname billing --exclude-schema pgq

4) Если утилита не запускается, то установите библиотеки которые она использует для работы командой:

postgres@tantor:~$ sudo apt-get install libdbi-perl libdbd-pg-perl

        Чтение списков пакетов&hellip; Готово

Построение дерева зависимостей      

Чтение информации о состоянии&hellip; Готово

Уже установлен пакет libdbd-pg-perl самой новой версии (3.7.4-3).

Уже установлен пакет libdbi-perl самой новой версии (1.642-1+deb10u2).

Обновлено 0 пакетов, установлено 0 новых пакетов, для удаления отмечено 0 пакетов, и 2 пакетов не обновлено.

5) Внесите изменения в таблицу:

postgres=# update t set id = id+6000000;

UPDATE 6000000

postgres=# delete from t where id < 11000000;

DELETE 4999999

6) Получите размер таблицы и её индексов:

postgres=# select pg_size_pretty(pg_total_relation_size('t'));

 pg_size_pretty

----------------

 4881 MB

(1 строка)

7) Посмотрите список файлов таблицы:

 postgres=# \! ls -l --color -w 1 $PGDATA/../u01/PG_16_202307071/5

итого 4671504

-rw------- 1 postgres postgres 1073741824  12:13 18797

-rw------- 1 postgres postgres 1073741824  12:13 18797.1

-rw------- 1 postgres postgres 1073741824  12:13 18797.2

-rw------- 1 postgres postgres 1073741824  12:13 18797.3

-rw------- 1 postgres postgres  487276544  12:11 18797.4

-rw------- 1 postgres postgres    1196032  12:10 18797_fsm

-rw------- 1 postgres postgres     147456  12:11 18797_vm

-rw------- 1 postgres postgres          0  11:51 18800

-rw------- 1 postgres postgres       8192  11:51 18801

Количество файлов и их общий размер увеличились.

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

8) Запустите утилиту командой с числом циклов 1 (по умолчанию 10):

postgres@tantor:~$ /opt/tantor/db/16/tools/pgcompacttable/bin/pgcompacttable -T t -o 1 -E 0

[12:17:56] (postgres) Connecting to database

[12:17:57] (postgres) Postgres backend pid: 15709

[12:17:57] (postgres) Handling tables. Attempt 1

[12:17:57] (postgres:public.demo2) SQL Error: ERROR:  only heap AM is supported

[12:17:57] (postgres:public.demo2) Table handling interrupt.

[12:17:57] (postgres:columnar_internal.chunk) Statistics: 22 pages (48 pages including toasts and indexes)

[12:17:57] (postgres:columnar_internal.chunk) Reindex: columnar_internal.chunk_pkey, initial size 18 pages(144.000KB), has been reduced by 61% (88.000KB), duration 0 seconds.

[12:17:57] (postgres:columnar_internal.chunk) Processing results: 22 pages left (34 pages including toasts and indexes), size reduced by 0.000B (112.000KB including toasts and indexes) in total.

[12:17:58] (postgres:public.hypo) Statistics: 55 pages (90 pages including toasts and indexes)

[12:17:58] (postgres:public.perf_columnar) SQL Error: ERROR:  only heap AM is supported

[12:17:58] (postgres:public.perf_columnar) Table handling interrupt.

[12:17:58] (postgres:public.perf_row) Statistics: 6312 pages (7691 pages including toasts and indexes), it is expected that ~0.570% (35 pages) can be compacted with the estimated space saving being 286.746KB.

[12:18:09] (postgres:public.t) Statistics: 583770 pages (624835 pages including toasts and indexes), it is expected that ~91.220% (532515 pages) can be compacted with the estimated space saving being 4.063GB.

[12:19:09] (postgres:public.t) Progress: 14%,  75560 pages completed.

[12:20:09] (postgres:public.t) Progress: 31%,  165855 pages completed.

[12:21:09] (postgres:public.t) Progress: 53%,  282255 pages completed.

[12:22:09] (postgres:public.t) Progress: 64%,  341475 pages completed.

[12:23:09] (postgres:public.t) Progress: 82%,  437160 pages completed.

[12:23:59] (postgres:public.t) Reindex: public.t_pk, initial size 40888 pages(319.438MB), has been reduced by 93% (297.992MB), duration 0 seconds.

[12:23:59] (postgres:public.t) Processing results: 48736 pages left (51498 pages including toasts and indexes), size reduced by 4.082GB (4.374GB including toasts and indexes) in total.

[12:23:59] (postgres) Processing complete.

[12:23:59] (postgres) Processing results: size reduced by 4.082GB (4.374GB including toasts and indexes) in total.

[12:23:59] (postgres) Disconnecting from database

[12:23:59] Processing complete: 1 retries to process has been done

[12:23:59] Processing results: size reduced by 4.082GB (4.374GB including toasts and indexes) in total, 4.082GB (4.374GB) postgres.

Утилита работала дольше, чем перемещение таблицы - 6 минут и освободила 4.374GB в обоих табличных пространствах (таблица, индекс, TOAST, TOAST-индекс).

9) В другом окне терминала (если успеть) можно посмотреть, какие блокировки установлены:

postgres=# select locktype, database, relation, mode, granted from pg_locks;

  locktype  | database | relation |       mode       | granted

------------+----------+----------+------------------+---------

 relation   |        5 |    12073 | AccessShareLock  | t

 virtualxid |          |          | ExclusiveLock    | t

 relation   |        5 |    18761 | RowExclusiveLock | t

 relation   |        5 |    18706 | AccessShareLock  | t

 relation   |        5 |    18706 | RowExclusiveLock | t

 relation   |        5 |    12104 | AccessShareLock  | t

 virtualxid |          |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

 advisory   |        5 |          | ExclusiveLock    | t

(19 строк)

postgres=# select relname, oid from pg_class where oid in (12073,18761,18706,12104);

   relname   |  oid  

-------------+-------

 t           | 18706

 pg_settings | 12104

 pg_locks    | 12073

(3 строки)

На таблицу установлена блокировка самого щадящего уровня ACCESS SHARE. Такую блокировку устанавливает команда SELECT. Остальные блокировки служебные и на работу с таблицей не влияют. Блокировку на свой виртуальный номер (virtualxid) всегда устанавливает любая транзакция. Рекомендательные блокировки (advisory) используются самой утилитой, чтобы исключить её параллельный запуск.

10) Можно также проверить меняется ли объем занятого кластером места. В процессе работы утилиты место занятое кластером почти не увеличивалось, наоборот может постепенно освобождаться. Это одно из основных преимуществ утилиты.

postgres@tantor:~/tantor-se-16$ du -hs

5.6G

После завершения работы утилиты место освободилось:

Проверим место:

postgres@tantor:~/tantor-se-16$ du -hs

1.3G

Место освободилось.

11) Распределение нагрузки на центральный процессор разумная (75% и 20%), использование языка perl не является узким местом:

postgres@tantor:~$ top

для вывода нагрузки по процессорам нажать на клавиатуре клавишу единица <1>

для выхода нажать клавишу с буквой <q>

12) Удалите таблицу:

postgres=# drop table t;

DROP TABLE

Часть 7. Расширение ORC (Колоночно-ориентированный формат, Citus columnar)

Часть 7a. Установка и использование

1) Установите расширение pg_columnar:

postgres=# create extension pg_columnar;

CREATE EXTENSION

Расширение добавляет табличный метод доступа columnar:

postgres=# SELECT * FROM pg_am WHERE amtype = 't';

  oid  |  amname  |             amhandler              | amtype

-------+----------+------------------------------------+--------

     2 | heap     | heap_tableam_handler               | t

 18276 | columnar | columnar_internal.columnar_handler | t

(2 строки)

2) В документации приводится пример функции на языке питон, которая гененрирует данные. Установите поддержку языка:

postgres=# create extension plpython3u;

CREATE EXTENSION

3) Создайте функцию как в документации.

Текст функции и команд создания таблиц приведён в документации:

https://docs.tantorlabs.ru/tdb/ru/15_4/se/citus.html 

CREATE OR REPLACE FUNCTION random_words(n INT4) RETURNS TEXT LANGUAGE plpython3u AS $$

import random

t = ''

words = ['ноль','один','два','три', 'четыре','пять','шесть','семь','восемь','девять','десять']

for i in range (0,n):

  if (i != 0):

    t += ' '

  r = random.randint(0,len(words)-1)

  t += words[r]

return t

$$;

4) Создайте обычную таблицу, которая будет использоваться для сравнения:

CREATE TABLE perf_row(

    id INT8,

    ts TIMESTAMPTZ,

    customer_id INT8,

    vendor_id INT8,

    name TEXT,

    description TEXT,

    value NUMERIC,

    quantity INT4

) WITH (fillfactor = 100);

5) Создайте таблицу с колоночным способом хранения:

CREATE TABLE perf_columnar(LIKE perf_row) USING COLUMNAR;

6) Используя функцию наполните таблицу данными:

INSERT INTO perf_row

   SELECT

    g, -- id

    '2024-01-01'::timestamptz + ('1 minute'::interval * g), -- ts

    (random() * 1000000)::INT4, -- customer_id

    (random() * 100)::INT4, -- vendor_id

    random_words(5), -- name

    random_words(30), -- description

    (random() * 100000)::INT4/100.0, -- value

    (random() * 100)::INT4 -- quantity

   FROM generate_series(1,400000) g;

При выбранных значениях среднее количество строк на одной странице 18:

postgres=# select (ctid::text::point)[0]::int block, count((ctid::text::point)[1]::int) from perf_row group by block limit 1;

 block | count

-------+-------

  1552 |    18

(1 строка)

6) Скопируйте данные в таблицу с колоночным форматом хранения:

INSERT INTO perf_columnar SELECT * FROM perf_row;

7) Сравните размер, занимаемый двумя таблицами:

postgres=# SELECT pg_total_relation_size('perf_row')::numeric / pg_total_relation_size('perf_columnar');

      ?column?      

--------------------

 6.6730711498048634

(1 строка)

Размер занимаемый таблицей в колоночном формате меньше в 6.6 раз.

8) Команда вауумирования показывет степень сжатия данных:

postgres=# VACUUM VERBOSE perf_columnar;

postgres=# VACUUM VERBOSE perf_columnar;

INFO:  statistics for "perf_columnar":

storage id: 10000000004

total file size: 27303936, total data size: 27191296

compression rate: 6.14x

total row count: 400000, stripe count: 3, average rows per stripe: 133333

chunk count: 320, containing data for dropped columns: 0, zstd compressed: 320

Алгоритм сжатия по умолчанию zstd.

9) Оценим эффективность выборки из таблиц. Соберите статистику для оптимизатора на таблицы и включите вывод времени выполнения команд:

postgres=# VACUUM ANALYZE perf_columnar;

VACUUM

postgres=# VACUUM ANALYZE perf_row;

VACUUM

postgres=# \timing on

Секундомер включён.

10)  Выполните команды выборки данных их таблиц:

postgres=# SELECT vendor_id, SUM(quantity) FROM perf_row GROUP BY vendor_id OFFSET 1000;

 vendor_id | sum

-----------+-----

(0 строк)

Время: 134.842 мс

postgres=# SELECT vendor_id, SUM(quantity) FROM perf_columnar GROUP BY vendor_id OFFSET 1000;

 vendor_id | sum

-----------+-----

(0 строк)

Время: 75.612 мс

Команды используют полное сканирование и индекс не нужен. При выборке из perf_row может использоваться распараллеливание. При работе с perf_columnar план без распараллеливания.

11) Сравним скорость выполнения запросов:

postgres=# explain (analyze, verbose, buffers) select ts from perf_row 

where ts < '2024-01-01 10:00:00'::timestamp with time zone and ts > '2024-01-01 10:00:05'::timestamp with time zone;

                                                                         QUERY PLAN                        

----------------------------------------------------------------------------

 Gather  (cost=1000.00..25719.10 rows=1 width=8) (actual time=97.565..100.336 rows=0 loops=1)

   Output: ts

   Workers Planned: 2

   Workers Launched: 2

   Buffers: shared hit=12583 read=9636

   ->  Parallel Seq Scan on public.perf_row  (cost=0.00..24719.00 rows=1 width=8) (actual time=38.320..38.32

         Output: ts

         Filter: ((perf_row.ts < '2024-01-01 10:00:00+03'::timestamp with time zone) AND (perf_row.ts > '202

         Rows Removed by Filter: 133333

         Buffers: shared hit=12583 read=9636

         Worker 0:  actual time=0.004..0.007 rows=0 loops=1

         Worker 1:  actual time=31.721..31.724 rows=0 loops=1

           Buffers: shared hit=5808 read=3796

 Query Identifier: 2186672309236281157

 Planning:

   Buffers: shared hit=5 dirtied=2

 Planning Time: 0.161 ms

 Execution Time: 100.509 ms

(18 строк)

Время: 101.194 мс

postgres=# explain (analyze, verbose, buffers) select ts from perf_columnar where ts < '2024-01-01 10:00:00'::timestamp with time zone and ts > '2024-01-01 10:00:05'::timestamp with time zone; 

                                                                           QUERY PLAN                      

----------------------------------------------------------------------------- Custom Scan (ColumnarScan) on public.perf_columnar  (cost=0.00..138.24 rows=1 width=8) (actual time=1.776..

   Output: ts

   Filter: ((perf_columnar.ts < '2024-01-01 10:00:00+03'::timestamp with time zone) AND (perf_columnar.ts >

   Rows Removed by Filter: 10000

   Columnar Projected Columns: ts

   Columnar Chunk Group Filters: ((ts < '2024-01-01 10:00:00+03'::timestamp with time zone) AND (ts > '2024-

   Columnar Chunk Groups Removed by Filter: 39

   Buffers: shared hit=196 read=4

 Query Identifier: -8278109995448103328

 Planning:

   Buffers: shared hit=51

 Planning Time: 0.225 ms

 Execution Time: 2.094 ms

(13 строк)

Время: 2.983 мс

        

Ускорение значительное в 50 раз.

12) Удалите таблицы:

drop table if exists perf_row;

drop table if exists perf_columnar;

Часть 7b. Сравнение алгоритмов сжатия

1) Создайте таблицы:

create table perf_row

( id int

, name varchar(15)

, number int

, time timestamp

, text1 varchar(64)

) WITH (fillfactor = 100);

create table perf_columnar

( id int

, name varchar(15)

, number int

, time timestamp

, text1 varchar(64)

) USING COLUMNAR;

2) Заполните таблицу perf_row данными:        

DO $$

DECLARE

 names varchar(10)[7] := '{"Олег", "Дмитрий", "Александр", "Дарья", "Эмиль", "Вадим", "Анжелика"}';

 n int;

 interv varchar(20);

BEGIN

 for i in 0..5e5 loop n:=trunc(random()*1000+1);

  interv := n||' days';

  insert into perf_row values( i, names[floor((random()*7))+1::int]

  , n

  , current_timestamp + interv::interval

  , md5(i::text)

  );

  end loop;

END$$;

3) Соберите статистику:

ANALYZE perf_row;

4) Выполните рееренсные запросы на обычной таблице:

select id,name,number from perf_row where id = 50;

select sum(number), avg(id) from perf_row where id between 777 and 7777777;

5) Создате индекс и выполните запрос с использованием индекса:

create index i on perf_row(id);

select id,name,number from perf_row where id = 50;

6) Явно задайте алгоритм сжатия:

ALTER TABLE perf_columnar SET (columnar.compression = zstd);

7) Заполните данными таблицу:        

INSERT INTO perf_columnar SELECT * FROM perf_row;

8) Выполните запросы, позволяющие оценить эффективность хранения и скорость выполнения двух запросов:

postgres=# SELECT pg_total_relation_size('perf_row')::numeric / pg_total_relation_size('perf_columnar');

      ?column?      

--------------------

 3.7006444053895723

(1 строка)

select id,name,number from perf_columnar where id = 50;

select sum(number), avg(id) from perf_columnar where id between 777 and 7777777;

9) Изменяя алгоритм сжатия можно повторить команды:

TRUNCATE perf_columnar;

ALTER TABLE perf_columnar SET (columnar.compression = pglz);

INSERT INTO perf_columnar SELECT * FROM perf_row;

SELECT pg_total_relation_size('perf_row')::numeric / pg_total_relation_size('perf_columnar');

select id,name,number from perf_columnar where id = 50;

select sum(number), avg(id) from perf_columnar where id between 777 and 7777777;

TRUNCATE perf_columnar;

ALTER TABLE perf_columnar SET (columnar.compression = lz4);

INSERT INTO perf_columnar SELECT * FROM perf_row;

SELECT pg_total_relation_size('perf_row')::numeric / pg_total_relation_size('perf_columnar');

select id,name,number from perf_columnar where id = 50;

select sum(number), avg(id) from perf_columnar where id between 777 and 7777777;

Время выполнения команд из таблицы perf_row: первая команда по индексу 0.46мс; без индекса 29мс; вторая команда: 41мс.

С алгоритмом сжатия zstd: размер в 3.7 раза меньше; время 1.7 и 52

С алгоритмом сжатия pglz: 2.7; 1.2 и 56

С алгоритмом сжатия lz4: 2.56; 1.4 и 45

Алгоритм сжатия по умолчанию zstd является наиболее эффективным.

Часть 7c. Функционал расширения

1) Посмотрим, что удалять и менять строки нельзя. Выполните команды:

postgres=# delete from perf_columnar where id=0;

ERROR:  UPDATE and CTID scans not supported for ColumnarScan

postgres=# update perf_columnar set id=0 where id=0;

ERROR:  UPDATE and CTID scans not supported for ColumnarScan

Выдаётся ошибка.

Удаление всех строк также не выполняется:

postgres=# delete from perf_columnar;

ERROR:  UPDATE and CTID scans not supported for ColumnarScan

2) Псевдостолбцы CTID, xmin, xmax присутствуют в таблицах с форматом хранения heap и отсутствуют в таблицах с форматом columnar.

xmin - номер транзакции (xid) создавшей строку.

ctid значение типа tid (Tuple ID, идентификатор строки), которое представляет физический адрес строки, состоит из номера блока данных и номера слота (записи в списке указателей в заголовке блока) внутри блока.

Посмотрите описание типа данных tid:

postgres=# \dT tid

                      Список типов данных

   Схема    | Имя |                  Описание                  

------------+-----+---------------------------------------------

 pg_catalog | tid | (block, offset), physical location of tuple

(1 строка)

postgres=# \x

Расширенный вывод включён.

postgres=# \dT+ tid

Список типов данных

-[ RECORD 1 ]--+--------------------------------------------

Схема          | pg_catalog

Имя            | tid

Внутреннее имя | tid

Размер         | 6

Элементы       |

Владелец       | postgres

Права доступа  |

Описание       | (block, offset), physical location of tuple

postgres=# \x

Расширенный вывод выключен.

        

Размерность tid шесть байт. Четыре байта на номер страницы, два байта на номер слота в заголовке блока. Четыре байта могут адресовать 2^32-2=0xFFFFFFFE блоков, что соответствует 32Тб (и минус 2 байта) для 8Кб блока, являющееся ограничением на размер таблицы.

Это ограничение определено в файле исходного кода src/include/storage/block.h как #define MaxBlockNumber ((BlockNumber) 0xFFFFFFFE)

Таблица (и другие объекты) хранится в файлах размером до 2Гб, номер блока указывается относительно первого блока первого файла, нумерация блоков начинается с нуля: ctid=(0,*).

Командой \dT+ можно узнать размер физического места, которое занимают поля типов данных небольшого размера. Например date, boolean, timestamp, timestamptz, point.

3) В heap таблице присутствуют псевдостолбцы:

postgres=# select ctid, xmin, xmax, * from perf_row where id=0;

 ctid  | xmin | xmax | id |   name   | number |            time            |  text1            

-------+------+------+----+----------+--------+----------------------------+-----------

 (0,1) | 1006 |    0 |  0 | Анжелика |    962 | 2026-12-08 15:39:59.029462 |cfcd20849..

(1 строка)

4) В columnar таблице псевдостолбцы отсутствуют:

postgres=# select ctid, * from perf_columnar where id=0;           

ERROR:  UPDATE and CTID scans not supported for ColumnarScan

postgres=# select xmin, xmax, * from perf_columnar where id=0;   

ERROR:  UPDATE and CTID scans not supported for ColumnarScan

postgres=# select xmin, * from perf_columnar where id=0;     

ERROR:  UPDATE and CTID scans not supported for ColumnarScan

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

5) Посмотрим что можно использовать ограничения целостности. Ограничения целостности PRIMARY KEY и UNIQUE используют индекс для быстрой проверки соответствия вставляемой строки ограничению. По умолчанию автоматически создаётся уникальный индекс. PRIMARY KEY отличается от UNIQUE тем, что добавляет ограничение целостности NOT NULL на столбцы которые указаны в PRIMARY KEY («ключевые столбцы»). При удалении ограничения целостности индекс, используемый ограничением целостности удаляется. Создание индекса может быть ресурсоемким и долгим, администраторам баз данных нужно знать эти особенности при удалении или добавлении ограничений целостности.

postgres=# alter table perf_columnar alter column id drop not null;

ALTER TABLE

postgres=# alter table perf_columnar add unique (id) deferrable;

ERROR:  Foreign keys and AFTER ROW triggers are not supported for columnar tables

ПОДСКАЗКА:  Consider an AFTER STATEMENT trigger instead.

Ограничения целостности с отложенной проверкой (при фиксации транзакции) не поддерживаются.

postgres=# alter table perf_columnar add unique (id);

ALTER TABLE

postgres=# \d perf_columnar

                                Таблица "public.perf_columnar"

 Столбец |             Тип             | Правило сортировки | Допустимость NULL | По умолчанию

---------+-----------------------------+--------------------+-------------------+--------------

 id      | integer                     |                    |                   |

 name    | character varying(15)       |                    |                   |

 number  | integer                     |                    |                   |

 time    | timestamp without time zone |                    |                   |

 text1   | character varying(64)       |                    |                   |

Индексы:

 "perf_columnar_id_key" UNIQUE CONSTRAINT, btree (id)

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

postgres=# \d perf_columnar_id_key 

    Индекс "public.perf_columnar_id_key"

 Столбец |   Тип   | Ключевой? | Определение

---------+---------+-----------+-------------

 id      | integer | да        | id

уникальный, btree, для таблицы "public.perf_columnar"

postgres=# alter table perf_columnar drop constraint perf_columnar_id_key;

ALTER TABLE

postgres=# alter table perf_columnar add primary key (id);

ALTER TABLE

postgres=# \d perf_columnar

                                Таблица "public.perf_columnar"

 Столбец |             Тип             | Правило сортировки | Допустимость NULL | По умолчанию

---------+-----------------------------+--------------------+-------------------+--------------

 id      | integer                     |                    | not null          |

 name    | character varying(15)       |                    |                   |

 number  | integer                     |                    |                   |

 time    | timestamp without time zone |                    |                   |

 text1   | character varying(64)       |                    |                   |

Индексы:

    "perf_columnar_pkey" PRIMARY KEY, btree (id)

6) Проверим используется ли индекс:

postgres=# explain select id from perf_columnar where id = 10000;

                                   QUERY PLAN                                  

------------------------------------------------------------------------

 Custom Scan (ColumnarScan) on perf_columnar  (cost=0.00..84.88 rows=1 width=4)

   Filter: (id = 10000)

   Columnar Projected Columns: id

   Columnar Chunk Group Filters: (id = 10000)

(4 строки)

Индекс не используется и более того он неэффективен. Для использования индекса можно установить значение параметра:

SET columnar.enable_custom_scan TO OFF;

Эффективность использования индекса будет низка: время выполнения увеличится в 2 или более раз по сравнению с Custom Scan (ColumnarScan). Параметр columnar.enable_custom_scan скрыт.

7) Удалите первичный ключ:

postgres=# alter table perf_columnar drop constraint perf_columnar_pkey;

ALTER TABLE

postgres=# \d perf_columnar

                                Таблица "public.perf_columnar"

 Столбец |             Тип             | Правило сортировки | Допустимость NULL | По умолчанию

---------+-----------------------------+--------------------+-------------------+--------------

 id      | integer                     |                    | not null          |

 name    | character varying(15)       |                    |                   |

 number  | integer                     |                    |                   |

 time    | timestamp without time zone |                    |                   |

 text1   | character varying(64)       |                    |                   |

При удалении ограничения целостности удаляется индекс, используемый ограничением целостности. Ограничение целостности NOT NULL не удаляется, так как в системном каталоге не сохраняется существовало ли оно до создания ограничения целостности или было добавлено при создании ограничения целостности типа PRIMARY KEY.

8) В таблицу можно вставлять строки. Кроме команды INSERT можно использовать команду COPY. Выполните команду:

postgres=# COPY perf_columnar (id) FROM PROGRAM 'echo 500001';

COPY 1

Команда успешно вставила одну строку.

9) Посмотрите конфигурационные параметры расширения:

postgres=# \dconfig columnar.*

      Список параметров конфигурации

            Параметр            | Значение

--------------------------------+----------

 columnar.chunk_group_row_limit | 10000

 columnar.compression           | zstd

 columnar.compression_level     | 3

 columnar.planner_debug_level   | debug3

 columnar.stripe_row_limit      | 150000

(5 строк)

Если список пуст, то это означает что в текущей сессией не использовался функционал расширения. В этом можно дать команду, которая задействует функционал расширения. Например:

select id,name from perf_columnar where id = 1;

10) Посмотрите какие значения можно установить для алгорима сжатия:

postgres=# set columnar.compression TO <TAB><TAB>

DEFAULT  lz4      "none"   pglz     zstd

Поддерживается три алгоритма сжатия.

11) Часть параметров можно установить на уровне таблиц. Расширение создаёт представление где эти параметры хранения удобно смотреть:

postgres=# select * from columnar.options;

   relation    | chunk_group_row_limit | stripe_row_limit | compression | compression_level

---------------+-----------------------+------------------+-------------+-------------------

 demo2         |                 10000 |           150000 | zstd        |                 3

 perf_columnar |                 10000 |           150000 | pglz        |                 3

(2 строки)

12) Посмотрим команду установки параметра в значение по умолчанию. Выполните команду:

postgres=# ALTER TABLE perf_columnar RESET (columnar.compression);

ALTER TABLE


Раздел 5. Журналирование

Журналирование

  1. Какая информация попадает в журнал
  2. Расположение журналов сервера
  3. Как информация попадает в журнал
  4. Добавление формата csv
  5. Включение коллектора сообщений

Часть 1. Какая информация попадает в журнал

Загрузим psql:

astra@tantor:~$ psql

psql (16.1)

Введите "help", чтобы получить справку.

postgres=#


postgres=#
SHOW log_line_prefix;

   log_line_prefix      

------------------------

%m [%p:%v] [%d] %r %a  

(1 row)

%m: Уровень сообщения (DEBUG5, DEBUG4, INFO, WARNING, ERROR, и так далее).

[%p:%v]: Идентификатор процесса PostgreSQL и номер версии протокола.

[%d]: Имя базы данных.

%r: Идентификатор транзакции.

%a: IP-адрес и порт клиента.

Часть 2. Расположение журналов сервера

1) Посмотрим путь до журналов

postgres=# SHOW log_directory;

 log_directory

---------------

 log

(1 row)

Какая маска у файлов журнала?

postgres=# SHOW log_filename;

          log_filename          

--------------------------------

 postgresql-%Y-%m-%d_%H%M%S.log

(1 row)

Где находятся данных кластера БД?

postgres=# SHOW data_directory;

            data_directory            

---------------------------------------

 /var/lib/postgresql/tantor-se-14/data

(1 row)

2) Посмотрим содержимое папки журнала.

postgres=#\! ls -l /var/lib/postgresql/tantor-se-14/data/log

total 148228

-rw------- 1 postgres postgres     1115 Jul  3  2023 postgresql-2023-07-03_130021.log

-rw------- 1 postgres postgres     1112 Jul  3  2023 postgresql-2023-07-03_130033.log

-rw------- 1 postgres postgres     6545 Jul  3  2023 postgresql-2023-07-03_162937.log

-------------------------------

3) Посмотрим содержимое любого журнала.

postgres=# \! tail -n 10  /var/lib/postgresql/tantor-se-14/data/log/postgresql-2023-07-03_130021.log

2023-07-03 13:00:21.009 MSK [23506] LOG:  listening on IPv6 address "::1", port 5432

2023-07-03 13:00:21.012 MSK [23506] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"

2023-07-03 13:00:21.017 MSK [23506] LOG:  listening on Unix socket "/tmp/.s.PGSQL.5432"

2023-07-03 13:00:21.021 MSK [23508] LOG:  database system was shut down at 2023-07-03 13:00:19 MSK

2023-07-03 13:00:21.027 MSK [23506] LOG:  database system is ready to accept connections

2023-07-03 13:00:33.293 MSK [23506] LOG:  received fast shutdown request

2023-07-03 13:00:33.297 MSK [23506] LOG:  aborting any active transactions

2023-07-03 13:00:33.299 MSK [23506] LOG:  background worker "logical replication launcher" (PID 23514) exited with exit code 1

2023-07-03 13:00:33.300 MSK [23509] LOG:  shutting down

2023-07-03 13:00:33.328 MSK [23506] LOG:  database system is shut down

Убедились, что маска соответствует.

Часть 3. Как информация попадает в журнал

postgres=# CREATE TABLE t (id integer);

CREATE TABLE

postgres=# \! tail -n 10  /var/lib/postgresql/tantor-se-14/data/log/postgresql-2024-02-29_100938.log

2024-02-29 07:49:54.753 MSK [5289:8/30] [postgres] [local] psql LOG:  statement: create table t (id integer);

Часть 4. Добавление формата csv

1) Посмотрим параметр.

        

postgres=# SHOW log_destination;

log_destination

-----------------

 stderr

(1 row)

2) Изменим параметр и перечитаем конфигурацию.

postgres=# ALTER SYSTEM SET log_destination = 'stderr,csvlog';

ALTER SYSTEM

postgres=# SELECT pg_reload_conf();

 pg_reload_conf

----------------

 t

(1 row)

3) Посмотрим что параметр успешно применяется.

postgres=# SHOW log_destination;

 log_destination

-----------------

 stderr,csvlog

(1 row

4) Вставим новое значение в таблицу t.

postgres=# INSERT INTO t VALUES(1);

INSERT 0 1

5) Посмотрим содержимое файла.

postgres=# \! ls -l /var/lib/postgresql/tantor-se-14/data/log/*csv

-rw------- 1 postgres postgres 30749 Feb 29 08:02 /var/lib/postgresql/tantor-se-14/data/log/postgresql-2024-02-29_080122.csv

6) Добавился формат данных csv.

        

postgres=#\! tail -n 1 /var/lib/postgresql/tantor-se-14/data/log/postgresql-2024-02-29_080122.csv

2024-02-29 08:08:54.580 MSK,"postgres","postgres",9199,"[local]",65e01024.23ef,3,"idle",2024-02-29 08:03:32 MSK,5/325,0,LOG,00000,"statement: insert into t values(1);",,,,,,,,,"psql","client backend",,0

7) Сравним с содержимым обычного журнала.

postgres=# INSERT INTO t VALUES(1);

INSERT 0 1

postgres=# \! tail -n 1 /var/lib/postgresql/tantor-se-14/data/log/postgresql-2024-02-29_080122.log

2024-02-29 08:12:02.631 MSK [9199:5/326] [postgres] [local] psql LOG:  statement: insert into t values(1);

postgres=#

Часть 5.  Включение коллектора сообщений

postgres=# show logging_collector;

 logging_collector

-------------------

 on

(1 row)

Удалим ненужные объекты:

postgres=# DROP TABLE t;

DROP TABLE

postgres=# ALTER SYSTEM SET log_destination = 'stderr';        

ALTER SYSTEM

postgres=# SELECT pg_reload_conf();

 pg_reload_conf

----------------

 t

(1 row)


Раздел 6. Безопасность

Ролевая модель безопасности

  1. Создание новой роли
  2. Установка атрибутов
  3. Создание групповой роли
  4. Создание схемы и таблицы
  5. Выдача роли доступа к таблице
  6. Удаление созданных объектов

Часть 1. Создание новой роли

1) Загрузим инструмент psql

astra@alse-vanilla-gui:~$ psql

psql (16.1)

Введите "help", чтобы получить справку.

postgres=#

2) Создадим новую роль:

postgres=# CREATE ROLE user1;

CREATE ROLE

3) Посмотрим, какие есть роли в СУБД:

postgres=# \du

                                      Список ролей

    Имя роли    |                                Атрибуты                                

----------------+-------------------------------------------------------------------------

 anon_test_user | Суперпользователь

 pma_user       | Суперпользователь, Создаёт роли

 postgres       | Суперпользователь, Создаёт роли, Создаёт БД, Репликация, Пропускать RLS

 replicator     | Репликация

 user1          | Вход запрещён


Часть 2. Установка атрибутов

postgres=# ALTER ROLE user1 LOGIN CREATEDB;      

ALTER ROLE

postgres=# \du

                                      Список ролей

    Имя роли    |                                Атрибуты                                

----------------+-------------------------------------------------------------------------

 anon_test_user | Суперпользователь

 pma_user       | Суперпользователь, Создаёт роли

 postgres       | Суперпользователь, Создаёт роли, Создаёт БД, Репликация, Пропускать RLS

 replicator     | Репликация

 user1          | Создаёт БД

Часть 3. Создание групповой роли

Предположим что нам нужна роль, под которой можно только подключаться к кластеру, а под второй создавать БД но нельзя делать соединения к БД.

1) Создадим вторую роль

postgres=# CREATE ROLE user2;

CREATE ROLE

postgres=# ALTER ROLE user2 LOGIN;           

ALTER ROLE

2) Отзовем лишний атрибут:

postgres=# ALTER ROLE user1 NOLOGIN;

ALTER ROLE

postgres=# \du

                                      Список ролей

    Имя роли    |                                Атрибуты                                

----------------+-------------------------------------------------------------------------

 anon_test_user | Суперпользователь

 pma_user       | Суперпользователь, Создаёт роли

 postgres       | Суперпользователь, Создаёт роли, Создаёт БД, Репликация, Пропускать RLS

 replicator     | Репликация

 user1          | Создаёт БД, Вход запрещён

 user2          |

3) Дадим право вхождения в группу user1 роли user2.

postgres=# GRANT user1 TO user2;

GRANT ROLE  

4) Проверим условия задания:

5) Первая роль не может входить в кластер БД:

postgres=# \c - user1

подключиться к серверу через сокет "/var/run/postgresql/.s.PGSQL.5432" не удалось: ВАЖНО:  для роли "user1" вход запрещён

Сохранено предыдущее подключение

6) Входим под второй ролью:

postgres=# \c - user2

Вы подключены к базе данных "postgres" как пользователь "user2"

7) Пытаемся создать базу данных под второй ролью

postgres=> CREATE DATABASE  dat1;

ОШИБКА:  нет прав на создание базы данных

8) Переключаем роль на первую

postgres=> SET ROLE user1;

SET

9) Теперь создать базу данных можно

postgres=> CREATE DATABASE  dat1;

CREATE DATABASE

10) Вернемся к роли user2

postgres=> RESET ROLE;

RESET

11) Подключаемся к БД dat1

dat1=> \c dat1

Вы подключены к базе данных "dat1" как пользователь "user2".

Часть 4. Создание схемы и таблицы

dat1=> CREATE SCHEMA sch1;

CREATE SCHEMA

Посмотрим кто владелец схемы

 

dat1=> \dn+

                                      List of schemas

 Name  |       Owner       |           Access privileges            |      Description        

-------+-------------------+----------------------------------------+------------------------

public | pg_database_owner | pg_database_owner=UC/pg_database_owner+| standard public schema

       |                   | =U/pg_database_owner                   |  

sch1   | user2             |                                        |  

(2 строки)

dat1=> CREATE TABLE sch1.a1 (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, str text);  

CREATE TABLE

Посмотрим описание таблицы

dat1=>\d sch1.a1

                           Таблица "sch1.a1"

 Столбец |   Тип   | Правило сортировки | Допустимость NULL |         По умолчанию        

---------+---------+--------------------+-------------------+------------------------------

 id      | integer |                    | not null          | generated always as identity

 str     | text    |                    |                   |

Индексы:

    "a1_pkey" PRIMARY KEY, btree (id)

Посмотрим разрешения на таблицу

dat1=>\dp sch1.a1 

                                                    Права доступа

 Схема | Имя |   Тип   | Права доступа | Права для столбцов | Политики

-------+-----+---------+---------------+--------------------+----------

 sch1  | a1  | таблица |               |                    |

(1 строка)

Пока нет ни у какой роли, кроме суперпользовательской.

Часть 5. Выдача роли доступа к таблице

1) Создадим еще одну роль:

dat1=> \c - postgres

Вы подключены к базе данных "dat1" как пользователь "postgres"

dat1=# CREATE ROLE user3 LOGIN;

CREATE ROLE

2) Попробуем получить доступ к таблице a1.

dat1=# \c - user3

Вы подключены к базе данных "dat1" как пользователь "user3".

dat1=> \dn

    Список схем

  Имя   |     Владелец      

--------+-------------------

 public | pg_database_owner

 sch1   | user2

(2 rows)

dat1=> SELECT * FROM sch1.a1;

ОШИБКА:  нет доступа к схеме sch1

СТРОКА 1: SELECT * FROM sch1.a1;

3) В доступе отказано нет привилегий на схему.

dat1=> \c - postgres

Вы подключены к базе данных "dat1" как пользователь "postgres"

dat1=> GRANT USAGE on SCHEMA sch1 TO user3;

GRANT

dat1=> \dn+ sch1

               Список схем

 Имя  | Владелец | Права доступа  | Описание

------+----------+----------------+----------

 sch1 | user2    | user2=UC/user2+|

      |          | user3=U/user2  |

(1 строка)

dat1=> \c - user3

You are now connected to database "dat1" as user "user3".

dat1=> SELECT * FROM sch1.a1;

ОШИБКА:  нет доступа к таблице a1

Теперь отказ из-за отсутствия привилегий на таблице a1.

dat1=> \c - postgres

Вы подключены к базе данных "dat1" как пользователь "postgres"

dat1=> GRANT SELECT, INSERT (str) ON TABLE sch1.a1 to user3;

GRANT

dat1=> \dp sch1.a1

                               Права доступа

 Схема | Имя |   Тип   |    Права доступа    | Права для столбцов | Политики

-------+-----+---------+---------------------+--------------------+----------

 sch1  | a1  | таблица | user2=arwdDxt/user2+| str:              +|

       |     |         | user3=r/user2       |   user3=a/user2    |

(1 строка)

dat1=> \c - user3

Вы подключены к базе данных "dat1" как пользователь "user3"

dat1=> SELECT * FROM sch1.a1;

id | str

----+-----

(0 строк)

Теперь все в порядке. Доступ предоставлен в рамках выданных привилегий.
Проверим вставку в столбец.

dat1=> INSERT INTO sch1.a1 (str) VALUES ('первая запись');  

INSERT 0 1

dat1=> SELECT * FROM sch1.a1;

id |      str      

----+---------------

 1 | первая запись

(1 row)

Проверим вставку в первый столбец.

dat1=> INSERT INTO sch1.a1 OVERRIDING SYSTEM VALUE values (2);                 

ОШИБКА:  нет доступа к таблице a1

Не хватает привилегий

Удаление также строк и объекта невозможно - нужно быть владельцем или суперпользователем.

dat1=> DELETE FROM sch1.a1;                                     

ОШИБКА:  нет доступа к таблице a1

dat1=> DROP TABLE sch1.a1;  

ОШИБКА:  нужно быть владельцем таблицы a1

Часть 6. Удаление созданных объектов

Удалим схему.

dat1=> \c - user2

Вы подключены к базе данных "dat1" как пользователь "user2".

dat1=> DROP SCHEMA sch1;

ОШИБКА:  удалить объект схема sch1 нельзя, так как от него зависят другие объекты

ПОДРОБНОСТИ:  таблица sch1.a1 зависит от объекта схема sch1

ПОДСКАЗКА:  Для удаления зависимых объектов используйте DROP ... CASCADE.

Схема не пуста, можно удалить каскадом.

dat1=> DROP SCHEMA sch1 CASCADE;

ЗАМЕЧАНИЕ:  удаление распространяется на объект таблица sch1.a1

DROP SCHEMA

Переключимся на другую базу данных и удалим dat1.

dat1=> \c postgres  

Вы подключены к базе данных "postgres" как пользователь "user2".

postgres=> DROP DATABASE dat1 (force);

DROP DATABASE

Для удаления ролей воспользуемся суперпользовательской ролью.

        

postgres=> \c - postgres  

Вы подключены к базе данных "postgres" как пользователь "postgres".

postgres=# DROP ROLE user1, user2, user3;

DROP ROLE


Подключение и аутентификация

  1. Расположение файлов конфигурации
  2. Просмотр правил аутентификации
  3. Локальные изменения для аутентификации
  4. Проверка корректности настройки
  5. Очистка ненужных объектов

Часть 1. Расположение файлов конфигурации

1) Загрузим инструмент psql.

astra@alse-vanilla-gui:~$ sudo su - postgres

postgres@alse-vanilla-gui:~$ psql

psql (16.1)

Введите "help", чтобы получить справку.

2) Посмотрим место расположение конфигурационного файла.


postgres=# SHOW hba_file;

                    hba_file                      

---------------------------------------------------

/var/lib/postgresql/tantor-se-16/data/pg_hba.conf

(1 строка)

3) Можно посмотреть правила подключения с помощью представления pg_hba_file_rules


postgres=# \d pg_hba_file_rules;

                Представление "pg_catalog.pg_hba_file_rules"

   Столбец   |   Тип   | Правило сортировки | Допустимость NULL | По умолчанию

-------------+---------+--------------------+-------------------+--------------

 rule_number | integer |                    |                   |

 file_name   | text    |                    |                   |

 line_number | integer |                    |                   |

 type        | text    |                    |                   |

 database    | text[]  |                    |                   |

 user_name   | text[]  |                    |                   |

 address     | text    |                    |                   |

 netmask     | text    |                    |                   |

 auth_method | text    |                    |                   |

 options     | text[]  |                    |                   |

 error       | text    |                    |                   |


Часть 2. Просмотр правил аутентификации

postgres=# SELECT rule_number, type, database, user_name, auth_method FROM  pg_hba_file_rules();   

rule_number | type  |   database    | user_name  | auth_method

-------------+-------+---------------+------------+-------------

           1 | local | {all}         | {pma_user} | md5

           2 | local | {all}         | {all}      | trust

           3 | host  | {all}         | {all}      | trust

           4 | host  | {all}         | {all}      | trust

           5 | local | {replication} | {all}      | trust

           6 | host  | {replication} | {all}      | trust

           7 | host  | {replication} | {all}      | trust

           8 | host  | {all}         | {all}      | md5

(8 строк)

(7 rows)

Часть 3. Локальные изменения для аутентификации

1) Любым редактором внесем две строки.

Файл pg_hba.conf

# TYPE  DATABASE        USER            ADDRESS                 METHOD

local   all             all                                   ident    map=map1

Файл pg_ident.conf

# MAPNAME       SYSTEM-USERNAME         PG-USERNAME

map1   astra  user1

postgres@alse-vanilla-gui:~$ psql

psql (16.1)

Введите "help", чтобы получить справку

postgres=# SELECT pg_reload_conf();

pg_reload_conf  

----------------

t

(1 row)

2) Создадим двух пользователей user1 и 2.

postgres=# CREATE ROLE user1 LOGIN;

CREATE ROLE

postgres=# CREATE ROLE user2 LOGIN;

CREATE ROLE

postgres=# \du

                                      Список ролей

    Имя роли    |                                Атрибуты                                

----------------+-------------------------------------------------------------------------

 anon_test_user | Суперпользователь

 pma_user       | Суперпользователь, Создаёт роли

 postgres       | Суперпользователь, Создаёт роли, Создаёт БД, Репликация, Пропускать RLS

 replicator     | Репликация

3) Посмотрим есть ли ошибки в конфигурации

postgres=# \d pg_ident_file_mappings;

              Представление "pg_catalog.pg_ident_file_mappings"

   Столбец   |   Тип   | Правило сортировки | Допустимость NULL | По умолчанию

-------------+---------+--------------------+-------------------+--------------

 map_number  | integer |                    |                   |

 file_name   | text    |                    |                   |

 line_number | integer |                    |                   |

 map_name    | text    |                    |                   |

 sys_name    | text    |                    |                   |

 pg_username | text    |                    |                   |

 error       | text    |                    |                   |

postgres=# SELECT * FROM pg_ident_file_mappings;

map_number |                      file_name                      | line_number | map_name

| sys_name | pg_username | error

------------+-----------------------------------------------------+-------------+----------

+----------+-------------+-------

          1 | /var/lib/postgresql/tantor-se-16/data/pg_ident.conf |          74 | map1    

| astra    | user1       |

(1 строка)

postgres=# SELECT rule_number, type, database, user_name, auth_method, address, options, error  FROM  pg_hba_file_rules();

rule_number  | type  |   database    | user_name  | auth_method |  address  | options  | error

-------------+-------+---------------+------------+-------------+-----------+----------+---

           1 | local | {all}         | {all}      | peer        |           | {map=m1} |

           2 | local | {all}         | {pma_user} | md5         |           |          |

           3 | local | {all}         | {all}      | trust       |           |          |

           4 | host  | {all}         | {all}      | trust       | 127.0.0.1 |          |

           5 | host  | {all}         | {all}      | trust       | ::1       |          |

           6 | local | {replication} | {all}      | trust       |           |          |

           7 | host  | {replication} | {all}      | trust       | 127.0.0.1 |          |

           8 | host  | {replication} | {all}      | trust       | ::1       |          |

           9 | host  | {all}         | {all}      | md5         | 0.0.0.0   |          |

(9 строк)

Часть 4. Проверка корректности настройки

astra@alse-vanilla-gui:~$ psql -U user2 -d postgres 

psql: ошибка: подключиться к серверу через сокет "/var/run/postgresql/.s.PGSQL.5432" не удалось: ВАЖНО:  пользователь "user2" не прошёл проверку подлинности (Peer)

astra@alse-vanilla-gui:~$ psql -U user1 -d postgres  

psql (16.1)

Введите "help", чтобы получить справку.

postgres=> \q

Часть 5. Очистка ненужных объектов

postgres=# DROP ROLE user1, user2;

DROP ROLE

Удалить строки.
        Файл
pg_hba.conf

        

# TYPE  DATABASE        USER            ADDRESS                 METHOD

local   all             all                                   ident    map=map1

Файл pg_ident.conf

# MAPNAME       SYSTEM-USERNAME         PG-USERNAME

map1   astra  user1

postgres=# SELECT pg_reload_conf();

pg_reload_conf  

----------------

t

(1 row)

postgres=# SELECT rule_number, type, database, user_name, auth_method, address, options, error  FROM  pg_hba_file_rules();

rule_number | type  |   database    | user_name  | auth_method |  address  | options | error  

------------+-------+---------------+------------+-------------+-----------+---------+-------

          1 | local | {all}         | {pma_user} | md5         |           |         |  

          2 | local | {all}         | {all}      | trust       |           |         |  

          3 | host  | {all}         | {all}      | trust       | 127.0.0.1 |         |  

          4 | host  | {all}         | {all}      | trust       | ::1       |         |  

          5 | local | {replication} | {all}      | trust       |           |         |  

          6 | host  | {replication} | {all}      | trust       | 127.0.0.1 |         |  

          7 | host  | {replication} | {all}      | trust       | ::1       |         |  

(7 строк)

postgres=# select * from pg_ident_file_mappings;

map_number | file_name | line_number | map_name | sys_name | pg_username | error  

-----------+-----------+-------------+----------+----------+-------------+-------

(0 строк)

Раздел 7. Резервное копирование

Физическое копирование

  1. Создание базовой резервной копии кластера
  2. Запуск экземпляра на копии кластера
  3. Файлы журнала
  4. Проверка целостности резервной копии
  5. Согласованная резервная копия
  6. Удаление файлов журнала
  7. Создание архива журнала утилитой pg_receivewal
  8. Синхронная фиксация транзакций и pg_receivewal
  9. Минимизация потерь данных транзакций

Часть 1. Создание базовой резервной копии кластера

  1. Утилита pg_basebackup не резервирует, если директория куда делается бэкап существует и при этом не пустая. Удалите директорию:

postgres@tantor:~$ rm -rf $HOME/backup

  1. Мы будем резервировать на тот же хост, где располагается резервируемый кластер. Если есть табличные пространства, то нужно будет указывать соответствие (mapping) их директориям. Проверьте есть ли табличные пространства в кластере:

postgres@tantor:~$ ls -l $PGDATA/pg_tblspc

total 0

lrwxrwxrwx 1 postgres postgres 44 Mar 10 13:41 32913 -> /var/lib/postgresql/tantor-se-16/data/../u01

В директории есть символическая ссылка, значит есть табличное пространство.

Табличное пространство создавалось в п.10 части 1 практики к главе 4b командой:

CREATE TABLESPACE u01tbs LOCATION '/var/lib/postgresql/tantor-se-16/u01';

Если табличного пространства нет, то создайте его этой командой.

3) Создайте таблицу в табличном пространстве u01tbs:

postgres=# CREATE TABLE t (id bigserial, t text) TABLESPACE u01tbs;

CREATE TABLE

4) Создайте бэкап:

postgres@tantor:~$

pg_basebackup -D $HOME/backup/1 -T $PGDATA/../u01=$HOME/backup/1/u01 -P

4472057/4472057 kB (100%), табличное пространство 2/2

5) Посмотрите содержимое резервной копии:

postgres@tantor:~$ ls --color -w 60 $HOME/backup/1

backup_label     pg_multixact  pg_twophase

backup_manifest  pg_notify     PG_VERSION

base             pg_replslot   pg_wal

global           pg_serial     pg_xact

pg_commit_ts     pg_snapshots  postgresql.auto.conf

pg_dynshmem      pg_stat       postgresql.conf

pg_hba.conf      pg_stat_tmp   u01

pg_ident.conf    pg_subtrans

pg_logical       pg_tblspc

6) Посмотрите на какую директорию указывает символическая ссылка табличного пространства:

postgres@tantor:~$ ls -l  $HOME/backup/1/pg_tblspc

total 0

lrwxrwxrwx 1 postgres postgres 32 32913 -> /var/lib/postgresql/backup/1/u01

Всё корректно: если запустить ещё один экземпляр, используя в качестве PGDATA директорию бэкапа, то директория табличного пространства будет найдена и использована по этому (/var/lib/postgresql/backup/1/u01) пути, а не по пути из кластера (/var/lib/postgresql/tantor-se-16/data/../u01) , который резервировали.

Часть 2. Запуск экземпляра на копии кластера

1) В файле $HOME/backup/1/postgresql.conf параметр port закомментирован, это значит что будет использоваться значение по умолчанию 5432. Нужно установить другое значение порта, так как порт 5432 занят экземпляром кластера, который мы резервировали.

Можно использовать любое значение выше 1023 (на портах ниже 1024 процессы непривилегированных пользователей операционной системы не могут прослушивать). Порт не должен быть занят и (желательно не занят ни на одном интерфейсе).

Устанавливать порт (как и другие параметры) можно в параметре командной строки, передаваемой процессу postgres (в том числе через утилиты-обёртки, например, pg_ctl); в postgresql.auto.conf; в postgresql.conf. Выбирают наиболее удобный способ.

Установите в основном файле параметров значение порта 5433:

postgres@tantor:~$ echo "port = 5433" >> $HOME/backup/1/postgresql.conf

2) Дайте команду на остановку экземпляра, который может быть запущен и использовать порт 5433:

postgres@tantor:~$ pg_ctl stop -D /var/lib/postgresql/tantor-se-16-replica/data

ожидание завершения работы сервера.... готово

сервер остановлен

3) Запустите экземпляр:

postgres@tantor:~$ pg_ctl start -D $HOME/backup/1

ожидание запуска сервера....

СООБЩЕНИЕ:  передача вывода в протокол процессу сбора протоколов

ПОДСКАЗКА:  В дальнейшем протоколы будут выводиться в каталог "log".

 готово

сервер запущен

В лог кластера будут выводиться сообщения:

LOG:  starting PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

LOG:  listening on IPv4 address "0.0.0.0", port 5433

LOG:  listening on IPv6 address "::", port 5433

LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5433"

LOG:  database system was interrupted; last known up at 23:36:29 MSK

LOG:  redo starts at 115/A9000028

LOG:  consistent recovery state reached at 115/A9000178

LOG:  redo done at 115/A9000178 system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s

LOG:  checkpoint starting: end-of-recovery immediate wait

LOG:  checkpoint complete: wrote 4 buffers (0.0%); 0 WAL file(s) added, 0 removed, 1 recycled; write=0.003 s, sync=0.001 s, total=0.008 s; sync files=3, longest=0.001 s, average=0.001 s; distance=16384 kB, estimate=16384 kB; lsn=115/AA000028, redo lsn=115/AA000028

4) Если вы хотите, чтобы диагностические сообщения выводились на экран, то нужно закомментировать в конце файла postgresql.conf строку с параметром.

logging_collector = 'on'

или добавить строку в файл конфигурации, например, командой:

echo "logging_collector = off" >> $HOME/backup/1/postgresql.auto.conf

Рестартовать экземпляр и проверить, что сообщения выводятся:

postgres@tantor:~$ pg_ctl stop -D $HOME/backup/1

ожидание завершения работы сервера....

готово

сервер остановлен

postgres@tantor:~$ pg_ctl start -D $HOME/backup/1

ожидание запуска сервера....

[20912] СООБЩЕНИЕ:  запускается PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

[20912] СООБЩЕНИЕ:  для приёма подключений по адресу IPv4 "0.0.0.0" открыт порт 5433

[20912] СООБЩЕНИЕ:  для приёма подключений по адресу IPv6 "::" открыт порт 5433

[20912] СООБЩЕНИЕ:  для приёма подключений открыт Unix-сокет "/var/run/postgresql/.s.PGSQL.5433"

[20915] СООБЩЕНИЕ:  система БД была выключена: MSK

[20912] СООБЩЕНИЕ:  система БД готова принимать подключения

 готово

сервер запущен

5) Подсоединитесь к экземпляру:

postgres@tantor:~$ psql -p 5433

Так как экземпляр открылся в режиме обычном режиме («чтение-запись», режим позволяющей вносить изменения), мы получили клон исходного кластера.

Часть 3. Файлы журнала

1) Посмотрите название текущего файла журнала:

postgres=# select pg_walfile_name_offset(pg_current_wal_lsn()), pg_current_wal_lsn();

     pg_walfile_name_offset     | pg_current_wal_lsn

--------------------------------+--------------------

 (0000000100000115000000AA,264) | 115/AA000108

(1 строка)

Линия времени не изменилась и равна 1. Почему?

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

Почему аварийной?

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

2) Переключите журнал и посмотрите что изменится, чтобы увидеть как меняются числа:

postgres=# select pg_switch_wal();

 pg_switch_wal

---------------

 115/AA000122

(1 строка)

3) Что выдала функция? LSN в том файле с которого переключились плюс один байт. То есть LSN начала неиспользуемой части файла журнала.

Посмотрите какой файл стал текущим:

postgres=# select pg_walfile_name_offset(pg_current_wal_lsn()), pg_current_wal_lsn();

     pg_walfile_name_offset     | pg_current_wal_lsn

--------------------------------+--------------------

 (0000000100000115000000AB,112) | 115/AB000070

(1 строка)

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

4) Выполним функцию переключения файла журнала несколько раз:

postgres=# select pg_switch_wal();

 pg_switch_wal

---------------

 115/AB00008A

(1 строка)

postgres=# select pg_switch_wal();

 pg_switch_wal

---------------

 115/AC000000

(1 строка)

postgres=# select pg_switch_wal();

 pg_switch_wal

---------------

 115/AC000000

(1 строка)

5) Почему последние вызовы не переключали журнал? Это описано в документации (https://docs.tantorlabs.ru/tdb/ru/15_6/se/functions-admin.html):

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

6) Подставляя произвольные значения, убедитесь, что функция pg_walfile_name_offset рассчитывает значения в зависимости от линии времени и размера файла журнала:

postgres=# select pg_walfile_name_offset('ABCD/EF00FFFF');

      pg_walfile_name_offset      

----------------------------------

 (000000010000ABCD000000EF,65535)

(1 строка)

Мы посмотрели как можно по виду LSN (синий цвет значения EF) предположить в каком файле журнала находится этот LSN без вызова функций.

7) Остановите экземпляр клона:

postgres@tantor:~$ pg_ctl stop -D $HOME/backup/1

Пример сообщений на английском языке:

LOG:  received fast shutdown request

LOG:  aborting any active transactions

LOG:  background worker "logical replication launcher" (PID 31666) exited with exit code 1

waiting for server to shut down....

LOG:  shutting down

LOG:  checkpoint starting: shutdown immediate

LOG:  checkpoint complete: wrote 0 buffers (0.0%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.001 s, sync=0.001 s, total=0.005 s; sync files=0, longest=0.000 s, average=0.000 s; distance=0 kB, estimate=44236 kB; lsn=115/AE000198, redo lsn=115/AE000198

LOG:  database system is shut down

 done

server stopped

Можно сравнить с сообщениями на русском языке.

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

Часть 4. Проверка целостности резервной копии

1) pg_basebackup создала файл backup_manifest, с помощью которого можно проверить не поменялись ли файлы в бэкапе за время их хранения. Проверим копию на которой уже запускали экземпляр:

postgres@tantor:~$ pg_verifybackup $HOME/backup/1

pg_verifybackup: error: "pg_stat/pg_stat_statements.stat" is present on disk but not in the manifest

pg_verifybackup: error: "pg_stat/pgstat.stat" is present on disk but not in the manifest

pg_verifybackup: error: "postmaster.opts" is present on disk but not in the manifest

pg_verifybackup: error: "base/5/pg_internal.init" is present on disk but not in the manifest

pg_verifybackup: error: "global/pg_internal.init" is present on disk but not in the manifest

pg_verifybackup: error: "global/pg_store_plans.stat" is present on disk but not in the manifest

pg_verifybackup: error: "postgresql.conf" has size 30440 on disk but size 30428 in the manifest

pg_verifybackup: error: "backup_label.old" is present on disk but not in the manifest

pg_verifybackup: error: "pg_subtrans/000000000001" is present on disk but not in the manifest

pg_verifybackup: error: "u01/PG_16_202307071/5/382539" is present on disk but not in the manifest

pg_verifybackup: error: "u01/PG_16_202307071/5/382539.1" is present on disk but not in the manifest

pg_verifybackup: error: "u01/PG_16_202307071/5/382541" is present on disk but not in the manifest

pg_verifybackup: error: "u01/PG_16_202307071/5/382540" is present on disk but not in the manifest

pg_verifybackup: error: "u01/PG_16_202307071/5/382539_vm" is present on disk but not in the manifest

pg_verifybackup: error: "u01/PG_16_202307071/5/382539_fsm" is present on disk but not in the manifest

pg_verifybackup: error: "backup_label" is present in the manifest but not on disk

pg_waldump: error: could not find file "0000000100000115000000A9": No such file or directory

pg_verifybackup: error: WAL parsing failed for timeline 1

Сообщения о файлах .stat internal.init pg_subtrans/* обычны, файлы не попадают в бэкап. postgresql.conf мы обновили добавив номер порта. Файл журнала A9 исчез потому, что не понадобился клону для его восстановления и не удерживался параметром min_wal_size. Файл backup_label был переименован в backup_label.old

Файл backup_label важен, если он присутствует, что используются значения из него для того чтобы определить с какого LSN начинать накат журналов, а не данные из pg_control. Содержимое pg_control было изменено экземпляром, он присутствует в файле манифеста, если его удалить то будет выдано сообщение, но pg_control не был выдан в списке изменённых.

2) Почему присутствуют записи обо всех файлах табличного пространства?

Контрольные суммы этих файлов есть в backup_manifest. Файлы не менялись и были успешно проверены:

postgres@tantor:~$ cat $HOME/backup/1/backup_manifest | grep pg_tblspc

{ "Path": "pg_tblspc/32913/PG_16_202307071/5/382539", "Size": 1073741824, "Last-Modified": "11:16:27 GMT", "Checksum-Algorithm": "CRC32C", "Checksum": "b5d86155" },

{ "Path": "pg_tblspc/32913/PG_16_202307071/5/382539.1", "Size": 18530304, "Last-Modified": "11:16:27 GMT", "Checksum-Algorithm": "CRC32C", "Checksum": "77531a0d" },

{ "Path": "pg_tblspc/32913/PG_16_202307071/5/382541", "Size": 8192, "Last-Modified": "11:16:27 GMT", "Checksum-Algorithm": "CRC32C", "Checksum": "381590e3" },

{ "Path": "pg_tblspc/32913/PG_16_202307071/5/382540", "Size": 0, "Last-Modified": "11:16:27 GMT", "Checksum-Algorithm": "CRC32C", "Checksum": "00000000" },

{ "Path": "pg_tblspc/32913/PG_16_202307071/5/382539_vm", "Size": 40960, "Last-Modified": "11:16:27 GMT", "Checksum-Algorithm": "CRC32C", "Checksum": "53622029" },

{ "Path": "pg_tblspc/32913/PG_16_202307071/5/382539_fsm", "Size": 286720, "Last-Modified": "11:16:27 GMT", "Checksum-Algorithm": "CRC32C", "Checksum": "b594827d" },

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

3) Удалим директорию клона:

postgres@tantor:~$ rm -rf $HOME/backup

Часть 5. Согласованная резервная копия

1) Создадим снова бэкап и расположим директорию табличного пространства u01 вне основной директории:

pg_basebackup -D $HOME/backup/1 -T $PGDATA/../u01=$HOME/backup/u01 -P

4472018/4472018 kB (100%), 2/2 tablespaces

2) Создадим файл standby.signal. Если этот файл присутствует (содержимое файла не важно), что экземпляр видя его не открывает кластер на чтение-запись (переходит в «режим реплики»):

postgres@tantor:~$ touch $HOME/backup/1/standby.signal

Установим параметр, чтобы диагностические сообщения выводились в консоль:

postgres@tantor:~$ echo "logging_collector = off" >> $HOME/backup/1/postgresql.auto.conf

3) Запустим экземпляр, чтобы получить «согласованную» копию. Так как бэкап автономный, то он содержит файлы журналов, нужные для согласования файлов бэкапа.

Можно запускать экземпляр командой:

pg_ctl start -D $HOME/backup/1 -o "--port=5433 --recovery_target=immediate --recovery_target_action=shutdown"

Так как после запуска экземпляра он достигнув согласованности должен погаснуть (recovery_target_action=shutdown) то можно запустить напрямую основной процесс экземпляра. Если бы экземпляр сам не останавливался, то лучше использовать pg_ctl, так как надо было бы знать какой сигнал можно передать процессу postgres, чтобы его корректно остановить. Запустим экземпляр:

postgres@tantor:~$ postgres -D $HOME/backup/1 --port=5433 --recovery_target=immediate --recovery_target_action=shutdown

LOG:  starting PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

LOG:  listening on IPv4 address "0.0.0.0", port 5433

LOG:  listening on IPv6 address "::", port 5433

LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5433"

LOG:  database system was interrupted; last known up at

WARNING:  specified neither primary_conninfo nor restore_command

HINT:  The database server will regularly poll the pg_wal subdirectory to check for files placed there.

LOG:  entering standby mode

LOG:  redo starts at 115/BB000028

LOG:  consistent recovery state reached at 115/BB000178

LOG:  database system is ready to accept read-only connections

LOG:  recovery stopping after reaching consistency

LOG:  shutdown at recovery target

LOG:  shutting down

LOG:  database system is shut down

4) Проверим бэкап:

postgres@tantor:~$ pg_verifybackup $HOME/backup/1

pg_verifybackup: error: "pg_stat/pg_stat_statements.stat" is present on disk but not in the manifest

pg_verifybackup: error: "pg_stat/pgstat.stat" is present on disk but not in the manifest

pg_verifybackup: error: "postmaster.opts" is present on disk but not in the manifest

pg_verifybackup: error: "global/pg_store_plans.stat" is present on disk but not in the manifest

pg_verifybackup: error: "backup_label.old" is present on disk but not in the manifest

pg_verifybackup: error: "backup_label" is present in the manifest but not on disk

При этой проверке ошибок связанных с файлами в директории u01 нет. Файл backup_label был переименован, а это значит, что при использовании этого бэкапа восстановление начнется с LSN, указанных в файле pg_control.

5) Проверим LSN-записи в управляющем файле:

postgres@tantor:~$ pg_controldata -D $HOME/backup/1

Database cluster state:               shut down in recovery

Latest checkpoint location:           115/BB000070

Latest checkpoint's REDO location:    115/BB000028

Latest checkpoint's REDO WAL file:    0000000100000115000000BB

Latest checkpoint's TimeLineID:       1

Latest checkpoint's PrevTimeLineID:   1

Latest checkpoint's full_page_writes: on

Latest checkpoint's NextXID:          35739

Latest checkpoint's NextOID:          399126

Latest checkpoint's NextMultiXactId:  502936

Latest checkpoint's NextMultiOffset:  2034077

Latest checkpoint's oldestXID:        723

Latest checkpoint's oldestXID's DB:   1

Latest checkpoint's oldestActiveXID:  35739

Latest checkpoint's oldestMultiXid:   1

Latest checkpoint's oldestMulti's DB: 1

Latest checkpoint's oldestCommitTsXid:0

Latest checkpoint's newestCommitTsXid:0

...

Fake LSN counter for unlogged rels:   0/3E8

Minimum recovery ending location:     115/BB000178

Min recovery ending loc's timeline:   1

Backup start location:                0/0

Backup end location:                  0/0

End-of-backup record required:        no

wal_level setting:                    replica

Если удалить журнальный файл 0000000100000115000000BB то экземпляр не запустится.

С этого бэкапа нельзя (ни до согласования ни после) восстановиться на момент раньше "Minimum recovery ending location"

Часть 6. Удаление файлов журнала

1) Удалите файл standby.signal

postgres@tantor:~$ rm $HOME/backup/1/standby.signal

2) Запустите экземпляр:

postgres@tantor:~$ pg_ctl start -D $HOME/backup/1 -o "--port=5433"

LOG:  database system was not properly shut down; automatic recovery in progress

LOG:  redo starts at 115/BB000028

LOG:  redo done at 115/BB000178 system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s

LOG:  checkpoint starting: end-of-recovery immediate wait

LOG:  checkpoint complete: wrote 4 buffers (0.0%); 0 WAL file(s) added, 0 removed, 1 recycled; write=0.003 s, sync=0.001 s, total=0.008 s; sync files=3, longest=0.001 s, average=0.001 s; distance=16384 kB, estimate=16384 kB; lsn=115/BC000028, redo lsn=115/BC000028

LOG:  database system is ready to accept connections

 done

server started

3) Корректно остановите экземпляр:

postgres@tantor:~$ pg_ctl stop -D $HOME/backup/1

LOG:  received fast shutdown request

waiting for server to shut down....

LOG:  aborting any active transactions

LOG:  background worker "logical replication launcher" (PID 4137) exited with exit code 1

LOG:  shutting down

LOG:  checkpoint starting: shutdown immediate

LOG:  checkpoint complete: wrote 0 buffers (0.0%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.001 s, sync=0.001 s, total=0.007 s; sync files=0, longest=0.000 s, average=0.000 s; distance=0 kB, estimate=14745 kB; lsn=115/BC000108, redo lsn=115/BC000108

LOG:  database system is shut down

 done

server stopped

4) Посмотрим, что поменялось в управляющем файле после обычной остановки по сравнению с режимом запуска в режиме реплики:

postgres@tantor:~$ pg_controldata -D $HOME/backup/1

Database cluster state:               shut down

pg_control last modified:             03:58:27 AM MSK

Latest checkpoint location:           115/BC000108

Latest checkpoint's REDO location:    115/BC000108

Latest checkpoint's REDO WAL file:    0000000100000115000000BC

Latest checkpoint's TimeLineID:       1

Latest checkpoint's PrevTimeLineID:   1

Latest checkpoint's full_page_writes: on

Latest checkpoint's NextXID:          35739

Latest checkpoint's NextOID:          399126

Latest checkpoint's NextMultiXactId:  502936

Latest checkpoint's NextMultiOffset:  2034077

Latest checkpoint's oldestXID:        723

Latest checkpoint's oldestXID's DB:   1

Latest checkpoint's oldestActiveXID:  0

Latest checkpoint's oldestMultiXid:   1

Latest checkpoint's oldestMulti's DB: 1

Latest checkpoint's oldestCommitTsXid:0

Latest checkpoint's newestCommitTsXid:0

Time of latest checkpoint:            03:58:27 AM MSK

Fake LSN counter for unlogged rels:   0/3E8

Minimum recovery ending location:     0/0

Min recovery ending loc's timeline:   0

Backup start location:                0/0

Backup end location:                  0/0

End-of-backup record required:        no

5) Удалите все файлы (0000000100000115000000BC) журнала:

postgres@tantor:~$ rm -r $HOME/backup/1/pg_wal/*

6) Попробуйте запустить экземпляр:

postgres@tantor:~$ pg_ctl start -D $HOME/backup/1 -o "--port=5433"

waiting for server to start.......

LOG:  database system was shut down at 03:58:27 MSK

LOG:  creating missing WAL directory "pg_wal/archive_status"

LOG:  invalid checkpoint record

PANIC:  could not locate a valid checkpoint record

LOG:  startup process (PID 4151) was terminated by signal 6: Aborted

LOG:  aborting startup due to startup process failure

LOG:  database system is shut down

 stopped waiting

pg_ctl: could not start server

Examine the log output.

Без файла журнала Latest checkpoint's REDO WAL file 0000000100000115000000BC экземпляр не запустился.

Вручную удалять файлы в директории pg_wal нельзя.

Как минимум один из файлов (текущий сегомент журнала) понадобится при запуске экземпляра.

7) Удалите директорию бэкапа:

postgres@tantor:~$ rm -rf $HOME/backup

Часть 7. Создание архива журнала утилитой pg_receivewal

1) Откройте новое окно терминала для пользователя postgres.

Создайте директорию для архива журналов:

postgres@tantor:~$ mkdir $HOME/archivelog

2) Запустите pg_receivewal:

postgres@tantor:~$ pg_receivewal -D $HOME/archivelog --slot=arch --synchronous -v

pg_receivewal: error: replication slot "arch" does not exist

pg_receivewal: disconnected; waiting 5 seconds to try again

pg_receivewal: error: replication slot "arch" does not exist

pg_receivewal: disconnected; waiting 5 seconds to try again

Будут выдаваться сообщения что слота нет. Дальше будем изучать, как будут меняться сообщения, когда создадим слот.

3) Создадим бэкап с созданием и использованием слота:

pg_basebackup -D $HOME/backup/1 -T $PGDATA/../u01=$HOME/backup/u01 -P -C --slot=arch

4472018/4472018 kB (100%), 2/2 tablespaces

4) Пока бэкап делается, в окне с запущенной утилитой pg_receivewal будут выдаваться ошибки о том, что слот используется:

pg_receivewal: starting log streaming at 115/BD000000 (timeline 1)

pg_receivewal: error: could not send replication command "START_REPLICATION": ERROR:  replication slot "arch" is active for PID 5013

pg_receivewal: disconnected; waiting 5 seconds to try again

Один слот может использовать только одна репликационная сессия.

Мы запустили pg_receivewal заранее, но стоило запустить и после резервирования, пропуска журналов бы не было. Запускать утилиту заранее не обязательно. После того, как pg_basebackup отсоединился от экземпляра, в пределах 5 секунд подсоединился pg_receivewal:

pg_receivewal: starting log streaming at 115/BD000000 (timeline 1)

pg_receivewal: finished segment at 115/BE000000 (timeline 1)

pg_receivewal получил журнальный файл BD.

5) Проверим с какого файла журнала начнется восстановление:

postgres@tantor:~$ cat $HOME/backup/1/backup_label

START WAL LOCATION: 115/BD000028 (file 0000000100000115000000BD)

CHECKPOINT LOCATION: 115/BD000070

BACKUP METHOD: streamed

BACKUP FROM: primary

START TIME: 07:48:25 MSK

LABEL: pg_basebackup base backup

START TIMELINE: 1

Восстановление начнется с журнала BD.

6) Посмотрим какой файл текущий:

postgres@tantor:~$ psql

postgres=# select pg_walfile_name_offset(pg_current_wal_lsn()), pg_current_wal_lsn();

     pg_walfile_name_offset     | pg_current_wal_lsn

--------------------------------+--------------------

 (0000000100000115000000BE,112) | 115/BE000070

(1 строка)

Текущий файл BE.

7) Посмотрим что получает pg_receivewal:

postgres@tantor:~$ ls -al $HOME/archivelog

total 32776

drwxr-xr-x  2 postgres postgres     4096 07:48 .

drwxr-xr-x 10 postgres postgres     4096 08:04 ..

-rw-r-----  1 postgres postgres 16777216 07:48 0000000100000115000000BD

-rw-r-----  1 postgres postgres 16777216 07:48 0000000100000115000000BE.partial

Он в настоящее время получает журнальные записи и пишет в файл BE. У файла расширение .partial Запись идёт синхронно (поблочно: wal_block_size=8Кб), так как мы указали параметр --synchronous

8) Проверим, что файл .partial и текущий журнал кластера одинаковы:

postgres@tantor:~$ diff $HOME/archivelog/0000000100000115000000BE.partial $PGDATA/pg_wal/0000000100000115000000BE

Разницы нет, файлы одинаковы

9) Посмотрим статус слота репликации:

postgres=# select * from pg_replication_slots \gx

-[ RECORD 1 ]-------+-------------

slot_name           | arch

plugin              |

slot_type           | physical

datoid              |

database            |

temporary           | f

active              | t

active_pid          | 5018

xmin                |

catalog_xmin        |

restart_lsn         | 115/BE000198

confirmed_flush_lsn |

wal_status          | reserved

safe_wal_size       | 150994536

two_phase           | f

conflicting         |

postgres=# select * from pg_stat_replication \gx

-[ RECORD 1 ]----+----------------------------

pid              | 5018

usesysid         | 10

usename          | postgres

application_name | pg_receivewal

client_addr      |

client_hostname  |

client_port      | -1

backend_start    | 07:48:43.452192+03

backend_xmin     |

state            | streaming

sent_lsn         | 115/BE000198

write_lsn        | 115/BE000198

flush_lsn        | 115/BE000198

replay_lsn       |

write_lag        | 00:00:00.001285

flush_lag        | 00:00:00.001285

replay_lag       | 00:27:34.008699

sync_priority    | 0

sync_state       | async

reply_time       | 08:16:17.463519+03

Часть 8. Синхронная фиксация транзакций и pg_receivewal

1) Укажем имя приложения в списке тех клиентов, которые могут подтверждать транзакции в синхронном режиме:

postgres=# alter system set synchronous_standby_names = pg_receivewal;

ALTER SYSTEM

postgres=# select pg_reload_conf();

 pg_reload_conf

----------------

 t

(1 строка)

2) Обязательно убедитесь, что статус стал sync:

postgres=# select * from pg_stat_replication \gx

-[ RECORD 1 ]----+------------------------------

pid              | 5169

usesysid         | 10

usename          | postgres

application_name | pg_receivewal

client_addr      |

client_hostname  |

client_port      | -1

backend_start    | 08:30:00.356885+03

backend_xmin     |

state            | streaming

sent_lsn         | 115/BE000F70

write_lsn        | 115/BE000F70

flush_lsn        | 115/BE000F70

replay_lsn       |

write_lag        | 00:00:00.003395

flush_lag        | 00:00:00.003395

replay_lag       | 00:01:32.059937

sync_priority    | 1

sync_state       | sync

reply_time       | 08:31:32.419514+03

3) Если нет ни одного клиента со статусом sync, synchronous_standby_names не пуст, synchronous_commit не установлен в local или off, то транзакции будут подвисать и при прерывании <сtrl+c> выдавать ошибки вида:

postgres=# insert into t (t) values ('aaa');

^CCancel request sent

WARNING:  canceling wait for synchronous replication due to user request

DETAIL:  The transaction has already committed locally, but might not have been replicated to the standby.

INSERT 0 1

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

4) Уберите режим:

postgres=# alter system RESET synchronous_standby_names;

ALTER SYSTEM

postgres=# select pg_reload_conf();

 pg_reload_conf

----------------

 t

(1 строка)

Часть 9. Минимизация потерь данных транзакций

Для исключения потерь данных транзакций нужно до потери использовать режим синхронной фиксации.

Если позволяет размер директории pg_wal, скопируем архивные файлы, которые понадобятся для восстановления резервной копии в директорию pg_wal. Файлы журнала с которого начнется накат указан в backup_label или если его нет (переименован в backup_label.old), то в pg_control (который просматривается pg_controldata). Если размер директории не позволяет и использовать линки на уровне файловой системы не хочется, то можно использовать параметр rectore_command, но она будет копировать файлы журналов из архивной директории в pg_wal на что тратится время и увеличивает время восстановления.

Считаем что основной кластер у нас сбойнул и исчез. Благодаря синхронному режиму pg_receivewal принял все блоки текущего журнала. Если он использовался для подтверждения транзакций, то по журнальным записям (о фиксации транзакций), которые он не получил и не успел подтвердить и клиенты выполнявшие эти транзакции не получили подтверждения о фиксации, а получили сообщение о разрыве соединения (кластер сбойнул и исчез).

Не будем тратить время на создание сессий, выдачу команд, отслеживание LSN, чтобы не отвлекаться и сосредоточиться на основном.

1) Скопируем содержимое директории:

postgres@tantor:~$ cp $HOME/archivelog/* $HOME/backup/1/pg_wal

2) Переименуем файл .partial убрав расширение:

postgres@tantor:~$ mv $HOME/backup/1/pg_wal/0000000100000115000000BE.partial $HOME/backup/1/pg_wal/0000000100000115000000BE

3) Запустим резервный кластер:

postgres@tantor:~$ pg_ctl start -D $HOME/backup/1 -o "--port=5433"

LOG:  consistent recovery state reached at 115/BD000178

LOG:  invalid record length at 115/BE000F70: expected at least 26, got 0

LOG:  database system is ready to accept connections

что соответствует значению sent_lsn = 115/BE000F70 которое мы видели в pg_stat_replication.

4) Как останавливать pg_receivewal? Если не отправили в фон, как в нашем случае, то набрать в его окне Ctrl+c. Если отправили в фон, то найти номер процесса и послать сигнал SIGINT. Это корректное завершение pg_receivewal. Пример:

postgres@tantor$ kill -s SIGINT 5169

Утилита сообщит в stdout:

pg_receivewal: not renaming "0000000100000115000000BE.partial", segment is not complete

5) Остановим запасной кластер:

postgres@tantor:~$ pg_ctl stop -D $HOME/backup/1


Логическое копирование

  1. Использование утилиты pg_dump
  2. Формат custom и утилита pg_restore
  3. Формат directory
  4. Сжатие и скорость резервирования
  5. Команда COPY 

Часть 1. Использование утилиты pg_dump        

1) Выполните команду:

postgres@tantor:~$ pg_dump --schema-only

Параметр --schema-only позволяет выгружать только определения объектов («схемы объектов»), без данных. Другие параметры pg_dump не использовали, значит использовались параметры по умолчанию:

подсоединение к базе данных, к которой подсоединился бы psql;

вывод в sdout - на экран терминала;

формат формируемого дампа plain - текстовый скрипт.

2) Нажимая на клавиатуре комбинацию клавиш <Shift+PgUp> посмотрите как выглядит содержимое «дампа». Формат называется plain. «Дамп» содержит комментарии, команды SET, устанавливающие параметры сессии, позволяющие не зависеть от значений параметров той базы данных, в которой выполнялись бы команды из дампа:

SET statement_timeout = 0;

SET lock_timeout = 0;

SET idle_in_transaction_session_timeout = 0;

SET transaction_timeout = 0;

SET client_encoding = 'UTF8';

SET standard_conforming_strings = on;

SELECT pg_catalog.set_config('search_path', '', false);

SET check_function_bodies = false;

SET xmloption = content;

SET client_min_messages = warning;

SET row_security = off;

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

        Параметр row_security позволит получать ошибки в случае, если сработает политика row level security ("RLS"). По умолчанию pg_dump откажется выгружать данные, если у роли нет прав обходить эти политики. Право обходить политики даёт атрибут роли BYPASSRLS и SUPERUSER. Это нужно, чтобы убедиться, что все строки выгружены и будут загружены без ошибок.

        Параметр check_function_bodies отключает проверку тела подпрограмм в момент создания. Эта проверка нужна разработчикам, чтобы они видели ошибки при создании. Утилита отключает эту проверку, чтобы не заботиться о порядке выгрузки и порядке создания объектов. Это даёт гибкость: иметь возможность создать подпрограммы до создания таблиц, функций и других объектов, от которых зависят подпрограммы.

        3) Создайте базу данных с названием dump и таблицу в этой базе данных:

postgres=# CREATE DATABASE dump;

CREATE DATABASE

postgres=# \c dump

Вы подключены к базе данных "dump" как пользователь "postgres".

dump=# CREATE TABLE t (id bigserial, t text, b bytea);

CREATE TABLE

dump=# INSERT INTO t(t) values ('абвг'), (NULL), ('');

INSERT 0 3

        3) В командной строке (в другом окне терминала или выйти из psql) создайте базу данных dump1 и перегрузите в неё содержимое базы данных dump:

dump=# \q

postgres@tantor:~$ createdb dump1;

postgres@tantor:~$ pg_dump -d dump | psql -d dump1

...

 set_config

------------

 

(1 row)

...

CREATE TABLE

ALTER TABLE

CREATE SEQUENCE

ALTER SEQUENCE

ALTER SEQUENCE

ALTER TABLE

COPY 3

 setval

--------

      3

(1 строка)

В процессе работы psql выдаёт сообщения на экран терминала.

Утилита pg_dump подсоединилась к базе данных dump и через пайп ("|") передавала команды утилите psql, которая их тут же выполняла, подсоединившесь к базе данных dump1.

Преимущества использования пайпа («конвеер»):

не нужно место для файла в который выгружались бы данные;

уменьшается время, так как одновременно работают процессы выгрузки (pg_dump) и загрузки (psql).

Часть 2. Формат custom и утилита pg_restore

1) Запустите перегрузку данных из базы dump1 в базу dump в формате custom и предварительным удалением объектов перед их созданием:

postgres@tantor:~$

pg_dump -d dump1 --format=custom | pg_restore -d dump --clean --if-exists

Ошибок нет. Формат custom формирует один файл, который может загружать не psql, а утилита pg_restore.

2) Повторите перегрузку, добавив параметр --verbose чтобы посмотреть, какую информацию выдаёт:

postgres@tantor:~$

pg_dump -d dump1 --format=custom | pg_restore -d dump --clean --if-exists -v

pg_restore: подключение к базе данных для восстановления

pg_restore: удаляется DEFAULT t id

pg_restore: удаляется SEQUENCE t_id_seq

pg_restore: удаляется TABLE t

pg_restore: создаётся TABLE "public.t"

pg_restore: создаётся SEQUENCE "public.t_id_seq"

pg_restore: создаётся SEQUENCE OWNED BY "public.t_id_seq"

pg_restore: создаётся DEFAULT "public.t id"

pg_restore: обрабатываются данные таблицы "public.t"

pg_restore: выполняется SEQUENCE SET t_id_seq

3) Проверьте, что содержимое таблицы t соответствует исходному:

postgres@tantor:~$ psql -d dump -c "select * from t"

 id |  t   | b

----+------+---

  4 | абвг |

  5 |      |

  6 |      |

(3 строки)

4) Запустите выгрузку и загрузку утилитой pg_restore c параметром --list:

postgres@tantor:~$ pg_dump -d dump1 --format=custom | pg_restore -l

;

; Archive created at ..

;     dbname: dump1

;     TOC Entries: 10

;     Compression: gzip

;     Dump Version: 1.15-0

;     Format: CUSTOM

;     Integer: 4 bytes

;     Offset: 8 bytes

;     Dumped from database version: 16.1

;     Dumped by pg_dump version: 16.1

;

;

; Selected TOC Entries:

;

216; 1259 448357 TABLE public t postgres

217; 1259 448362 SEQUENCE public t_id_seq postgres

3288; 0 0 SEQUENCE OWNED BY public t_id_seq postgres

3135; 2604 448363 DEFAULT public t id postgres

3280; 0 448357 TABLE DATA public t postgres

3289; 0 0 SEQUENCE SET public t_id_seq postgres

        

Утилита pg_restore выдала содержимое (TOC, title of contents) дампа.

Параметр -l работает с дампами в формате custom или directory. Посмотрите как выглядит список. По каждому объекту выведена строка. Строки можно закомментировать и используя параметр -L утилиты pg_restore не загружать эти объекты.

5) У объектов могут быть зависимости от наличия других объектов. Зависимости выводятся в списке параметром при запуске pg_restore с параметорм -v. Используйте этот параметр чтобы посмотреть как отображаются зависимости:

postgres@tantor:~$ pg_dump -d dump1 --format=custom | pg_restore -l -v

;

; Archive created at 2024-03-23 08:36:49 MSK

;     dbname: dump1

;     TOC Entries: 10

;     Compression: gzip

;     Dump Version: 1.15-0

;     Format: CUSTOM

;     Integer: 4 bytes

;     Offset: 8 bytes

;     Dumped from database version: 16.1

;     Dumped by pg_dump version: 16.1

;

;

; Selected TOC Entries:

;

3284; 0 0 ENCODING - ENCODING

3285; 0 0 STDSTRINGS - STDSTRINGS

3286; 0 0 SEARCHPATH - SEARCHPATH

3287; 1262 448356 DATABASE - dump1 postgres

216; 1259 448357 TABLE public t postgres

217; 1259 448362 SEQUENCE public t_id_seq postgres

;       depends on: 216

3288; 0 0 SEQUENCE OWNED BY public t_id_seq postgres

;       depends on: 217

3135; 2604 448363 DEFAULT public t id postgres

;       depends on: 217 216

3280; 0 448357 TABLE DATA public t postgres

;       depends on: 216

3289; 0 0 SEQUENCE SET public t_id_seq postgres

;       depends on: 217

Строки с отображением зависимостей закомментированы.

6) Если не указать утилите pg_restore параметры -d или -l , а указать только -f, то из дампа в формате custom, directory, tar создаётся скрипт с командами SQL. Создайте скрипт:

postgres@tantor:~$

pg_dump -d dump1 --format=custom | pg_restore -f script.sql

7) Создайте скрипт дампа в формате plain:

postgres@tantor:~$ pg_dump -d dump1 -f script1.sql

8) Сравните два скрипта:

postgres@tantor:~$ diff script.sql script1.sql

Скрипты не отличаются друг от друга. Утилита pg_restore может формировать файл дампа формата plain из дампов форматов custom, directory, tar.

Часть 3. Формат directory

1) Создайте дамп в формате directory:

postgres@tantor:~$ pg_dump -d dump1 --format=directory -f ./1

postgres@tantor:~$ ls ./1

3280.dat.gz  toc.dat

2) Директория создаётся автоматически. В директории лежит бинарный файл дампа и файлы с данными для которых по умолчанию используется сжатие:

3) Удалите директорию и создайте дамп без сжатия:

postgres@tantor:~$ rm -rf ./1

postgres@tantor:~$ pg_dump -d dump1 --format=directory -Z0 -f ./1

postgres@tantor:~$ ls ./1

3280.dat  toc.dat

4) Посмотрите содержимое любого файла .dat:

postgres@tantor:~$ cat ./1/3280.dat 

4       абвг    \N

5       \N      \N

6               \N

\.

Файл .dat содержит результат выполнения команды COPY в формате по умолчанию для этой команды. \N - пустые (NULL) значения. \. - символы завершения команды COPY.

5) Можно выгрузить только данные, без команд создания объектов:

postgres@tantor:~$ pg_dump -d dump -a

в дампе будут отсутствовать команды CREATE.

6) Параметр --quote-all-identifiers указывает брать в кавычки все идентификаторы:

postgres@tantor:~$ pg_dump -d dump --quote-all-identifiers | grep \"

-- Name: SCHEMA "public"; Type: COMMENT; Schema: -; Owner: pg_database_owner

COMMENT ON SCHEMA "public" IS 'standard public schema';

SET default_table_access_method = "heap";

CREATE TABLE "public"."t" (

    "id" bigint NOT NULL,

    "t" "text",

    "b" "bytea"

ALTER TABLE "public"."t" OWNER TO "postgres";

CREATE SEQUENCE "public"."t_id_seq"

ALTER SEQUENCE "public"."t_id_seq" OWNER TO "postgres";

ALTER SEQUENCE "public"."t_id_seq" OWNED BY "public"."t"."id";

ALTER TABLE ONLY "public"."t" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."t_id_seq"'::"regclass");

COPY "public"."t" ("id", "t", "b") FROM stdin;

SELECT pg_catalog.setval('"public"."t_id_seq"', 6, true);

7) Для формирования команд INSERT вместо команды COPY используется параметр
--rows-per-insert:

postgres@tantor:~$ pg_dump -d dump --rows-per-insert=1 | grep INS

INSERT INTO public.t VALUES (4, 'абвг', NULL);

INSERT INTO public.t VALUES (5, NULL, NULL);

INSERT INTO public.t VALUES (6, '', NULL);

Часть 4. Сжатие и скорость резервирования

1) Запустите psql и подсоединитесь к базе данных dump:

postgres@tantor:~$ psql -d dump

psql (16.1)

Введите "help", чтобы получить справку.

2) Выполните в psql следующие команды создания таблицы и наполнения её данными:

DROP TABLE IF EXISTS t;

CREATE TABLE t (id bigserial, t text);

INSERT INTO t(t) SELECT encode((floor(random()*1000)::numeric ^ 100::numeric)::text::bytea, 'base64') from generate_series(1,500000);

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

postgres@tantor:~$ date +%T ; rm -rf ./1 ; pg_dump -d dump --format=directory -Z lz4 -f ./1 ; date +%T ; ls -l ./1

23:28:54

23:28:55

total 114804

-rw-r--r-- 1 postgres postgres 117547931 23:28 3281.dat.lz4

-rw-r--r-- 1 postgres postgres      2127 23:28 toc.dat

postgres@tantor:~$ date +%T ; rm -rf ./1 ; pg_dump -d dump --format=directory -Z zstd -f ./1 ; date +%T ; ls -l ./1

23:29:17

23:29:18

total 7504

-rw-r--r-- 1 postgres postgres 7677214 23:29 3281.dat.zst

-rw-r--r-- 1 postgres postgres    2127 23:29 toc.dat

postgres@tantor:~$ date +%T ; rm -rf ./1 ; pg_dump -d dump --format=directory -Z gzip -f ./1 ; date +%T ; ls -l ./1

23:29:31

23:29:46

total 66436

-rw-r--r-- 1 postgres postgres 68022603 23:29 3281.dat.gz

-rw-r--r-- 1 postgres postgres     2127 23:29 toc.dat

postgres@tantor:~$ date +%T ; rm -rf ./1 ; pg_dump -d dump --format=directory -Z 0 -f ./1 ; date +%T ; ls -l ./1

23:29:52

23:29:53

total 175624

-rw-r--r-- 1 postgres postgres 179830026 23:29 3281.dat

-rw-r--r-- 1 postgres postgres      2127 23:29 toc.dat

По результатам команд можно оценить время выгрузки в зависимости от выбранного алгоритма сжатия. Также можно менять степень сжатия, указав после названия алгоритма двоеточие и число: -Z zstd:1

Часть 5. Команда COPY

1) Результат команды COPY можно передавать на вход программе, например gzip:

dump=# COPY pg_authid TO PROGRAM 'gzip > file.gz';

COPY 17

В этом примере вызывается программа gzip и создаёт файл $PGDATA/file.gz, в котором содержится текстовый файл с названием "file".

2) Можно сохранять результаты выполнения любых команд, возвращающих данные. Например, команды WITH:

COPY (WITH RECURSIVE

 t(n) AS ( SELECT 1

 UNION ALL

 SELECT n+1 FROM t

)

SELECT n FROM t LIMIT 1

)

TO stdout;

3) Выполните команды:

drop table if exists t2;

create table t2 (c1 text);

insert into t2 (c1) VALUES (repeat(E'a\n', 357913941));

COPY t2 TO '/tmp/test';

При выполнении последней команды выдастся ошибка:

ERROR:  out of memory

ПОДРОБНОСТИ:  Cannot enlarge string buffer containing 1073741822 bytes by 1 more bytes.

Размер поля - треть гигабайта.

При выгрузке в текстовом виде содержимое поля будет выглядеть так:

a\na\na\na\n и размер поля увеличится в три раза до 1073741823 байт, что на 1 байт превышает максимальный размер буфера строк.

4) Поле можно выгрузить используя формат binary:

postgres=# COPY t2 TO '/tmp/test' WITH BINARY;

COPY 1

5) Удалите файл:

postgres=# \! rm /tmp/test

6) Сравним формат по умолчанию и CSV. Выполните команды:

postgres=# copy t to stdout with (format text);

1       абвг    \N

2       \N      \N

3               \N

postgres=# copy t to stdout with (format csv);

1,абвг,

2,,

3,"",

В формате CSV пустая строка была взята в кавычки.


Раздел 8. Репликация

Физическая репликация

  1. Создание реплики
  2. Слоты репликации
  3. Изменение имени кластера
  4. Создание второй реплики
  5. Выбор реплики на роль мастера
  6. Подготовка к переключению на реплику
  7. Переключение на реплику
  8. Включение обратной связи
  9. Утилита pg_rewind

Часть 1. Создание реплики

1) Проверьте, есть ли табличные пространства:

postgres=# \db

     List of tablespaces

    Name    |  Owner   |       Location      

------------+----------+----------------------------------------------

 pg_default | postgres |

 pg_global  | postgres |

 u01tbs     | postgres | /var/lib/postgresql/tantor-se-16/data/../u01

(3 rows)

2) Если есть табличные пространства, кроме двух стандартных (pg_global, pg_default) посмотрите какие отношения есть в них:

SELECT n.nspname, relname

FROM pg_class c

 LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,

 pg_tablespace t

WHERE relkind IN ('r','m','i','S','t') AND

 n.nspname <> 'pg_toast' AND t.oid = reltablespace AND

 t.spcname = 'u01tbs';

 nspname | relname

---------+---------

 public  | t

(1 строка)

3) Удалите объекты, которые используют эти табличные пространства:

postgres=# drop table t;

DROP TABLE

4) Удалите табличное пространство u01tbs:

postgres=# drop tablespace u01tbs;

DROP TABLESPACE

5) Если нет второго окна терминала (fly-term), то откройте второе окно терминала и переключитесь в пользователя postgres:

astra@tantor:~$ su - postgres

Password: postgres

postgres@tantor:~$

6) Удалите директорию:

postgres@tantor:~$ rm -rf /var/lib/postgresql/tantor-se-16-replica/data1

7) Сделайте бэкап с параметрами:

-P показывает прогресс резервирования;

-C или --slot создает слот;

-R создает файлы конфигурации для реплики:

postgres@tantor:~$ pg_basebackup -D /var/lib/postgresql/tantor-se-16-replica/data1 -P -R -C --slot=replica1

Если резервирование прервать, то нужно будет удалить директорию:
rm -rf /var/lib/postgresql/tantor-se-16-replica/data1

и слот на мастере:
select pg_drop_replication_slot('replica1');

8) После успешного создания бэкапа нужно установить порт для экземпляра реплики. Обязательно две угловые скобки, если будет одна, то файл затрётся:

echo "port=5433" >> /var/lib/postgresql/tantor-se-16-replica/data1/postgresql.auto.conf

9) Чтобы диагностические сообщения выводились на экран терминала добавьте строку в файл конфигурации:

echo "logging_collector = off" >> /var/lib/postgresql/tantor-se-16-replica/data1/postgresql.auto.conf

Иначе диагностические сообщения будут писаться в файл директории PGDATA/log.

При запуске экземпляра утилитой pg_ctl в таком случае будет выдаваться сообщение:

ожидание запуска сервера....

[pid] СООБЩЕНИЕ:  передача вывода в протокол процессу сбора протоколов

[pid] ПОДСКАЗКА:  В дальнейшем протоколы будут выводиться в каталог "log".

 готово

В сообщении "ПОДСКАЗКА" выдаётся значение парамера log_directory. Параметр log_destination = stderr, а это значит что в PGDATA создаётся файл current_logfiles, в который записывается расположение файлов логов, в которые пишет процесс коллектора.

10) Можно запустить реплику:

pg_ctl start -D /var/lib/postgresql/tantor-se-16-replica/data1

ожидание запуска сервера....

[7849] СООБЩЕНИЕ:  запускается PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

[7849] СООБЩЕНИЕ:  для приёма подключений по адресу IPv4 "0.0.0.0" открыт порт 5433

[7849] СООБЩЕНИЕ:  для приёма подключений по адресу IPv6 "::" открыт порт 5433

[7849] СООБЩЕНИЕ:  для приёма подключений открыт Unix-сокет "/var/run/postgresql/.s.PGSQL.5433"

[7852] СООБЩЕНИЕ:  работа системы БД была прервана; последний момент работы:

[7852] СООБЩЕНИЕ:  переход в режим резервного сервера

[7852] СООБЩЕНИЕ:  запись REDO начинается со смещения 9/BB000028

[7852] СООБЩЕНИЕ:  согласованное состояние восстановления достигнуто в позиции 9/BB000130

[7849] СООБЩЕНИЕ:  система БД готова принимать подключения в режиме "только чтение"

[7853] СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 1

 готово

сервер запущен

Диагностические сообщения (протокол работы экземпляра) выводятся в терминал.

Реплика создана, получает без задержки журнальные записи и применяет их.

Часть 2. Слоты репликации

1) В окне терминала с psql, подсоединенному к мастеру посмотрите, что слот создан и активен:

postgres=# select * from pg_replication_slots;

 slot_name | plugin | slot_type | datoid | database | temporary | active |

-----------+--------+-----------+--------+----------+-----------+--------+-

 replica1  |        | physical  |        |          | f         | t      |

(1 строка)

2) Еще одно представление для мониторинга репликации:

postgres=# select * from pg_stat_replication \gx

-[ RECORD 1 ]----+------------------------------

pid              | 7854

usesysid         | 10

usename          | postgres

application_name | walreceiver

client_addr      |

client_hostname  |

client_port      | -1

backend_start    | 13:56:31.619654+03

backend_xmin     |

state            | streaming

sent_lsn         | 9/BC000198

write_lsn        | 9/BC000198

flush_lsn        | 9/BC000198

replay_lsn       | 9/BC000198

write_lag        |

flush_lag        |

replay_lag       |

sync_priority    | 0

sync_state       | async

reply_time       | 14:24:31.557301+03

Имя приложения по умолчанию walreceiver.

3) Подключитесь к реплике:

postgres=# \connect postgres postgres /var/run/postgresql 5433

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5433".

4) Посмотрите название слота репликации, к которому подсоединяется реплика:

postgres=# \dconfig primary_slot_name 

List of configuration parameters

     Parameter     |  Value  

-------------------+----------

 primary_slot_name | replica1

(1 строка)

5) Посмотрите значение параметра cluster_name:

postgres=# \dconfig cluster_name

List of configuration parameters

  Parameter   | Value

--------------+-------

 cluster_name |

Значение параметра cluster_name пусто, поэтому поэтому значение параметра application_name имеет значение по умолчанию walreceiver.

6) Посмотрите значение параметра primary_conninfo:

postgres=# show primary_conninfo \gx

-[ RECORD 1 ]----+-----

primary_conninfo | user=postgres passfile='/var/lib/postgresql/.pgpass' channel_binding=prefer port=5432 sslmode=prefer sslcompression=0 sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0 compression=off target_session_attrs=any load_balance_hosts=disable

Часть 3. Изменение имени кластера

1) Установите значение параметра cluster_name:

postgres=# alter system set cluster_name ='replica1';

ALTER SYSTEM

2) На реплике представление pg_stat_replication пусто:

postgres=# select * from pg_stat_replication;

 pid | usesysid | usename | application_name | client_addr | client_hostname

-----+----------+---------+------------------+-------------+----------------

(0 строк)

3) Изменение параметра cluster_name требует рестарта экземпляра, перезапустите экземпляр реплики в окне терминала:

postgres@tantor:~$ pg_ctl restart -D /var/lib/postgresql/tantor-se-16-replica/data1

ожидание завершения работы сервера....

 готово

сервер остановлен

ожидание запуска сервера....

[25550] СООБЩЕНИЕ:  запускается PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

[25550] СООБЩЕНИЕ:  для приёма подключений по адресу IPv4 "0.0.0.0" открыт порт 5433

MSK [25550] СООБЩЕНИЕ:  для приёма подключений по адресу IPv6 "::" открыт порт 5433

[25550] СООБЩЕНИЕ:  для приёма подключений открыт Unix-сокет "/var/run/postgresql/.s.PGSQL.5433"

MSK [25553] СООБЩЕНИЕ:  система БД была выключена в процессе восстановления: 14:37:36 MSK

[25553] СООБЩЕНИЕ:  переход в режим резервного сервера

[25553] СООБЩЕНИЕ:  запись REDO начинается со смещения 9/BC000070

[25553] СООБЩЕНИЕ:  согласованное состояние восстановления достигнуто в позиции 9/BC000198

[25553] СООБЩЕНИЕ:  неверная длина записи в позиции 9/BC000198: ожидалось минимум 26, получено 0

[25550] СООБЩЕНИЕ:  система БД готова принимать подключения в режиме "только чтение"

[25554] СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 1

 готово

сервер запущен        

4) Посмотрите список процессов в названии которых присутствует буквосочетание wal:

postgres@tantor:~$ ps -ef | grep wal

UID      PID   PPID   CMD

postgres  2654 13810  postgres: 11/main: walwriter  

70       11476 13796  postgres: walwriter

postgres 13539 13534  postgres: walwriter

postgres 25554 25550  postgres: replica1: walreceiver 

postgres 25555 13534  postgres: walsender postgres [local] streaming 9/BC000198

postgres 26488 31415  grep wal

В списке есть процессы:

walsender который передаёт журнальную запись

walwriter который принимает то, что ему передаёт walsender

PPID=25550 это номер родительского процесса (Parent Process ID) для процесса с PID=25554.

В данном примере номер процесса postgres мастера 13534, walsender 25555.

5) Посмотрите список процессов мастера:

postgres@tantor:~$ ps -o pid,command --ppid `head -n 1 /var/lib/postgresql/tantor-se-16/data/postmaster.pid`

  PID COMMAND

13535 postgres: logger

13536 postgres: checkpointer

13537 postgres: background writer

13539 postgres: walwriter

13540 postgres: autovacuum launcher

13541 postgres: logical replication launcher

25555 postgres: walsender postgres [local] streaming 9/BC000198

Процесс postgres, который их запустил не выводится.

6) Посмотрите список процессов реплики:

postgres@tantor:~$ ps -o pid,command --ppid `head -n 1 /var/lib/postgresql/tantor-se-16-replica/data1/postmaster.pid`

  PID COMMAND

25551 postgres: replica1: checkpointer

25552 postgres: replica1: background writer

25553 postgres: replica1: startup recovering 0000000100000009000000BC

25554 postgres: replica1: walreceiver

После установки значения cluster_name у процессов реплики присутствует идентификатор replica1.

7) Сделаем префиксирование названий процессов для мастера, подсоединитесь к мастеру:

postgres=# \c postgres postgres /var/run/postgresql 5432

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5432".

8) Выполните команду:

postgres=# alter system set cluster_name ='master';

ALTER SYSTEM

9) Изменение параметра cluster_name требует рестарта экземпляра, перезапустите экземпляр мастера в окне терминала:

postgres@tantor:~$ sudo systemctl restart tantor-se-server-16

В окне терминала, в котором выполнялась команда pg_ctl start для запуска реплики будут выданы диагностические сообщения экземпляра реплики:

[25554] СООБЩЕНИЕ:  репликация прекращена главным сервером

[25554] ПОДРОБНОСТИ:  На линии времени 1 в 9/BC000230 достигнут конец журнала.

[25554] ВАЖНО:  не удалось отправить главному серверу сообщение о конце передачи: сервер неожиданно закрыл соединение

                Скорее всего сервер прекратил работу из-за сбоя

                до или в процессе выполнения запроса.

        операция COPY не выполняется

[25553] СООБЩЕНИЕ:  неверная длина записи в позиции 9/BC000230: ожидалось минимум 26, получено 0

[5727] ВАЖНО:  не удалось подключиться к главному серверу: подключиться к серверу через сокет "/var/run/postgresql/.s.PGSQL.5432" не удалось: сервер неожиданно закрыл соединение

                Скорее всего сервер прекратил работу из-за сбоя

                до или в процессе выполнения запроса.

[25553] СООБЩЕНИЕ:  waiting for WAL to become available at 9/BC00024A

[5782] СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 1

[25551] СООБЩЕНИЕ:  начата точка перезапуска: time

[25551] СООБЩЕНИЕ:  точка перезапуска завершена: записано буферов: 1 (0.0%); добавлено файлов WAL 0, удалено: 0, переработано: 0; запись=0.002 сек., синхр.=0.001 сек., всего=0.010 сек.; синхронизировано_файлов=0, самая_долгая_синхр.=0.000 сек., средняя=0.000 сек.; расстояние=0 kB, ожидалось=0 kB; lsn=9/BC000198, lsn redo=9/BC000198

[25551] СООБЩЕНИЕ:  точка перезапуска восстановления в позиции 9/BC000198

10) Посмотрите список процессов реплики:

postgres@tantor:~$ ps -o pid,command --ppid `head -n 1 /var/lib/postgresql/tantor-se-16-replica/data1/postmaster.pid`

  PID COMMAND

 5782 postgres: replica1: walreceiver streaming 9/BC0003A0

25551 postgres: replica1: checkpointer

25552 postgres: replica1: background writer

25553 postgres: replica1: startup recovering 0000000100000009000000BC

Предыдущий процесс walreceiver 25554 был остановлен и выгружен из памяти. Был запущен процесс walreceiver 5725, но он не смог подсоединиться, так как экземпляр мастера отказал в соединении. Был запущен процесс walreceiver 5782, который успешно подсоединился к мастеру и принимает журнальные данные.

11) Посмотрите список процессов мастера:

postgres@tantor:~$ ps -o pid,command --ppid `head -n 1 /var/lib/postgresql/tantor-se-16/data/postmaster.pid`

  PID COMMAND

 5743 postgres: master: logger

 5751 postgres: master: checkpointer

 5752 postgres: master: background writer

 5755 postgres: master: walwriter

 5756 postgres: master: autovacuum launcher

 5757 postgres: master: logical replication launcher

 5783 postgres: master: walsender postgres [local] streaming 9/BC000278

Теперь после имени процессов мастера указано значение параметра cluster_name.

Часть 4. Создание второй реплики

1) Создайте на мастере слот для второй реплики:

postgres=# select pg_copy_physical_replication_slot('replica1','replica2');

 pg_copy_physical_replication_slot

-----------------------------------

 (replica2,)

(1 строа)

2) Посмотрите список слотов:

postgres=# select slot_name, active, restart_lsn, wal_status from pg_replication_slots;

 slot_name  | active | restart_lsn | wal_status

------------+--------+-------------+------------

 pgstandby1 | f      | 0/19187E70  | lost

 replica1   | t      | 9/BC0003A0  | reserved

 replica2   | f      | 9/BC0003A0  | reserved

(3 строки)

Второй слот будет удерживать журнальные файлы начиная с файла в котором содержится журнальная запись с адресом restart_lsn.

В списке может присутствовать слот pgstandby1. Это слот реплики, которая была изначально в виртуальной машине. Эту реплику и слот можно будет удалить за ненадобностью.

3) Сгенерируем журнальные записи на мастере. Выполните контрольную точку:

postgres=# checkpoint;

CHECKPOINT

в лог сообщений кластера master в директории PGDATA/log выведется:

[5751] LOG:  checkpoint starting: immediate force wait

[5751] LOG:  checkpoint complete: wrote 0 buffers (0.0%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.001 s, sync=0.001 s, total=0.009 s; sync files=0, longest=0.000 s, average=0.000 s; distance=0 kB, estimate=0 kB; lsn=9/BC0003E8, redo lsn=9/BC0003A0

4) Посмотрим как изменился restart_lsn. Выполните запрос к списку слотов:

postgres=# select slot_name, active, restart_lsn, wal_status from pg_replication_slots;

 slot_name | active | restart_lsn  | wal_status

-----------+--------+--------------+------------

 pgstandby1 | f      | 0/19187E70  | lost

 replica1   | t      | 9/BC0004C8  | reserved

 replica2   | f      | 9/BC0003A0  | reserved

(3 строки)

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

5) Создадим вторую реплику. Чтобы не нагружать мастер сделаем бэкап копируя файлы с реплики («backup offloading»).

postgres@tantor:~$ pg_basebackup -p 5433 -D /var/lib/postgresql/tantor-se-16-replica/data2 -P -R

466575/466575 КБ (100%), табличное пространство 1/1

В случае ошибки можно удалить бэкап:

rm -rf /var/lib/postgresql/tantor-se-16-replica/data2

6) Добавьте параметр port=5434 и logging_collector = off для второй реплики. Можно отредактировать файл текстовым редактором, можно добавить параметр в конец файла. Последнее значение превалирует.

postgres@tantor:~$ echo "port=5434" >> /var/lib/postgresql/tantor-se-16-replica/data2/postgresql.auto.conf

postgres@tantor:~$ echo "logging_collector = off" >> /var/lib/postgresql/tantor-se-16-replica/data2/postgresql.auto.conf

7) Посмотрите содержимое файла postgresql.auto.conf  новой реплики:

postgres@tantor:~/tantor-se-16-replica/data2$ cat /var/lib/postgresql/tantor-se-16-replica/data2/postgresql.auto.conf 

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

listen_addresses = '*'

max_slot_wal_keep_size = '128MB'

max_wal_size = '128MB'

min_wal_size = '512MB'

idle_in_transaction_session_timeout = '100min'

primary_conninfo = 'user=postgres passfile=''/var/lib/postgresql/.pgpass'' channel_binding=prefer port=5432 sslmode=prefer sslcompression=0 sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0 compression=off target_session_attrs=any load_balance_hosts=disable'

primary_slot_name = 'replica1'

port = '5433'

logging_collector = 'off'

cluster_name = 'replica1'

primary_conninfo = 'user=postgres passfile=''/var/lib/postgresql/.pgpass'' channel_binding=prefer port=5433 sslmode=prefer sslcompression=0 sslcertmode=allow sslsni=1 ssl_min_protocol_version=TLSv1.2 gssencmode=prefer krbsrvname=postgres gssdelegation=0 compression=off target_session_attrs=any load_balance_hosts=disable'

port=5434

logging_collector = off

pg_basebackup резервировал подключившись к первой реплике и поставил её порт 5433 в параметр primary_conninfo. С таким значением получается каскадирование передачи журнальных данных.

8) Отредактируйте файл /var/lib/postgresql/tantor-se-16-replica/data2/postgresql.auto.conf, установив порт 5432 - пусть вторая реплика подсоединяется к мастеру напрямую, имя слота и кластера в replica2.

Пример содержимого файла после редактирования:

postgres@tantor:~$ cat /var/lib/postgresql/tantor-se-16-replica/data2/postgresql.auto.conf

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

listen_addresses = '*'

max_slot_wal_keep_size = '128MB'

max_wal_size = '128MB'

min_wal_size = '512MB'

idle_in_transaction_session_timeout = '100min'

primary_conninfo = 'user=postgres port=5432'

primary_slot_name = 'replica2'

cluster_name = 'replica2'

port=5434

logging_collector = off

9) Запустите вторую реплику:

postgres@tantor:~$ pg_ctl start -D /var/lib/postgresql/tantor-se-16-replica/data2

ожидание запуска сервера....

[5728] СООБЩЕНИЕ:  запускается PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

[5728] СООБЩЕНИЕ:  для приёма подключений по адресу IPv4 "0.0.0.0" открыт порт 5434

[5728] СООБЩЕНИЕ:  для приёма подключений по адресу IPv6 "::" открыт порт 5434

[5728] СООБЩЕНИЕ:  для приёма подключений открыт Unix-сокет "/var/run/postgresql/.s.PGSQL.5434"

[5731] СООБЩЕНИЕ:  система БД была выключена в процессе восстановления:

[5731] СООБЩЕНИЕ:  переход в режим резервного сервера

[5731] СООБЩЕНИЕ:  запись REDO начинается со смещения 9/BC0003A0

[5731] СООБЩЕНИЕ:  согласованное состояние восстановления достигнуто в позиции 9/BC0004C8

[5728] СООБЩЕНИЕ:  система БД готова принимать подключения в режиме "только чтение"

[5731] СООБЩЕНИЕ:  неверная длина записи в позиции 9/BC0004C8: ожидалось минимум 26, получено 0

[5732] СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 1

 готово

сервер запущен

10) Проверьте статус слотов:

postgres=# select slot_name, active, restart_lsn, wal_status from pg_replication_slots;

 slot_name  | active | restart_lsn | wal_status

------------+--------+-------------+------------

 pgstandby1 | f      | 0/19187E70  | lost

 replica1   | t      | 9/BC0004C8  | reserved

 replica2   | t      | 9/BC0004C8  | reserved

(3 строки)

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

11) Сгенерируем журнальные записи. Выполните контрольную точку:

postgres=# checkpoint;

CHECKPOINT

12) Повторите запрос к pg_replication_slots:

postgres=# select slot_name, active, restart_lsn, wal_status from pg_replication_slots;

 slot_name  | active | restart_lsn | wal_status

------------+--------+-------------+------------

 pgstandby1 | f      | 0/19187E70  | lost

 replica1   | t      | 9/BC0005F0  | reserved

 replica2   | t      | 9/BC0005F0  | reserved

(3 строки)

Сообщения экземпляров реплик:

[5729] СООБЩЕНИЕ:  начата точка перезапуска: time

[5729] СООБЩЕНИЕ:  точка перезапуска завершена: записано буферов: 1 (0.0%); добавлено файлов WAL 0, удалено: 0, переработано: 0; запись=0.002 сек., синхр.=0.001 сек., всего=0.008 сек.; синхронизировано_файлов=0, самая_долгая_синхр.=0.000 сек., средняя=0.000 сек.; расстояние=0 kB, ожидалось=0 kB; lsn=9/BC000510, lsn redo=9/BC0004C8

[5729] СООБЩЕНИЕ:  точка перезапуска восстановления в позиции 9/BC0004C8

[25551] СООБЩЕНИЕ:  начата точка перезапуска: time

[25551] СООБЩЕНИЕ:  точка перезапуска завершена: записано буферов: 0 (0.0%); добавлено файлов WAL 0, удалено: 0, переработано: 0; запись=0.001 сек., синхр.=0.001 сек., всего=0.006 сек.; синхронизировано_файлов=0, самая_долгая_синхр.=0.000 сек., средняя=0.000 сек.; расстояние=0 kB, ожидалось=0 kB; lsn=9/BC000510, lsn redo=9/BC0004C8

[25551] СООБЩЕНИЕ:  точка перезапуска восстановления в позиции 9/BC0004C8

Точка перезапуска - отражение контрольной точки мастера.

Часть 5. Выбор реплики на роль мастера

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

1) postgres@tantor:~$ chmod -w /var/lib/postgresql/tantor-se-16-replica/data2/pg_wal/000*

postgres@tantor:~$ pg_ctl restart -D /var/lib/postgresql/tantor-se-16-replica/data2

12:19:48.996 MSK [5728] СООБЩЕНИЕ:  получен запрос на быстрое выключение

ожидание завершения работы сервера....

12:19:48.998 MSK [5728] СООБЩЕНИЕ:  прерывание всех активных транзакций

12:19:48.998 MSK [5732] ВАЖНО:  завершение процесса считывания журнала по команде администратора

12:19:49.004 MSK [5729] СООБЩЕНИЕ:  выключение

12:19:49.017 MSK [5728] СООБЩЕНИЕ:  система БД выключена

 готово

сервер остановлен

ожидание запуска сервера....

12:19:49.142 MSK [24184] СООБЩЕНИЕ:  запускается PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

12:19:49.142 MSK [24184] СООБЩЕНИЕ:  для приёма подключений по адресу IPv4 "0.0.0.0" открыт порт 5434

12:19:49.142 MSK [24184] СООБЩЕНИЕ:  для приёма подключений по адресу IPv6 "::" открыт порт 5434

12:19:49.144 MSK [24184] СООБЩЕНИЕ:  для приёма подключений открыт Unix-сокет "/var/run/postgresql/.s.PGSQL.5434"

12:19:49.149 MSK [24187] СООБЩЕНИЕ:  система БД была выключена в процессе восстановления: 12:19:48 MSK

12:19:49.149 MSK [24187] СООБЩЕНИЕ:  переход в режим резервного сервера

12:19:49.152 MSK [24187] СООБЩЕНИЕ:  запись REDO начинается со смещения 9/BC0004C8

12:19:49.152 MSK [24187] СООБЩЕНИЕ:  согласованное состояние восстановления достигнуто в позиции 9/BC0005F0

12:19:49.152 MSK [24187] СООБЩЕНИЕ:  неверная длина записи в позиции 9/BC0005F0: ожидалось минимум 26, получено 0

12:19:49.152 MSK [24184] СООБЩЕНИЕ:  система БД готова принимать подключения в режиме "только чтение"

12:19:49.160 MSK [24188] СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 1

12:19:49.160 MSK [24188] ВАЖНО:  не удалось открыть файл "pg_wal/0000000100000009000000BC": Отказано в доступе

12:19:49.167 MSK [24190] СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 1

12:19:49.168 MSK [24190] ВАЖНО:  не удалось открыть файл "pg_wal/0000000100000009000000BC": Отказано в доступе

12:19:49.168 MSK [24187] СООБЩЕНИЕ:  waiting for WAL to become available at 9/BC00060A

 готово

сервер запущен

В журнал кластера будут выдаваться ошибки раз в 5 секунд начение параметра wal_retrieve_retry_interval):

12:19:54.173 MSK [24232] СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 1

12:19:54.174 MSK [24232] ВАЖНО:  не удалось открыть файл "pg_wal/0000000100000009000000BC": Отказано в доступе

12:19:54.174 MSK [24187] СООБЩЕНИЕ:  waiting for WAL to become available at 9/BC00060A

2) Поменяйте интервал повторения попыток walreceiver с 5 секунд на 30 секунд. В окне терминала psql подсоединитесь ко второй реплике:

postgres=# \c postgres postgres /var/run/postgresql 5434

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5434".

postgres=# alter system set wal_retrieve_retry_interval='30s';

ALTER SYSTEM

postgres=# select pg_reload_conf();

 pg_reload_conf

----------------

 t

(1 строка)

Ошибки у второй реплики станут выдаваться реже, раз в 30 секунд.

3) Заставим мастер создать журнальные записи. Подсоединитесь к мастеру и выполните контрольную точку:

postgres=# \c postgres postgres /var/run/postgresql 5432

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5432".

postgres=# checkpoint;

CHECKPOINT

postgres=# select slot_name, active, restart_lsn, wal_status from pg_replication_slots;

 slot_name | active | restart_lsn  | wal_status

-----------+--------+--------------+------------

 pgstandby1 | f      | 0/19187E70  | lost

 replica1   | t      | 9/BC0006D0  | reserved

 replica2   | f      | 9/BC0005F0  | reserved

(3 строки)

Статус второй реплики неактивен и restart_lsn стал разным.

4) Симулируем сбой мастера. Остановите мастер:

postgres@tantor:~$ pg_ctl stop -D /var/lib/postgresql/tantor-se-16/data

ожидание завершения работы сервера....

12:40:35.444 MSK [5782] СООБЩЕНИЕ:  репликация прекращена главным сервером

12:40:35.444 MSK [5782] ПОДРОБНОСТИ:  На линии времени 1 в 9/BC0007B0 достигнут конец журнала.                                                                                                     12:40:35.444 MSK [5782] ВАЖНО:  не удалось отправить главному серверу сообщение о конце передачи: сервер неожиданно закрыл соединение

Скорее всего сервер прекратил работу из-за сбоя до или в процессе выполнения запроса.

операция COPY не выполняется                                                                                                                            12:40:35.444 MSK [25553] СООБЩЕНИЕ:  неверная длина записи в позиции 9/BC0007B0: ожидалось минимум 26, получено 0

12:40:35.453 MSK [753] ВАЖНО:  не удалось подключиться к главному серверу: подключиться к серверу через сокет "/var/run/postgresql/.s.PGSQL.5432" не удалось: сервер неожиданно закрыл соединение

Скорее всего сервер прекратил работу из-за сбоя до или в процессе выполнения запроса.

12:40:35.453 MSK [25553] СООБЩЕНИЕ:  waiting for WAL to become available at 9/BC0007CA

 готово

сервер остановлен

5) Устраним проблему на второй реплике. Верните разрешения на файл журнала:

postgres@tantor:~$ chmod +w /var/lib/postgresql/tantor-se-16-replica/data2/pg_wal/000*

6) Имея две реплики в случае сбоя мастера нужно выбрать ту реплику, которую лучше сделать мастером.

Посмотрите какие журнальные записи имеются на первой реплике:

postgres@tantor:~$ \c postgres postgres /var/run/postgresql 5433

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5433".

postgres=# select pg_last_wal_replay_lsn();

 pg_last_wal_replay_lsn

------------------------

 9/BC0007B0

(1 строка)

postgres=# select pg_last_wal_receive_lsn();

 pg_last_wal_receive_lsn

-------------------------

 9/BC0007B0

(1 строка)

7) Посмотрите какие журнальные записи имеются на второй реплике:

postgres@tantor:~$ \c postgres postgres /var/run/postgresql 5434

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5434".

postgres=# select pg_last_wal_replay_lsn();

 pg_last_wal_replay_lsn

------------------------

 9/BC0005F0

(1 строка)

postgres=# select pg_last_wal_receive_lsn();

 pg_last_wal_receive_lsn

-------------------------

 9/BC000000

(1 строка)

8) При активной работе рассчитать какое значение LSN больше сложно.

Вычислите, подставив значения, которые реплики:

postgres=# select '9/BC0005F0'::pg_lsn - '9/BC0007B0'::pg_lsn;

 ?column?

----------

     -448

(1 строка)

У первой реплики значения больше, значит она содержит последние изменения.

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

В случае, если синхронной фиксации с подтверждением не было включено, стоит поискать журнальные файлы на мастере или если они повреждены в поточном архиве журналов (заполняемый утилитой pg_receivewal), если он был настроен. При использовании файлов из архива нужно будет скопировать файл текущего журнала. Его легко идентифицировать по суффиксу .partial в названии. При копировании в директорию реплики, который планируется сделать мастером (чтобы реплика накатила файл) нужно убрать суффикс.

9) Рассмотрим случай, когда PGDATA/pg_wal мастера была найдена. Эта директория содержит последние журнальные записи, который сохранил мастер. Скопируем все файлы в директорию PGDATA/pg_wal второй реплики (она не получила последние журнальные записи от мастера):

postgres@tantor:~$ cp /var/lib/postgresql/tantor-se-16/data/pg_wal/* /var/lib/postgresql/tantor-se-16-replica/data2/pg_wal

cp: не указан -r; пропускается каталог '/var/lib/postgresql/tantor-se-16/data/pg_wal/archive_status'

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

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

10) Посмотрим какие журнальные записи применены (процессом startup, который читает директорию pg_wal с журналами и применяет файлы из неё) и были приняты (процессом walreceiver, который принимает журнальные записи и пишет в файлы журналов в директории pg_wal) на второй реплике:

postgres=# select pg_last_wal_replay_lsn();

 pg_last_wal_replay_lsn

------------------------

 9/BC0007B0

(1 строка)

postgres=# select pg_last_wal_receive_lsn();

 pg_last_wal_receive_lsn

-------------------------

 9/BC000000

(1 строка)

По walreceiver изменений нет, мастер остановлен и процесс ничего не мог принять.

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

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

Часть 6. Подготовка к переключению на реплику

Настроим параметры конфигурации бывшего мастера.

Имя слота в параметре primary_slot_name можно задать заранее. После переключения на реплику слоты исчезнут - на новом мастере их не будет.

Параметры соединения primary_conninfo также можно установить заранее. Значение порта укажем на первую реплику 5433, её сделаем матером.

Большая часть параметров, которые относятся к свойствам реплики, не оказывают влияние, пока у кластера роль мастера поэтому значения таких параметров можно устанавливать заранее.

1) Посмотрите содержимое файла параметров бывшего мастера:

postgres@tantor:~$ cat /var/lib/postgresql/tantor-se-16/data/postgresql.auto.conf 

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

listen_addresses = '*'

max_slot_wal_keep_size = '128MB'

max_wal_size = '128MB'

min_wal_size = '512MB'

idle_in_transaction_session_timeout = '100min'

cluster_name = 'master'

2) Установите параметры сетевого соединения, откуда бывший мастер будет забирать журнальные данные:

postgres@tantor:~$ echo "primary_conninfo = 'user=postgres port=5433'" >> /var/lib/postgresql/tantor-se-16/data/postgresql.auto.conf

3) Установим название слота, который он будет использовать:

postgres@tantor:~$ echo "primary_slot_name = 'master'" >> /var/lib/postgresql/tantor-se-16/data/postgresql.auto.conf

4) Чтобы диагностические сообщения выводились на экран терминала:

echo "logging_collector = off" >> /var/lib/postgresql/tantor-se-16/data/postgresql.auto.conf

5) Проверьте, что строки добавились:

postgres@tantor:~$ cat /var/lib/postgresql/tantor-se-16/data/postgresql.auto.conf

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

listen_addresses = '*'

max_slot_wal_keep_size = '128MB'

max_wal_size = '128MB'

min_wal_size = '512MB'

idle_in_transaction_session_timeout = '100min'

cluster_name = 'master'

primary_conninfo = 'user=postgres port=5433'

primary_slot_name = 'master'

logging_collector = off

6) Можно заранее создать неинициализированные слоты репликации на случай если кластер станет мастером на всех репликах-кандидатах.

Создайте слот на второй реплике:

postgres@tantor:~$ psql -p 5434

psql (16.1)

Введите "help", чтобы получить справку.

postgres=# select pg_is_in_recovery();

 pg_is_in_recovery

-------------------

 t

(1 row)

postgres=# select pg_create_physical_replication_slot('master');

 pg_create_physical_replication_slot

-------------------------------------

 (master,)

(1 row)

7) Заранее создадим слот и для первой реплики. Выполните команду:

postgres=# select pg_create_physical_replication_slot('replica1');

 pg_create_physical_replication_slot

-------------------------------------

 (replica1,)

(1 row)

8) Проверьте параметры слотов:

postgres=# select * from pg_replication_slots \gx

-[ RECORD 1 ]-------+----------

slot_name           | master

plugin              |

slot_type           | physical

datoid              |

database            |

temporary           | f

active              | f

active_pid          |

xmin                |

catalog_xmin        |

restart_lsn         |

confirmed_flush_lsn |

wal_status          |

safe_wal_size       | 150994944

two_phase           | f

conflicting         |

-[ RECORD 2 ]-------+----------

slot_name           | replica1

plugin              |

slot_type           | physical

datoid              |

database            |

temporary           | f

active              | f

active_pid          |

xmin                |

catalog_xmin        |

restart_lsn         |

confirmed_flush_lsn |

wal_status          |

safe_wal_size       | 150994944

two_phase           | f

conflicting         |

        На будущем мастере replica1 (порт 5433) слоты создавать заранее не будем в целях практики (пункт 11 этой части практики).

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

Значение safe_wal_size=144Мб=128Мб+16Мб определяет сколько байт может быть записано в журнал, чтобы этот слот не оказался в состоянии lost. Определяется значением параметра max_slot_wal_keep_size=128Мб плюс wal_segment_size=16Мб.

9) Так как бывший мастер остановлен, можно создать файл standby.signal чтобы при запуске экземпляр не открыл бывший мастер в режиме записи. Создайте файл standby.signal в директории бывшего мастера:

postgres@tantor:~$ touch  /var/lib/postgresql/tantor-se-16/data/standby.signal

После создания файла standby.signal можно запустить бывший мастер и потом продвинуть одну из реплик. Или в обратном порядке: продвинуть одну из реплик, а потом запустить бывший мастер. Разницы не будет, если бывший мастер был остановлен и новый мастер получил и применил все журнальные записи (нет потерь транзакций).

10) Запустите бывший мастер:

postgres@tantor:~$ pg_ctl start -D /var/lib/postgresql/tantor-se-16/data

ожидание запуска сервера....

СООБЩЕНИЕ:  запускается PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

[7824] СООБЩЕНИЕ:  для приёма подключений по адресу IPv4 "0.0.0.0" открыт порт 5432

[7824] СООБЩЕНИЕ:  для приёма подключений по адресу IPv6 "::" открыт порт 5432

[7824] СООБЩЕНИЕ:  для приёма подключений открыт Unix-сокет "/var/run/postgresql/.s.PGSQL.5432"

[7827] СООБЩЕНИЕ:  система БД была выключена: 12:40:35 MSK

[7827] СООБЩЕНИЕ:  переход в режим резервного сервера

[7827] СООБЩЕНИЕ:  согласованное состояние восстановления достигнуто в позиции 9/BC0007B0

[7827] СООБЩЕНИЕ:  неверная длина записи в позиции 9/BC0007B0: ожидалось минимум 26, получено 0

[7824] СООБЩЕНИЕ:  система БД готова принимать подключения в режиме "только чтение"

[7828] СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 1

[7828] ОШИБКА:  слот репликации "master" не существует

[7828] STATEMENT:  START_REPLICATION SLOT "master" 9/BC000000 TIMELINE 1

[7828] ВАЖНО:  не удалось начать трансляцию WAL: ОШИБКА:  слот репликации "master" не существует

[7828] СООБЩЕНИЕ:  waiting for WAL to become available at 9/BC0007CA

 готово

сервер запущен

Экземпляр бывшего мастера запустился и успешно вошел в режим ожидания запуска репликации. Ошибки указывают на то, что слот с именем master не существует. Мы его заранее не создавали (в пункте 8 этой части практики) в целях получить эту ошибку и исправить её - создать слот после запуска бывшего мастера.

11) Подключитесь к первой реплике replica1 (порт 5433) и создайте слоты репликации:

postgres=# \c postgres postgres /var/run/postgresql 5433

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5433".

postgres=# select pg_create_physical_replication_slot('master');

 pg_create_physical_replication_slot

-------------------------------------

 (master,)

(1 row)

postgres=# select pg_create_physical_replication_slot('replica2');

 pg_create_physical_replication_slot

-------------------------------------

 (replica2,)

(1 row)

 

Бывший master автоматически станет использовать созданный слот.

В сообщениях экземпляра master появится:

СООБЩЕНИЕ:  waiting for WAL to become available at 9/BC0007CA

СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 1

12) Проверьте на replica1 (порт 5433) статус слотов репликации:

postgres=# select slot_name, active, restart_lsn, wal_status from pg_replication_slots;

 slot_name | active | restart_lsn | wal_status

-----------+--------+-------------+------------

 master    | t      | 9/BC0007B0  | reserved

 replica2  | f      |             |

(2 строки)

13) Проверьте статистику слотов репликации на бывшем мастере (master):

postgres=# \c postgres postgres /var/run/postgresql 5432

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5432".

postgres=# select slot_name, active, restart_lsn, wal_status from pg_replication_slots;

 slot_name  | active | restart_lsn | wal_status

------------+--------+-------------+------------

 pgstandby1 | f      | 0/19187E70  | lost

 replica1   | t      | 9/BC0007B0  | reserved

 replica2   | t      | 9/BC0007B0  | reserved

(3 строки)

postgres=# select * from pg_stat_replication \gx

-[ RECORD 1 ]----+------------------------------

pid              | 18624

usesysid         | 10

usename          | postgres

application_name | replica1

client_addr      |

client_hostname  |

client_port      | -1

backend_start    | 16:07:37.4+03

backend_xmin     |

state            | streaming

sent_lsn         | 9/BC0007B0

write_lsn        | 9/BC0007B0

flush_lsn        | 9/BC0007B0

replay_lsn       | 9/BC0007B0

write_lag        |

flush_lag        |

replay_lag       |

sync_priority    | 0

sync_state       | async

reply_time       | 16:31:37.866881+03

-[ RECORD 2 ]----+------------------------------

pid              | 18693

usesysid         | 10

usename          | postgres

application_name | replica2

client_addr      |

client_hostname  |

client_port      | -1

backend_start    | 16:07:52.385223+03

backend_xmin     |

state            | streaming

sent_lsn         | 9/BC0007B0

write_lsn        | 9/BC0007B0

flush_lsn        | 9/BC0007B0

replay_lsn       | 9/BC0007B0

write_lag        |

flush_lag        |

replay_lag       |

sync_priority    | 0

sync_state       | async

reply_time       | 16:31:32.847758+03

Оказывается будущий мастер replica1 и replica2 подсоединены к прежнему мастеру. Время reply_time текущее. Ни один из кластеров мы пока не продвинули до мастера - все три кластера физические реплики.

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

ОПЕРАТОР:  SELECT slot_name, database, slot_type, xmin::text::int8, active, pg_wal_lsn_diff(pg_current_wal_insert_lsn(), restart_lsn) AS retained_bytes FROM pg_replication_slots LIMIT 50 OFFSET 0;

ОШИБКА:  идёт процесс восстановления

ПОДСКАЗКА:  Функции управления WAL нельзя использовать в процессе восстановления.

14) Проверьте статистику слотов репликации на replica1 к которому подсоединен бывший master:

postgres=# \c postgres postgres /var/run/postgresql 5433

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5433".

postgres=# select * from pg_stat_replication \gx

-[ RECORD 1 ]----+------------------------------

pid              | 20280

usesysid         | 10

usename          | postgres

application_name | master

client_addr      |

client_hostname  |

client_port      | -1

backend_start    | 16:11:07.446672+03

backend_xmin     |

state            | streaming

sent_lsn         | 9/BC0007B0

write_lsn        | 9/BC0007B0

flush_lsn        | 9/BC0007B0

replay_lsn       | 9/BC0007B0

write_lag        |

flush_lag        |

replay_lag       |

sync_priority    | 0

sync_state       | async

reply_time       | 16:39:18.004533+03

Время reply_time текущее.

15) Посмотрите списки процессов экземпляров:

postgres@tantor:~$ ps -o pid,command --ppid `head -n 1 /var/lib/postgresql/tantor-se-16/data/postmaster.pid`

  PID COMMAND

18615 postgres: master: checkpointer

18616 postgres: master: background writer

18617 postgres: master: startup recovering 0000000100000009000000BC

18624 postgres: master: walsender postgres [local] streaming 9/BC0007B0

18693 postgres: master: walsender postgres [local] streaming 9/BC0007B0

20279 postgres: master: walreceiver

postgres@tantor:~$ ps -o pid,command --ppid `head -n 1 /var/lib/postgresql/tantor-se-16-replica/data1/postmaster.pid`

  PID COMMAND

18622 postgres: replica1: walreceiver 

20280 postgres: replica1: walsender postgres [local] streaming 9/BC0007B0

25551 postgres: replica1: checkpointer

25552 postgres: replica1: background writer

25553 postgres: replica1: startup recovering 0000000100000009000000BC

postgres@tantor:~$ ps -o pid,command --ppid `head -n 1 /var/lib/postgresql/tantor-se-16-replica/data2/postmaster.pid`

  PID COMMAND

18692 postgres: replica2: walreceiver 

24185 postgres: replica2: checkpointer

24186 postgres: replica2: background writer

24187 postgres: replica2: startup recovering 0000000100000009000000BC

Текущее состояние: replica1 забирает журналы с master. master забирает журналы с replica1. replica2 забирает журналы c master. Все кластера в режиме восстановления (физические реплики).

Часть 7. Переключение на реплику

Как реплику сделать мастером? Можно продвинуть replica1 до мастера командой:

a) pg_ctl promote -D /var/lib/postgresql/tantor-se-16-replica/data1

б) вызвав функцию psql -p 5433 -c "select pg_promote();"

Можно выбрать любой способ.

1) Продвиньте replica1 до мастера:

postgres@tantor:~$ psql -p 5433 -c "select pg_promote();"

 pg_promote

------------

 t

(1 row)

Сообщения в логах кластеров:

[25553] СООБЩЕНИЕ:  получен запрос повышения статуса

[18622] ВАЖНО:  завершение процесса считывания журнала по команде администратора

[25553] СООБЩЕНИЕ:  записи REDO обработаны до смещения 9/BC000718, нагрузка системы: CPU: пользов.: 0.94 с, система: 1.13 с, прошло: 104038.32 с

[25553] СООБЩЕНИЕ:  выбранный ID новой линии времени: 2

[25553] СООБЩЕНИЕ:  восстановление архива завершено

[25551] СООБЩЕНИЕ:  начата контрольная точка: force

[20279] СООБЩЕНИЕ:  репликация прекращена главным сервером

[20279] ПОДРОБНОСТИ:  На линии времени 1 в 9/BC0007B0 достигнут конец журнала.

[20279] СООБЩЕНИЕ:  загрузка файла истории для линии времени 2 с главного сервера

[20279] ВАЖНО:  завершение процесса считывания журнала по команде администратора

[18617] СООБЩЕНИЕ:  новая целевая линия времени 2

[25550] СООБЩЕНИЕ:  система БД готова принимать подключения

[25551] СООБЩЕНИЕ:  контрольная точка завершена: записано буферов: 2 (0.0%); добавлено файлов WAL 0, удалено: 0, переработано: 0; запись=0.002 сек., синхр.=0.001 сек., всего=0.014 сек.; синхронизировано_файлов=2, самая_долгая_синхр.=0.001 сек., средняя=0.001 сек.; расстояние=0 kB, ожидалось=0 kB; lsn=9/BC000828, lsn redo=9/BC0007E0

[4727] СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 2

[18617] СООБЩЕНИЕ:  запись REDO начинается со смещения 9/BC0007B0

[18692] СООБЩЕНИЕ:  репликация прекращена главным сервером

[18692] ПОДРОБНОСТИ:  На линии времени 1 в 9/BC0007B0 достигнут конец журнала.

[18692] СООБЩЕНИЕ:  загрузка файла истории для линии времени 2 с главного сервера

[18692] ВАЖНО:  завершение процесса считывания журнала по команде администратора

[24187] СООБЩЕНИЕ:  новая целевая линия времени 2

[4730] СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 2

2) Посмотрите статус слотов репликации:

postgres@tantor:~$ psql -p 5433

postgres=# select slot_name, active, restart_lsn, wal_status from pg_replication_slots;

 slot_name | active | restart_lsn  | wal_status

-----------+--------+--------------+------------

 master    | t      | 9/BC000908   | reserved

 replica2  | f      |              |

(2 строки)

postgres=# select * from pg_stat_replication \gx

-[ RECORD 1 ]----+------------------------------

pid              | 4729

usesysid         | 10

usename          | postgres

application_name | master

client_addr      |

client_hostname  |

client_port      | -1

backend_start    | 19:31:35.37509+03

backend_xmin     |

state            | streaming

sent_lsn         | 9/BC000908

write_lsn        | 9/BC000908

flush_lsn        | 9/BC000908

replay_lsn       | 9/BC000908

write_lag        |

flush_lag        |

replay_lag       |

sync_priority    | 0

sync_state       | async

reply_time       | 19:40:25.699932+03

postgres=# \c postgres postgres /var/run/postgresql 5432

Вы подключены к базе данных "postgres" как пользователь "postgres" через сокет в "/var/run/postgresql", порт "5432".

postgres=# select slot_name, active, restart_lsn, wal_status from pg_replication_slots;

 slot_name | active | restart_lsn  | wal_status

-----------+--------+--------------+------------

 pgstandby1 | f      | 0/19187E70  | lost

 replica1   | f      | 9/BC0007B0  | reserved

 replica2   | t      | 9/BC000908  | reserved

(3 строки)

postgres=# select * from pg_stat_replication \gx

-[ RECORD 1 ]----+------------------------------

pid              | 4731

usesysid         | 10

usename          | postgres

application_name | replica2

client_addr      |

client_hostname  |

client_port      | -1

backend_start    | 19:31:35.411578+03

backend_xmin     |

state            | streaming

sent_lsn         | 9/BC000908

write_lsn        | 9/BC000908

flush_lsn        | 9/BC000908

replay_lsn       | 9/BC000908

write_lag        |

flush_lag        |

replay_lag       |

sync_priority    | 0

sync_state       | async

reply_time       | 19:45:05.821085+03

Новый мастер replica1 передает журнальные данные физической реплике master. Физическая реплика master передает журнальные данные физической реплике replica1.

В списке слотов бывшего мастера присутствует слот replica1, который был инициализирован пока он был мастером. Названия слотов не зависят от названий кластеров. Кластера неотличимы друг от друга и бывший мастер не может сопоставить то, что слот replica1 использовался новым мастером. Это слот заставит master удерживать журнальные файлы для репликационного клиента, который вряд ли подсоединится, так как он теперь мастер.

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

3) При продвижении replica1 до мастера линия времени увеличилась на единицу. Это отражается в управляющих файлах и названиях файлов журналов. Также в директориях PGDATA/pg_wal кластеров были созданы текстовые файлы 00000002.history в названии которых имеется номер линии времени.

Посмотрите содержимое файла истории:

postgres@tantor:~$ cat /var/lib/postgresql/tantor-se-16-replica/data1/pg_wal/*.history

1       9/BC0007B0      no recovery target specified

4) Посмотрите линию времени в управляющем файле реплики master (у остальных кластеров то же самое):

postgres@tantor:~$ pg_controldata | grep timeline

postgres@tantor:~$ pg_controldata | grep времени

Линия времени последней конт. точки:  2

Пред. линия времени последней к. т.:  2

Линия времени мин. положения к. в.:   2

Формат хранения даты/времени:         64-битные целые

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

5) Неиспользуемые инициализированные слоты репликации всегда нужно удалять.

Иначе эти слоты будут удерживать журналы до достижения max_slot_wal_keep_size, статус слота сменится на unreserved. Если после контрольной точки (файлы удаляются после прохождения контрольной точки) файлы журналов не удалятся благодаря удержанию параметром wal_keep_size, то статус лота изменится на extended. Если удалятся, то статус слота изменится на lost и слот станет бесполезен.

Удалите слоты, которые не будем использовать:

postgres=# \c postgres postgres /var/run/postgresql 5432

Вы подключены к базе данных "postgres" как пользователь "postgres".

postgres=# select pg_drop_replication_slot('replica1');

 pg_drop_replication_slot

--------------------------

 

(1 строка)

postgres=# select pg_drop_replication_slot('pgstandby1');

 pg_drop_replication_slot

--------------------------

 

(1 строка)

6) Заранее создадим неинициализированный слот репликации для следующей смены ролей:

postgres=# select pg_create_physical_replication_slot('replica1');

 pg_create_physical_replication_slot

-------------------------------------

 (replica1,)

(1 строка)

7) Проверьте список слотов:

postgres=# select slot_name, active, restart_lsn, wal_status from pg_replication_slots;

 slot_name | active | restart_lsn  | wal_status

-----------+--------+--------------+------------

 replica1  | f      |             |

 replica2  | t      | 9/BC000908  | reserved

(2 строки)

Слот replica1 не инициализирован и не будет удерживать журналы.

Часть 8. Включение обратной связи

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

Установите значение параметра hot_standby_feedback=on на реплике master:

postgres=# \c postgres postgres /var/run/postgresql 5432

Вы подключены к базе данных "postgres" как пользователь "postgres".

postgres=# alter system set hot_standby_feedback = on;

ALTER SYSTEM

postgres=# select pg_reload_conf();

 pg_reload_conf

----------------

 t

(1 строка)

2) Протестировать обратную связь можно открыв транзакцию на реплике master:

postgres=# begin transaction isolation level repeatable read;

BEGIN

postgres=*# select count(*) from pg_class;

 count

-------

   423

(1 строка)

Транзакция началась в момент начала выполнения select. Таблица, к которой обратился select может быть любой.

3) В самих репликах искать процессы, выполняющие команды, удерживающие горизонт можно так же, как и на мастере - запросом к pg_stat_activity.  Выполните на реплике запрос:

postgres=*# SELECT backend_xmin, backend_xid, pid, datname, state FROM pg_stat_activity WHERE backend_xmin IS NOT NULL OR backend_xid IS NOT NULL ORDER BY greatest(age(backend_xmin), age(backend_xid)) DESC;

 backend_xmin | backend_xid | pid  | datname  | state  

--------------+-------------+------+----------+--------

        17580 |             | 4117 | postgres | active

(1 строка)

4) В другом окне терминала, в другой сессии к кластеру replica1 являющемуся мастером:

postgres@tantor:~$ psql -p 5433

postgres=# select slot_name, active, active_pid, xmin from pg_replication_slots;

 slot_name | active | active_pid | xmin  

-----------+--------+------------+-------

 master    | t      |       4729 | 17580

 replica2  | f      |            |      

(2 строки)

Реплика удерживает горизонт всех баз данных кластера (на мастере не могут удаляться вакуумом старые версии строк) на xid=17580

5) Получите номер транзакции:

postgres=# select pg_current_xact_id();

 pg_current_xact_id

--------------------

              17580

(1 строка)

6) Можно выполнить транзакции, но достаточно просто увеличить счетчик транзакций.

Получите номер транзакции и счетчик транзакций увеличится:

postgres=# select pg_current_xact_id();

 pg_current_xact_id

--------------------

              17581

(1 строка)

7) Выполните на мастере запрос, который покажет, какой серверный процесс экземпляра мастера удерживает горизонт:

postgres=# SELECT backend_xmin, backend_xid, pid, datname, state FROM pg_stat_activity WHERE backend_xmin IS NOT NULL OR backend_xid IS NOT NULL ORDER BY greatest(age(backend_xmin), age(backend_xid)) DESC;

 backend_xmin | backend_xid | pid  | datname  | state  

--------------+-------------+------+----------+--------

        17582 |             | 6562 | postgres | active

(1 row)

pg_stat_activity показывает только процессы своего экземпляра.

8) Выполните команду:

postgres=# select slot_name, active, active_pid, xmin from pg_replication_slots;

 slot_name | active | active_pid | xmin  

-----------+--------+------------+-------

 master    | t      |       4729 | 17580

 replica2  | f      |            |      

(2 строки)

Горизонт всех баз данных мастера (текущий мастер это replica1) удерживается на xid=17580.

9) Подсоединитесь к реплике и посмотрите данные о процессах реплики:

postgres=# \c postgres postgres /var/run/postgresql 5432

You are now connected to database "postgres" as user "postgres" via socket in "/var/run/postgresql" at port "5432".

postgres=# SELECT backend_xmin, backend_xid, pid, datname, state FROM pg_stat_activity WHERE backend_xmin IS NOT NULL OR backend_xid IS NOT NULL ORDER BY greatest(age(backend_xmin), age(backend_xid)) DESC;

 backend_xmin | backend_xid | pid  | datname  |        state        

--------------+-------------+------+----------+---------------------

        17580 |             | 4117 | postgres | idle in transaction

        17582 |             | 7692 | postgres | active

(2 rows)

4117 - pid серверного процесса, в котором открыта транзакция.

7692 - pid серверного процесса на master, в котором выполнялся этот запрос. 17582 означает, что этот серверный процесс выдаёт актуальные данные (в соответствии с изменениями, приехавшими с мастера и примененными к реплике).

7) Завершите открытую транзакцию в том окне, где она открыта:

postgres=*# commit;

COMMIT

8) В пределах 10 секунд (значение параметра walreceiver_status_interval) xmin на replica1 перестанет удерживаться и xmin увеличится:

postgres=# select slot_name, active, active_pid, xmin from pg_replication_slots;

 slot_name | active | active_pid | xmin  

-----------+--------+------------+-------

 master    | t      |       4729 | 17582

 replica2  | f      |            |      

(2 строки)

4729 - pid walsender на replica1. Это можно проверить командой:

postgres@tantor:~$ ps -ef | grep 4729

postgres  4729 25550 postgres: replica1: walsender postgres [local] streaming 9/BC0017B8


Часть 9. Утилита pg_rewind

        1) Остановите replica1:

postgres@tantor:~$ pg_ctl stop -D /var/lib/postgresql/tantor-se-16-replica/data1

ожидание завершения работы сервера...

[25550] СООБЩЕНИЕ:  получен запрос на быстрое выключение

[25550] СООБЩЕНИЕ:  прерывание всех активных транзакций

[25550] СООБЩЕНИЕ:  фоновый процесс "logical replication launcher" (PID 4728) завершился с кодом выхода 1

[25551] СООБЩЕНИЕ:  выключение

[25551] СООБЩЕНИЕ:  начата контрольная точка: shutdown immediate

[25551] СООБЩЕНИЕ:  контрольная точка завершена: записано буферов: 0 (0.0%); добавлено файлов WAL 0, удалено: 0, переработано: 0; запись=0.001 сек., синхр.=0.001 сек., всего=0.012 сек.; синхронизировано_файлов=0, самая_долгая_синхр.=0.000 сек., средняя=0.000 сек.; расстояние=0 kB, ожидалось=0 kB; lsn=9/BC001950, lsn redo=9/BC001950

[25550] СООБЩЕНИЕ:  система БД выключена

 готово

сервер остановлен

        2) Подсоединитесь к master (порт 5432) и повысьте его до мастера:

postgres@tantor:~$ psql

postgres=# select pg_promote();

LOG:  replication terminated by primary server

DETAIL:  End of WAL reached on timeline 2 at 9/BC001A18.

LOG:  fetching timeline history file for timeline 3 from primary server

FATAL:  terminating walreceiver process due to administrator command

LOG:  new target timeline is 3

СООБЩЕНИЕ:  неверная длина записи в позиции 9/BC001B40: ожидалось минимум 26, получено 0

СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 3

 pg_promote

------------

 t

(1 строка)

3) Сейчас master является мастером, к нему подсоединен replica2 которая не останавливалась и принимает журнальные данные.

Мы корректно остановили replica1 до продвижения нового мастера. Можем ли мы запустить replica1 или нужно ещё что-то сделать?

В демонстрационных целях запустим и остановим replica1. Это эквивалентно тому как если бы мы забыли остановить replica1 до продвижения master или тому как если бы экземпляр replica1 некорректно остановился и не успел бы передать последнюю журнальную запись на master.

postgres@tantor:~$ pg_ctl start -D /var/lib/postgresql/tantor-se-16-replica/data1

ожидание запуска сервера....

СООБЩЕНИЕ:  запускается PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

СООБЩЕНИЕ:  для приёма подключений по адресу IPv4 "0.0.0.0" открыт порт 5433

СООБЩЕНИЕ:  для приёма подключений по адресу IPv6 "::" открыт порт 5433

СООБЩЕНИЕ:  для приёма подключений открыт Unix-сокет "/var/run/postgresql/.s.PGSQL.5433"

СООБЩЕНИЕ:  система БД была выключена:

СООБЩЕНИЕ:  система БД готова принимать подключения

 готово

сервер запущен

4) Остановите replica1:

postgres@tantor:~$ pg_ctl stop -D /var/lib/postgresql/tantor-se-16-replica/data1

ожидание завершения работы сервера....

СООБЩЕНИЕ: получен запрос на быстрое выключение

СООБЩЕНИЕ: прерывание всех активных транзакций

СООБЩЕНИЕ: фоновый процесс "logical replication launcher" (PID 27234) завершился с кодом выхода 1

СООБЩЕНИЕ: выключение

СООБЩЕНИЕ: начата контрольная точка: shutdown immediate

СООБЩЕНИЕ: контрольная точка завершена: записано буферов: 3 (0.0%); добавлено файлов WAL 0, удалено: 0, переработано: 0; запись=0.001 сек., синхр.=0.001 сек., всего=0.004 сек.; синхронизировано_файлов=2, самая_долгая_синхр.=0.001 сек., средняя=0.001 сек.; расстояние=0 kB, ожидалось=0 kB; lsn=9/BC001A30, lsn redo=9/BC001A30

СООБЩЕНИЕ: система БД выключена

 готово

сервер остановлен

5) Перед запуском мы не создали файл standby.signal и кластер запустился с ролью мастера.

Создайте файл standby.signal:

postgres@tantor:~$ touch /var/lib/postgresql/tantor-se-16-replica/data1/standby.signal

Но уже поздно: при остановке была выполнена контрольная точка и создана журнальная запись на линии времени 2.

Если сейчас снова запустить экземпляр replica1, то master будет отказывать ему в доступе, replica1 будет переподсоединяться к master без задержки и безостановочно писать сообщения в диагностический лог. Пример таких сообщений:

СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 2

СООБЩЕНИЕ:  репликация прекращена главным сервером

ПОДРОБНОСТИ:  На линии времени 2 в 9/BC0019E8 достигнут конец журнала.

ВАЖНО:  завершение процесса считывания журнала по команде администратора

СООБЩЕНИЕ:  новая линия времени 3 ответвилась от текущей линии времени базы данных 2 до текущей точки восстановления 9/BC001AC8

СООБЩЕНИЕ:  waiting for WAL to become available at 9/BC001AE2

В таком случае можно попробовать использовать утилиту pg_rewind

6) Дайте команду:

postgres@tantor:~$ pg_rewind -D /var/lib/postgresql/tantor-se-16-replica/data1 --source-server='user=postgres port=5432' -R -P

pg_rewind: подключение к серверу установлено

pg_rewind: ошибка: на целевом сервере должны быть контрольные суммы данных или "wal_log_hints = on"

Если утилита не выдала ошибку о том, что на replica1 не включен подсчёт контрольных сумм (как и на остальных кластерах) и успешно выполнилась, то можно перейти к запуску экземпляра replica1.

7) Убедитесь, что подсчета контрольных сумм нет:

postgres@tantor:~$ pg_checksums -D /var/lib/postgresql/tantor-se-16-replica/data1           

pg_checksums: ошибка: контрольные суммы в кластере не включены

8) Включите подсчёт контрольных сумм:

postgres@tantor:~$ pg_checksums -e -D /var/lib/postgresql/tantor-se-16-replica/data1

Если утилита выдала ошибку типа:

pg_checksums: ошибка: неверный номер сегмента 0 в имени файла "/var/lib/postgresql/tantor-se-16-replica/data1/global/pg_store_plans.stat"

Это означает, то в файле табличного пространства присутствует файл, который не должен там находиться. Ошибки связанные с присутствием неизвестных файлов в PGDATA возможны. В этом примере это файл неизвестного формата:

pg_store_plans.c

/* Location of stats file */

#define PGSP_DUMP_FILE        "global/pg_store_plans.stat"

9) В любом случае утилита pg_rewind скопирует нужные файлы с мастера, поэтому удалите  файл из-за которого не включается подсчет контрольных сумм на блоках файлов данных:

postgres@tantor:~$ rm /var/lib/postgresql/tantor-se-16-replica/data1/global/pg_store_plans.stat

10) Повторите команду включения подсчета контрольных сумм:

postgres@tantor:~$ pg_checksums -e -D /var/lib/postgresql/tantor-se-16-replica/data1

Обработка контрольных сумм завершена

Просканировано файлов: 1913

Просканировано блоков: 54747

Записано файлов: 1563

Записано блоков: 54701

pg_checksums: синхронизация каталога данных

pg_checksums: модификация управляющего файла

Контрольные суммы в кластере включены

11) Запустите pg_rewind ещё раз:

postgres@tantor:~$ pg_rewind -D /var/lib/postgresql/tantor-se-16-replica/data1 --source-server='user=postgres port=5432' -R -P

pg_rewind: подключение к серверу установлено

pg_rewind: серверы разошлись в позиции WAL 9/BC0019E8 на линии времени 2

pg_rewind: перемотка от последней общей контрольной точки в позиции 9/BC001950 на линии времени 2

pg_rewind: чтение списка исходных файлов

pg_rewind: чтение списка целевых файлов

pg_rewind: чтение WAL в целевом кластере

pg_rewind: требуется скопировать 194 МБ (общий размер исходного каталога: 615 МБ)

199097/199097 КБ (100%) скопировано

pg_rewind: создание метки копии и модификация управляющего файла

pg_rewind: синхронизация целевого каталога данных

pg_rewind: Готово!

Можно ли запускать экземпляр кластера? Нельзя. Утилита pg_rewind синхронизировала все файлы с мастером, в том числе файлы параметров конфигурации и они содержат настройки мастера.

До запуска утилиты pg_rewind стоит сохранять файлы параметров, которые лежат в PGDATA.

12) Отредактируйте файл posgresql.auto.conf, приведя его к такому виду:

postgres@tantor:~$ cat /var/lib/postgresql/tantor-se-16-replica/data1/postgresql.auto.conf

# Do not edit this file manually!

# It will be overwritten by the ALTER SYSTEM command.

listen_addresses = '*'

max_slot_wal_keep_size = '128MB'

max_wal_size = '128MB'

min_wal_size = '512MB'

idle_in_transaction_session_timeout = '100min'

cluster_name = 'replica1'

primary_slot_name = 'replica1'

logging_collector = 'off'

hot_standby_feedback = 'on'

primary_conninfo = 'user=postgres port=5432'

wal_retrieve_retry_interval = '30s'

port = 5433

13) Запустите экземпляр replica1:

postgres@tantor:~$ pg_ctl start -D /var/lib/postgresql/tantor-se-16-replica/data1

ожидание запуска сервера....

[18861] СООБЩЕНИЕ:  запускается PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (AstraLinuxSE 8.3.0-6) 8.3.0, 64-bit

[18861] СООБЩЕНИЕ:  для приёма подключений по адресу IPv4 "0.0.0.0" открыт порт 5433

[18861] СООБЩЕНИЕ:  для приёма подключений по адресу IPv6 "::" открыт порт 5433

[18861] СООБЩЕНИЕ:  для приёма подключений открыт Unix-сокет "/var/run/postgresql/.s.PGSQL.5433"

[18864] СООБЩЕНИЕ:  работа системы БД была прервана в процессе восстановления, время в журнале:

[18864] ПОДСКАЗКА:  Если это происходит постоянно, возможно, какие-то данные были испорчены и для восстановления стоит выбрать более раннюю точку.

[18864] СООБЩЕНИЕ:  переход в режим резервного сервера

[18864] СООБЩЕНИЕ:  запись REDO начинается со смещения 9/BC0019E8

[18864] СООБЩЕНИЕ:  согласованное состояние восстановления достигнуто в позиции 9/BC001D48

[18864] СООБЩЕНИЕ:  неверная длина записи в позиции 9/BC001D48: ожидалось минимум 26, получено 0

[18861] СООБЩЕНИЕ:  система БД готова принимать подключения в режиме "только чтение"

[18865] СООБЩЕНИЕ:  начало передачи журнала с главного сервера, с позиции 9/BC000000 на линии времени 3

 готово

сервер запущен

14) Проверьте статистику репликации на мастере:

postgres@tantor:~$ psql

postgres=# select * from pg_stat_replication \gx

-[ RECORD 1 ]----+------------------------------

pid              | 23531

usesysid         | 10

usename          | postgres

application_name | replica2

client_addr      |

client_hostname  |

client_port      | -1

backend_start    | 12:32:18.89956+03

backend_xmin     |

state            | streaming

sent_lsn         | 9/BC001D48

write_lsn        | 9/BC001D48

flush_lsn        | 9/BC001D48

replay_lsn       | 9/BC001D48

write_lag        |

flush_lag        |

replay_lag       |

sync_priority    | 0

sync_state       | async

reply_time       | 13:17:04.270387+03

-[ RECORD 2 ]----+------------------------------

pid              | 18866

usesysid         | 10

usename          | postgres

application_name | replica1

client_addr      |

client_hostname  |

client_port      | -1

backend_start    | 13:13:42.353704+03

backend_xmin     |

state            | streaming

sent_lsn         | 9/BC001D48

write_lsn        | 9/BC001D48

flush_lsn        | 9/BC001D48

replay_lsn       | 9/BC001D48

write_lag        |

flush_lag        |

replay_lag       |

sync_priority    | 0

sync_state       | async

reply_time       | 13:17:02.430073+03

Обе реплики принимают журнальные данные и накатывают их.

Напоминание: контрольные суммы включены только на replica1. Обратная связь (hot_standby_feedback = 'on') включена на master и replica1.


Логическая репликация

  1. Репликация таблицы
  2. Репликация без первичного ключа
  3. Добавление таблицы в публикацию
  4. Двунаправленная репликация

Часть 1. Репликация таблицы

1) Повысьте реплику на порту 5433 до мастера:

postgres@tantor:~$ psql -p 5433 -c "select pg_promote()"

 pg_promote

------------

 t

(1 строка)

Сейчас два мастера на портах 5432 и 5433. У мастера на порту 5432 есть реплика на порту 5434.

Пример сообщений в логе о повышении реплики:

[18864] СООБЩЕНИЕ:  получен запрос повышения статуса

[9445] ВАЖНО:  завершение процесса считывания журнала по команде администратора

[18864] СООБЩЕНИЕ:  неверная длина записи в позиции 9/BC09D2D0: ожидалось минимум 26, получено 0

[18864] СООБЩЕНИЕ:  записи REDO обработаны до смещения 9/BC09D288, нагрузка системы: CPU: пользов.: 0.09 с, система: 0.11 с, прошло: 17564.43 с

[18864] СООБЩЕНИЕ:  последняя завершённая транзакция была выполнена в 18:03:37.805708+03

[18864

5) Проверьте, что строки добавились:

] СООБЩЕНИЕ:  выбранный ID новой линии времени: 4

[18864] СООБЩЕНИЕ:  восстановление архива завершено

[18862] СООБЩЕНИЕ:  начата контрольная точка: force

[18861] СООБЩЕНИЕ:  система БД готова принимать подключения

[18862] СООБЩЕНИЕ:  контрольная точка завершена: записано буферов: 7 (0.0%); добавлено файлов WAL 0, удалено: 0, переработано: 0; запись=0.504 сек., синхр.=0.004 сек., всего=0.510 сек.; синхронизировано_файлов=7, самая_долгая_синхр.=0.002 сек., средняя=0.001 сек.; расстояние=29 kB, ожидалось=159 kB; lsn=9/BC09D3C8, lsn redo=9/BC09D338

2) Удалите слот репликации replica1 в кластере master (порт 5432) который использовался replica1 (порт 5433) и уже не нужен:

psql -p 5432 -c "select slot_name, slot_type, active, restart_lsn, wal_status from pg_replication_slots"

 slot_name | slot_type | active | restart_lsn | wal_status

-----------+-----------+--------+-------------+------------

 replica1  | physical  | f      | 9/BC09D2D0  | reserved

 replica2  | physical  | t      | 9/BC09D3F8  | reserved

(2 строки)

psql -p 5432 -c "select pg_drop_replication_slot('replica1')"

 pg_drop_replication_slot

--------------------------

 

(1 строка)

psql -p 5432 -c "select slot_name, slot_type, active, restart_lsn, wal_status from pg_replication_slots"

 slot_name | slot_type | active | restart_lsn | wal_status

-----------+-----------+--------+-------------+------------

 replica2  | physical  | t      | 9/BC09D3F8  | reserved

(1 строка)

На replica2 есть два неинициализированных слота репликации:

psql -p 5434 -c "select slot_name, slot_type, active, restart_lsn, wal_status from pg_replication_slots"

 slot_name | slot_type | active | restart_lsn | wal_status

-----------+-----------+--------+-------------+------------

 master    | physical  | f      |             |

 replica1  | physical  | f      |             |

(2 строки)

3) Проверьте какие таблицы в базе postgres на порту 5432 не имеют репликационного идентификатора:

 psql -p 5432 -c "SELECT relnamespace::regnamespace||'.'||relname "table"

 FROM pg_class

 WHERE relreplident IN ('d','n') -- d первичный ключ, n никакие

  AND relkind IN ('r','p') -- r таблица, p секционированная

  AND oid NOT IN (SELECT indrelid FROM pg_index WHERE indisprimary)

  AND relnamespace <> 'pg_catalog'::regnamespace

  AND relnamespace <> 'information_schema'::regnamespace

ORDER BY 1"

  table  

----------

 public.demo2

 public.hypo

 utl_file.utl_file_dir

(3 строки)

4) Удалите таблицы demo2, t если они существуют:

psql -p 5432 -c "drop table if exists t"

NOTICE:  table "t" does not exist, skipping

DROP TABLE

psql -p 5432 -c "drop table if exists demo2"

DROP TABLE

5) Создайте таблицу которую будем реплицировать и вставьте строку:

psql -p 5432 -c "create table t (id bigserial PRIMARY KEY, t text)"

CREATE TABLE

psql -p 5432 -c "insert into t (t) values ('a')"

INSERT 0 1

6) Создайте определение таблицы-приемника в базе postgres кластера на порту 5433:

pg_dump -t t --schema-only --clean --if-exists | psql -p 5433

Этот шаг обязательный, структуру таблицы в которую будут реплицироваться изменения нужно создавать отдельно, так как в функционале логической репликации нет автоматического создания таблиц. Имя таблицы и столбцы должны иметь те же названия. Порядок столбцов не важен, могут быть дополнительные столбцы, наличие которых не мешало бы вставлять строки. Препятствие для вставки строк: наличие ограничения целостности NOT NULL в отсутствие значения по умолчанию DEFAULT.

7) Установите wal_level=logical на всех кластерах;

установите hot_standby_feedback=on на replica2, он не был установлен в предыдущей практике. Включение обратной связи обязательно для логической репликации, в противном случае при изменении определения и набора таблиц в публикации (то, что меняет строки в таблицах системного каталога, которые хранят данные о свойствах реплицируемых таблиц) могут возникать ошибки "Этот слот был аннулирован из-за конфликта с восстановлением".

Установите checkpoint_timeout='30min', чтобы контрольные точки и точки рестарат (по умолчанию раз в 5 минут) не писали сообщения в логи кластеров затрудняя чтение сообщений логической репликации:

psql -p 5432 -c "ALTER SYSTEM SET wal_level=logical"

psql -p 5433 -c "ALTER SYSTEM SET wal_level=logical"

psql -p 5434 -c "ALTER SYSTEM SET wal_level=logical"

psql -p 5434 -c "ALTER SYSTEM SET hot_standby_feedback=on"

psql -p 5432 -c "alter system set checkpoint_timeout='30min'"

psql -p 5433 -c "alter system set checkpoint_timeout='30min'"

psql -p 5434 -c "alter system set checkpoint_timeout='30min'"

Изменение параметра wal_level требует рестарта экземпляров. Убедитесь в этом:

psql -p 5432 -c "select pg_reload_conf()"

 pg_reload_conf

----------------

 t

(1 строка)

psql -p 5432 -c "select * from pg_settings where name = 'wal_level'" -x

-[ RECORD 1 ]---+--------------------------------------------------

name            | wal_level

setting         | replica

unit            |

category        | Write-Ahead Log / Settings

short_desc      | Sets the level of information written to the WAL.

extra_desc      |

context         | postmaster

vartype         | enum

source          | default

min_val         |

max_val         |

enumvals        | {minimal,replica,logical}

boot_val        | replica

reset_val       | replica

sourcefile      |

sourceline      |

pending_restart | t

8) Остановите и запустите экземпляры в окне (окнах) терминала, в котором (которых) хотите получать диагностические сообщения:

pg_ctl stop -D /var/lib/postgresql/tantor-se-16-replica/data2

pg_ctl stop -D /var/lib/postgresql/tantor-se-16-replica/data1

pg_ctl stop -D /var/lib/postgresql/tantor-se-16/data

pg_ctl start -D /var/lib/postgresql/tantor-se-16/data

pg_ctl start -D /var/lib/postgresql/tantor-se-16-replica/data1

pg_ctl start -D /var/lib/postgresql/tantor-se-16-replica/data2

Можно использовать несколько окон терминала, чтобы было удобнее видеть от какого экземпляра какие сообщения выводятся. В одном окне терминала сложно отличить сообщения от разных экземпляров. Три терминала удобно открыть как закладки, а не окна. Для открытия терминала в виде закладки можно выбрать в меню терминала File -> New или комбинацию клавиш <Ctrl+t>:

Ссылки для переключения (1 2 3) находятся слева внизу окна терминала:

9) Создайте публикацию для таблицы t:

psql -p 5432 -c "CREATE PUBLICATION t for TABLE t"

CREATE PUBLICATION

10) Создайте подписку на replica1 подсоединяющуюся к физической реплике replica2. Подсоединение к физической реплике усложняет топологию, но уменьшает нагрузку на мастер. Имя подписки определяет по умолчанию имя логического слота репликации и должно быть уникальным во всей конфигурации:

psql -p 5433 -c "CREATE SUBSCRIPTION sub1 CONNECTION 'dbname=postgres port=5434 user=postgres' PUBLICATION t WITH (origin=none)"

Если бы подписка подсоединялась напрямую к мастеру, то команда выдала бы результат сразу. У нас подписка подсоединена к физической реплике и команда создания подписки подвиснет. Через 15-17 секунд команда отвиснет и выдаст следующий результат:

ЗАМЕЧАНИЕ:  на сервере публикации создан слот репликации "sub1"

CREATE SUBSCRIPTION

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

11) Если команда создания подписки висит и не выдаёт результат, то в любом другом окне терминала можно дать команду:

psql -p 5432 -c "select pg_log_standby_snapshot()"

 pg_log_standby_snapshot

-------------------------

 9/BC0D9C58

(1 строка)

В логе кластеров видны команды репликационного протокола:

[12447] СООБЩЕНИЕ:  процесс логического декодирования достиг точки согласованности в 9/BC0D9C10

[12447] ПОДРОБНОСТИ:  Больше активных транзакций нет.

ОПЕРАТОР:  CREATE_REPLICATION_SLOT "sub1" LOGICAL pgoutput (SNAPSHOT 'nothing')

[12551] СООБЩЕНИЕ:  запускается применяющий процесс логической репликации для подписки "sub1"

[12552] СООБЩЕНИЕ:  начинается логическое декодирование для слота "sub1"

[12552] ПОДРОБНОСТИ:  Передача транзакций, фиксируемых после 9/BC0D9C58, чтение WAL с 9/BC0D9C10.

[12552] ОПЕРАТОР:  START_REPLICATION SLOT "sub1" LOGICAL 0/0 (proto_version '4', origin 'none', publication_names '"t"')

После следующего сообщения команда создания подписки выдаст результат:

[12552] СООБЩЕНИЕ:  процесс логического декодирования достиг точки согласованности в 9/BC0D9C10

[12552] ПОДРОБНОСТИ:  Больше активных транзакций нет.

[12552] ОПЕРАТОР:  START_REPLICATION SLOT "sub1" LOGICAL 0/0 (proto_version '4', origin 'none', publication_names '"t"')

[12553] СООБЩЕНИЕ:  процесс синхронизации таблицы при логической репликации для подписки "sub1", таблицы "t" запущен

Процессы 12551 и 12553 - это процессы replica1.

Остальные процессы - это процессы replica2.

12) Процесс синхронизации запустился, но в отсутствие транзакций на мастере будет висеть и ожидать точки синхронизации. Для ускорения синхронизации вызовите функцию pg_log_standby_snapshot() на мастере ещё раз, либо перейдите к 13 пункту в котором выполняется транзакция на мастере:

psql -p 5433 -c "select * from  t"

 id | t

----+---

(0 строк)

psql -p 5432 -c "select pg_log_standby_snapshot()"

 pg_log_standby_snapshot

-------------------------

 9/BC1CADE0

(1 строка)

После вызова функции (либо контрольной точки, либо через некоторое время после появления любой транзакции на мастере и передачи журнальной записи на физическую реплику) появится сообщение:

[12601] СООБЩЕНИЕ:  процесс синхронизации таблицы при логической репликации для подписки "sub1", таблицы "t" закончил обработку

В логе физической реплики появятся сообщения о том, что слот достиг точки согласованности и готов к работе:

СООБЩЕНИЕ:  процесс логического декодирования достиг точки согласованности в 9/BC263F10

ПОДРОБНОСТИ:  Больше активных транзакций нет.

ОПЕРАТОР:  CREATE_REPLICATION_SLOT "pg_43351_sync_43342_7353194261070147214" LOGICAL pgoutput (SNAPSHOT 'use')

СООБЩЕНИЕ:  начинается логическое декодирование для слота "pg_43351_sync_43342_7353194261070147214"

ПОДРОБНОСТИ:  Передача транзакций, фиксируемых после 9/BC263F58, чтение WAL с 9/BC263E48.

ОПЕРАТОР:  START_REPLICATION SLOT "pg_43351_sync_43342_7353194261070147214" LOGICAL 9/BC263F58 (proto_version '4', origin 'none', publication_names '"t"')

psql -p 5433 -c "select * from  t"

 id | t

----+---

  1 | a

(1 строка)

13) Проверьте, что репликация идёт:

psql  -p 5432 -c "INSERT INTO t (t) VALUES ('b')"

INSERT 0 1

psql -p 5433 -c "select * from t"

 id | t

----+---

  1 | a

  2 | b

(2 rows)

Вставили в мастер вторую строку, подписка подсоединенная получила эту строку с физической реплики.

Примечание к этой части практики: далее приводится список команд, которые позволяют повторить создание подписки (начиная с пункта 4 до 12 пункта):

psql -p 5432 -c "checkpoint"

psql -p 5433 -c "drop SUBSCRIPTION sub1"

psql -p 5432 -c "drop PUBLICATION t"

psql -p 5432 -c "select pg_log_standby_snapshot()"

psql -p 5432 -c "drop table t"

psql -p 5432 -c "create table t (id bigserial PRIMARY KEY, t text)"

psql -p 5432 -c "insert into t (t) values ('a')"

pg_dump -t t --schema-only --clean --if-exists | psql -p 5433 > /dev/null

psql -p 5432 -c "CREATE PUBLICATION t for TABLE t"

psql -p 5433 -c "CREATE SUBSCRIPTION sub1 CONNECTION 'dbname=postgres port=5434 user=postgres' PUBLICATION t WITH (origin=none)"

psql -p 5433 -c "select * from  t"

psql -p 5432 -c "INSERT INTO t (t) VALUES ('b')"

psql -p 5433 -c "select * from  t"

Часть 2. Репликация без первичного ключа

1) Удалите первичный ключ таблицы t на источнике (порт 5432):

psql -c "ALTER TABLE t DROP CONSTRAINT t_pkey"

ALTER TABLE

Можно было бы удалить ограничение целостности и на таблице-приёмнике, не будем этого делать, так как позже снова добавим первичный ключ.

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

psql -c "\d t"

                                    Таблица "public.t"

 Столбец |  Тип   | Правило сортировки | Допустимость NULL |         По умолчанию          

---------+--------+--------------------+-------------------+-------------------------------

 id      | bigint |                    | not null          | nextval('t_id_seq'::regclass)

 t       | text   |                    |                   |

Публикации:

    "t"

psql -p 5433 -c "\d t"

                                    Таблица "public.t"

 Столбец |  Тип   | Правило сортировки | Допустимость NULL |         По умолчанию          

---------+--------+--------------------+-------------------+-------------------------------

 id      | bigint |                    | not null          | nextval('t_id_seq'::regclass)

 t       | text   |                    |                   |

Индексы:

    "t_pkey" PRIMARY KEY, btree (id)

Последовательности для генерации значения столбца id и ограничение целостности NOT NULL сохранились и присутствуют в обеих таблицах.

2) Ключа на таблице нет. Проверим, что вставки строк не блокируются и корректно реплицируются. Вставьте строку в таблицу t:

psql -c "INSERT INTO t (t) VALUES ('b')"

INSERT 0 1

3) Обновления и удаления блокируются. Дайте команды обновления и удаления строки и посмотрите какая ошибка выдаётся:

psql -c "update t set t='c' where id=2"

ОШИБКА:  изменение в таблице "t" невозможно, так как в ней отсутствует идентификатор реплики, но она публикует изменения

ПОДСКАЗКА:  Чтобы эта таблица поддерживала изменение, установите REPLICA IDENTITY, выполнив ALTER TABLE.

psql -c "delete from t where id=2"

ОШИБКА:  удаление из таблицы "t" невозможно, так как в ней отсутствует идентификатор реплики, но она публикует удаления

ПОДСКАЗКА:  Чтобы эта таблица поддерживала удаление, установите REPLICA IDENTITY, выполнив ALTER TABLE.

4) Установите идентификаторами строк все столбцы:

psql -c "ALTER TABLE t REPLICA IDENTITY FULL"

ALTER TABLE

5) Обновления и удаления теперь не блокируются и корректно реплицируются. Выполните команды:

psql -c "update t set t='c' where id=3"

UPDATE 1

psql -c "delete from t where id=3"

DELETE 1

psql -p 5432 -c "select * from  t"

 id | t

----+---

  1 | a

  2 | b

(2 строки)

psql -p 5433 -c "select * from  t"

 id | t

----+---

  1 | a

  2 | b

(2 строки)

Команды вставки и удаления реплицировались корректно.

Использование REPLICA IDENTITY FULL нежелательно тем, что при выполнении на источнике UPDATE и DELETE через журнал передаются значения всех столбцов, а это увеличивает трафик. Если фактически можно идентифицировать строки по нескольким столбцам, то стоит их использовать в качестве идентификатора - первичного ключа.

6) Посмотрим что будет, если установить способ идентификации строк в NOTHING.

Выполните команду:

psql -c "ALTER TABLE t REPLICA IDENTITY NOTHING"

ALTER TABLE

7) Выполните команду обновления строки:

psql -c "update t set t='c' where id =2"

ОШИБКА:  изменение в таблице "t" невозможно, так как в ней отсутствует идентификатор реплики, но она публикует изменения

ПОДСКАЗКА:  Чтобы эта таблица поддерживала изменение, установите REPLICA IDENTITY, выполнив ALTER TABLE.

Ошибка такая же, как была прежде: нет идентификатора для опубликования обновления или удаления.

8) Ни добавление первичного ключа, ни REFRESH, ни DISABLE подписки не устранят ошибку. В следующих пунктах проверим это.

Приостановите подписку:

psql  -p 5433 -c "ALTER SUBSCRIPTION sub1 DISABLE"

ALTER SUBSCRIPTION

9) Посмотрите статус подписки командой psql \dRs:

psql  -p 5433 -c "\dRs"

            Список подписок

 Имя  | Владелец | Включён | Публикация

------+----------+---------+------------

 sub1 | postgres | f       | {t}

(1 строка)

10) На источнике попробуйте обновить строку:

psql -c "update t set t='c' where id =2"

ОШИБКА:  изменение в таблице "t" невозможно, так как в ней отсутствует идентификатор реплики, но она публикует изменения

ПОДСКАЗКА:  Чтобы эта таблица поддерживала изменение, установите REPLICA IDENTITY, выполнив ALTER TABLE.

Обновление не проходит, хоть подписка и приостановлена.

11) Вставьте строку на источнике:

psql -c "INSERT INTO t (t) VALUES ('c')"

INSERT 0 1

12) Проверьте что на приемнике строка не появилась:

psql  -p 5433 -c "select * from t"

 id | t

----+---

  1 | a

  2 | b

(2 строки)

13) Включите подписку:

psql  -p 5433 -c "ALTER SUBSCRIPTION sub1 ENABLE"

ALTER SUBSCRIPTION

14) Проверьте что на приемнике строка появилась:

psql  -p 5433 -c "select * from t"

 id | t

----+---

  1 | a

  2 | b

  4 | с

(3 строки)

После приостановки подписки (перевод в статус DISABLE) применение изменений приостановилось. После включения (перевод в статус ENABLE) накопленные изменения были применены.

15) Добавьте первичный ключ:

psql -c "ALTER TABLE t ADD CONSTRAINT t_key PRIMARY KEY (id)"

ALTER TABLE

Но его наличия не достаточно. Нужно указать чтобы он использовался, как REPLICA IDENTITY, так как до этого мы устанавливали REPLICA IDENTITY NOTHING.

16) Включите использование первичного ключа в качестве идентификатора репликации, то есть установите значение по умолчанию:

psql -c "ALTER TABLE t REPLICA IDENTITY DEFAULT"

ALTER TABLE

17) Теперь обновление строки не выдаёт ошибки и репликация проходит:

psql -c "update t set t='d' where id =4"

UPDATE 1

psql  -p 5433 -c "select * from t"

 id | t

----+---

  1 | a

  2 | b

  4 | d

(3 строки)

Часть 3. Добавление таблицы в публикацию

1) Создайте еще одну таблицу для репликации:

psql -c "CREATE TABLE t1 AS SELECT * FROM t"

SELECT 3

psql -c "ALTER TABLE t1 ADD CONSTRAINT t1_key PRIMARY KEY (id)"

ALTER TABLE

psql -c "\d t1"

                           Таблица "public.t1"

 Столбец |  Тип   | Правило сортировки | Допустимость NULL | По умолчанию

---------+--------+--------------------+-------------------+--------------

 id      | bigint |                    | not null          |

 t       | text   |                    |                   |

Индексы:

    "t1_key" PRIMARY KEY, btree (id)

В отличие от таблицы t автоинкрементального столбца и последовательности нет.

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

2) Посмотрите список публикаций командой psql:

psql -c "\dRp"

                                       Список публикаций

Имя|Владелец|Все таблицы|Добавления|Изменения|Удаления|Опустошения|Через корень

---+--------+-----------+----------+---------+--------+-----------+------------

t  |postgres|f          |t         |t        |t       |t          |f

(1 строка)

Реплицируются insert, update, delete, truncate.

3) Добавьте в публикацию новую таблицу:

psql -c "ALTER PUBLICATION t ADD TABLE t1"

ALTER PUBLICATION

Ошибок в логе кластеров не будет, так как мы не выполняли функцию pg_log_standby_snapshot()". Они появятся после команды вставки строки в следующем пункте.

4) Вставьте строку в таблицу t1:

psql -c "INSERT INTO t1 VALUES (5, 'e')"

INSERT 0 1

В логе подписчика replica1:

ОШИБКА:  целевое отношение логической репликации "public.t1" не существует

КОНТЕКСТ:  processing remote data for replication origin "pg_43450" during message type "INSERT" in transaction 17768, finished at 9/BC3D92F0

СООБЩЕНИЕ:  фоновый процесс "logical replication worker" (PID 17622) завершился с кодом выхода 1

СООБЩЕНИЕ:  запускается применяющий процесс логической репликации для подписки "sub1"

Ошибка о том, что logical replication worker не смог реплицировать вставку строки по причине отсутствия таблицы t1 на подписчике.

В логе физической реплики replica2:

СООБЩЕНИЕ:  9/BC3CD550 has been already streamed, forwarding to 9/BC3D9240

ОПЕРАТОР:  START_REPLICATION SLOT "sub1" LOGICAL 9/BC3CD550 (proto_version '4', origin 'none', publication_names '"t"')

СООБЩЕНИЕ:  начинается логическое декодирование для слота "sub1"

ПОДРОБНОСТИ:  Передача транзакций, фиксируемых после 9/BC3D9240, чтение WAL с 9/BC3D9088.

ОПЕРАТОР:  START_REPLICATION SLOT "sub1" LOGICAL 9/BC3CD550 (proto_version '4', origin 'none', publication_names '"t"')

СООБЩЕНИЕ:  процесс логического декодирования достиг точки согласованности в 9/BC3D9088

ПОДРОБНОСТИ:  Больше активных транзакций нет.

5) Создайте структуру таблицы в базе-приемнике:

pg_dump -t t1 --schema-only --clean --if-exists | psql -p 5433

Периодические ошибки в лог кластеров перестали выводиться, но строк в таблице подписки пока не будет:

psql -p 5433 -c "select * from t1"

 id | t

----+---

(0 строк)

6) Вставьте строку на источнике:

psql -c "INSERT INTO t1 VALUES (6, 'f')"

INSERT 0 1

7) Проверьте, что строка на подписчике не появится:

psql -p 5433 -c "select * from t1"

 id | t

----+---

(0 строк)

8) На подписчике строки не появятся пока подписка не обновится. При этом по другим таблицам в подписке репликация будет идти:

psql -c "INSERT INTO t (t) VALUES ('e')"

psql -p 5433 -c "select * from t"

 id | t

----+---

  1 | a

  3 | b

  4 | d

  5 | e

(4 строки)

7) Обновите на подписчике подписку:

psql  -p 5433 -c "ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION"

ALTER SUBSCRIPTION

psql  -p 5433 -c "select * from t1"

 id | t

----+---

(0 строк)

Строки пока не появились.

Через какое время появятся строки, то есть выполнится начальная синхронизация и применятся изменения?

Подписчик работает через физическую реплику. Если бы подписчик подсоединился к мастеру напрямую, то задержки бы не было.

Либо после контрольной точки на мастере, либо после вызова на источнике функции pg_log_standby_snapshot().

8) Вызовите эту функцию на источнике:

psql -c "select pg_log_standby_snapshot()"

 pg_log_standby_snapshot

-------------------------

 9/BC3D9938

(1 строка)

9) Проверьте, что строки на подписчике появились:

psql  -p 5433 -c "select * from t1"

 id | t

----+---

  1 | a

  2 | b

  4 | d

  5 | e

  6 | f

(5 строк)

В логе подписчика:

13:21:11.024 MSK [29400] СООБЩЕНИЕ:  процесс синхронизации таблицы при логической репликации для подписки "sub1", таблицы "t1" запущен

13:22:14.121 MSK [29400] СООБЩЕНИЕ:  процесс синхронизации таблицы при логической репликации для подписки "sub1", таблицы "t1" закончил обработку

В логе физической реплики:

13:22:14.101 MSK [29401] СООБЩЕНИЕ:  процесс логического декодирования достиг точки согласованности в 9/BC3D98F0

13:22:14.101 MSK [29401] ПОДРОБНОСТИ:  Больше активных транзакций нет.

13:22:14.101 MSK [29401] ОПЕРАТОР:  CREATE_REPLICATION_SLOT "pg_43450_sync_43451_7353194261070147214" LOGICAL pgoutput (SNAPSHOT 'use')

13:22:14.118 MSK [29401] СООБЩЕНИЕ:  начинается логическое декодирование для слота "pg_43450_sync_43451_7353194261070147214"

13:22:14.118 MSK [29401] ПОДРОБНОСТИ:  Передача транзакций, фиксируемых после 9/BC3D9938, чтение WAL с 9/BC3D98F0.

13:22:14.118 MSK [29401] ОПЕРАТОР:  START_REPLICATION SLOT "pg_43450_sync_43451_7353194261070147214" LOGICAL 9/BC3D9938 (proto_version '4', origin 'none', publication_names '"t"')

table synchronization worker синхронизировал (передал строки) таблицу на приемнике с таблицей на источнике.

Важным является то, что при добавлении таблицы в публикацию начинается захват изменений и после обновления подписки будет по умолчанию «бесшовно» (не блокируя доступ к таблице на источнике) выполнена синхронизация строк таблиц.

10) Очистите строки в таблице t1 на источнике:

psql -c "TRUNCATE t1"

TRUNCATE TABLE

Часть 4. Двунаправленная репликация

1) Создайте публикацию для таблиц t, t1:

psql -p 5433 -c "CREATE PUBLICATION t for TABLE t, t1;"

2) Создайте подписку. Имя подписки определяет по умолчанию имя логического слота репликации и должно быть уникальным во всей конфигурации, используйте имя sub2.

Нельзя чтобы слот копировал данные, так как таблицы синхронизированы, поэтому нужно установить copy_data=off.

Нельзя допускать зацикливания, поэтому origin=none:

psql -p 5432 -c "CREATE SUBSCRIPTION sub2 CONNECTION 'dbname=postgres port=5433 user=postgres' PUBLICATION t WITH (origin=none, copy_data=off)"

ЗАМЕЧАНИЕ:  на сервере публикации создан слот репликации "sub2"

CREATE SUBSCRIPTION

В логе на 5432:

13:37:12.419 MSK [3882] СООБЩЕНИЕ:  запускается применяющий процесс логической репликации для подписки "sub2"

В логе на 5433:

13:37:12.410 MSK [3881] СООБЩЕНИЕ:  процесс логического декодирования достиг точки согласованности в 9/BC3E3E88

13:37:12.410 MSK [3881] ПОДРОБНОСТИ:  Больше активных транзакций нет.

13:37:12.410 MSK [3881] ОПЕРАТОР:  CREATE_REPLICATION_SLOT "sub2" LOGICAL pgoutput (SNAPSHOT 'nothing')

13:37:12.424 MSK [3883] СООБЩЕНИЕ:  начинается логическое декодирование для слота "sub2"

13:37:12.424 MSK [3883] ПОДРОБНОСТИ:  Передача транзакций, фиксируемых после 9/BC3E3ED0, чтение WAL с 9/BC3E3E88.

13:37:12.424 MSK [3883] ОПЕРАТОР:  START_REPLICATION SLOT "sub2" LOGICAL 0/0 (proto_version '4', origin 'none', publication_names '"t"')

13:37:12.424 MSK [3883] СООБЩЕНИЕ:  процесс логического декодирования достиг точки согласованности в 9/BC3E3E88

13:37:12.424 MSK [3883] ПОДРОБНОСТИ:  Больше активных транзакций нет.

13:37:12.424 MSK [3883] ОПЕРАТОР:  START_REPLICATION SLOT "sub2" LOGICAL 0/0 (proto_version '4', origin 'none', publication_names '"t"')

Команда создания не подвиснет, так как источник и подписчик находятся в разных кластерах и подписка подсоединяется к мастеру, а не к физической реплике.

Подвисание произойдет, если базы данных источника и подписчика находятся в одном кластере.

Для продолжения работы команды нужно было бы вызывать на источнике (5433) функцию pg_log_standby_snapshot().

4) Проверьте, что репликация идёт в только что созданном направлении:

psql -p 5433 -c "INSERT INTO t (t) VALUES ('f')"

ОШИБКА:  повторяющееся значение ключа нарушает ограничение уникальности "t_pkey"

ПОДРОБНОСТИ:  Ключ "(id)=(1)" уже существует.

Возникла ошибка. Причина - использование последовательности для генерации значений в столбце первичного ключа. Состояния последовательностей не реплицируются и на 5433 последовательность сгенерировала значение 1.

4) Посмотрите какие значения выдают две последовательности в двух базах данных:

psql -p 5433 -c "select nextval('t_id_seq')"

 nextval

---------

       2

(1 row)

psql -p 5432 -c "select nextval('t_id_seq')"

 nextval

---------

       6

(1 row)

5) Проверьте какое максимальное значение есть в столбце реплицируемой таблицы:

psql -p 5433 -c "select max(id) from t"

 max

-----

   5

(1 строка)

6) Для устранения проблемы установим чтобы последовательность на одной базе выдавала четные числа, на другой нечётные. В случае использования трёх баз данных связанных репликацией было бы три последовательности и тогда бы использовали на каждой из них INCREMENT BY 3 и отличающиеся на единицу RESTART WITH.

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

psql -p 5432 -c "ALTER SEQUENCE t_id_seq INCREMENT BY 2 RESTART WITH 8"

psql -p 5433 -c "ALTER SEQUENCE t_id_seq INCREMENT BY 2 RESTART WITH 9"

Последовательности будут генерировать числа: 8,10,12... и 9,11,13...

7)  Проверьте что вставка работает:

psql -p 5433 -c "INSERT INTO t (t) VALUES ('g')"

psql -p 5432 -c "INSERT INTO t (t) VALUES ('h')"

        

8) Проверьте, что вставленные строки реплицировались:

postgres@tantor:~$ psql -p 5433 -c "select * from t"

 id | t

----+---

  1 | a

  2 | b

  4 | d

  5 | e

  9 | g

  8 | h

(6 строк)

postgres@tantor:~$ psql -p 5432 -c "select * from t"

 id | t

----+---

  1 | a

  2 | b

  4 | d

  5 | e

  9 | g

  8 | h

(6 строк)

9) Проверьте что обновления тоже работают и реплицируются:

psql -p 5432 -c "update t set t='HH' where id =8"

psql -p 5433 -c "update t set t='GG' where id =9"

psql -p 5432 -c "select * from t"

psql -p 5433 -c "select * from t"

postgres@tantor:~$ psql -p 5432 -c "select * from t"

 id | t  

----+----

  1 | a

  2 | b

  4 | d

  5 | e

  8 | HH

  9 | GG

(6 строк)

postgres@tantor:~$ psql -p 5433 -c "select * from t"

 id | t  

----+----

  1 | a

  2 | b

  4 | d

  5 | e

  8 | HH

  9 | GG

(6 строк)

Мы настроили двунаправленную репликацию. В одном направлении используется физическая реплика. Физические реплики могут использоваться в обоих направлениях.


Часть 5. Удаление подписок и публикаций

1) Удалите подписки, публикации, таблицы:

psql -p 5432 -c "drop subscription sub2"

psql -p 5433 -c "drop publication t"

psql -p 5433 -c "drop subscription sub1"

psql -p 5432 -c "drop publication t"

psql -p 5432 -c "checkpoint"

psql -p 5432 -c "drop table t"

psql -p 5432 -c "drop table t1"

psql -p 5433 -c "drop table t"

psql -p 5433 -c "drop table t1"

Примечание 1:

Если удалить слот репликации до удаления подписки, например, выдав команду:

psql -p 5434 -c "select pg_drop_replication_slot('sub1')"

то при попытке удаления подписки выдастся ошибка и подписка не будет удалена:

psql -p 5433 -c "drop subscription sub1"

ОШИБКА:  слот репликации "sub1" на сервере публикации не был удалён:

ОШИБКА:  слот репликации "sub1" не существует

В этом случае для удаления слота используется последовательность команд:

psql -p 5433 -c "alter subscription sub1 disable"

psql -p 5433 -c "alter subscription sub1 set (slot_name=none)"

psql -p 5433 -c "drop subscription sub1"

Примечание 2:

В процессе добавления таблиц в публикацию на физической реплике, изменения свойств подписок возможно возникновение ошибки вида:

СООБЩЕНИЕ:  запускается применяющий процесс логической репликации для подписки "sub1"

ОШИБКА:  не удалось начать трансляцию WAL: ОШИБКА:  из слота репликации "sub1" больше нельзя получать изменения

       ПОДРОБНОСТИ:  Этот слот был аннулирован из-за конфликта с восстановлением.

СООБЩЕНИЕ:  фоновый процесс "logical replication worker" (PID 31049) завершился с кодом выхода 1

На английском языке:

DETAIL: This slot has been invalidated because it was conflicting with recovery.

Ошибка возникает в случае:

1) hot_standby_feedback = off на том кластере, где создан слот логической репликации

2) hot_standby_feedback = on, но при этом отсутствует физический слот репликации на мастере для физической реплики, на которой создан слот логической репликации.

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

Описание: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=6af1793954e8c5e753af83c3edb37ed3267dd179


Платформа «Tantor»  

Обзор

  1. Рабочие пространства
  2. Обзор экземпляра
  3. Настройка экземпляра
  4. Профайлинг запросов
  5. Текущие активности
  6. Регламентные работы

Часть 1. Рабочие пространства

1) Войдите в Платформу по локальной ссылке https://education.tantorlabs.ru/platform/login

2) Введите учетные данные: [email protected] пароль Student123!

3) Откройте рабочее пространство «Tantor»

4) Откройте экземпляр «demo».

5) На странице «Обзор» изучите индикаторы.

Часть 2. Обзор экземпляра

Изучите выпадающее окно «Сессии», «Нагрузка ЦПУ», «Доступно ОЗУ», «Сеть», «Блок из буфера».

Часть 3. Настройка экземпляра

1) Откройте страницу «Настройки» &rarr; «Страницы настройки кластера» → «ПАРАМЕТРЫ POSTGRESQL».

2) Измените параметр autovacuum_analyze_scale_factor.

3) Нажмите на кнопку «Применить новые настройки».

4) Изучите возможные параметры фильтра.

Часть 4. Профайлинг запросов

1) Откройте страницу «Профилировщик запросов».

2) Выберете запрос с самым большим значением «Записано временных блоков».

3) Нажмите на поле «Хэш запроса» → изучите основную статистику запроса.

4) Перейдите на закладку «Планы».

5) Выберете план → нажмите на любое поле правее «Хэш запроса».

6) Изучите графический план запроса.

Часть 5. Текущие активности

1) Откройте страницу «Текущая активность».

2) Выберете БД postgres.

3) Изучите текущие подключения на вкладке «Выполнение».

Часть 6. Регламентные работы

1) Откройте страницу «Обслуживание».

2) Выберете БД test_db.

3) Выберете «Раздутие индексов».

4) Отсортируйте по убыванию колонку «КОЭФФ. РАЗДУТИЯ %»

5) Выберете самую первую таблицу, Действие «Reindex».

6) Нажмите кнопку «Запустить обслуживание».

7) Нажмите на кнопку «Запустить обслуживание».

8) Нажмите на ссылку «История».

9) Изучите результаты запуска.


Раздел 10. Дополнительные возможности и изменения СУБД Tantor по сравнению с PostgreSQL

Расширение orafce 

1) Посмотрите какие расширения установлены в базе:

postgres=# \dx

                                          Список установленных расширений

        Имя         | Версия |   Схема    |                                Описание                        

--------------------+--------+------------+---------------------------------------------------

 hypopg             | 1.4.0  | public     | Hypothetical indexes for PostgreSQL

 pg_stat_statements | 1.10   | public     | track planning and execution statistics of all SQL statements ex

 pg_store_plans     | 1.6.4  | public     | track plan statistics of all SQL statements executed

 plpgsql            | 1.0    | pg_catalog | PL/pgSQL procedural language

 plpython3u         | 1.0    | pg_catalog | PL/Python3U untrusted procedural language

(5 строк)

Список в вашей базе может отличаться от приведенного.

2) Проверьте, доступно ли для установки расширение orafce:

postgres=# select * from pg_available_extensions where name ilike '%ora%';

  name  | default_version | installed_version |                                            comment          

--------+-----------------+-------------------+----------------------------

 orafce | 4.7             | 4.7               | Functions and operators that emulate a subset of functions a

(1 строка)

3) Какие схемы есть в базе? Получите список схем:

postgres=# \dn

        Список схем

  Имя   |     Владелец      

--------+-------------------

 public | pg_database_owner

(1 строка)

4) Установите в базу расширение orafce:

postgres=# CREATE EXTENSION orafce;

CREATE EXTENSION

5) Получите список схем:

postgres=# \dn

           Список схем

     Имя      |     Владелец      

--------------+-------------------

 dbms_alert   | postgres

 dbms_assert  | postgres

 dbms_output  | postgres

 dbms_pipe    | postgres

 dbms_random  | postgres

 dbms_sql     | postgres

 dbms_utility | postgres

 oracle       | postgres

 plunit       | postgres

 plvchr       | postgres

 plvdate      | postgres

 plvlex       | postgres

 plvstr       | postgres

 plvsubst     | postgres

 public       | pg_database_owner

 utl_file     | postgres

(16 строк)

Расширение создало 15 схем.

В Oracle Database есть объекты пакеты процедур. В СУБД Тантор пакетов нет. Пакеты используются для объединения подпрограмм. Близкий аналог пакетов - схемы. В отличие от пакетов в схемах могут находиться объекты любого типа, а не только подпрограммы.

В Oracle Database пакеты поставляемые стандартно имеют префикс "dbms_"

6) Часть объектов, которые в Oracle Database вызываются без префикса имени пакета создаются расширением в схеме oracle. Вставьте имя этой схемы в путь поиска:

postgres=# set search_path TO "$user", public, oracle;

SET

7) Обратитесь к таблице dual, используемой приложениями работающими с Oracle Database для вызова однострочных функций. В Oracle Database обязательно использование фразы FROM в команде SELECT, в постгрес необязательно. Приложения в Oracle Database обычно используют команду "SELECT функция() FROM DUAL;". Выполните команду:

postgres=# SELECT sysdate() FROM dual;

Можно заметить, что круглые скобки обязательны. В Oracle Database функция SYSDATE используется без круглых скобок. В постгрес функции без аргументов не могут вызываться без круглых скобок, за исключением тех, которые по стандарту SQL вызываются без круглых скобок. Например: current_date, current_timestamp, current_catalog, current_role, current_user, session_user, user, current_schema. Причем, из этих функций только current_schema может вызываться с круглыми скобками.

8) Расширение создаёт используемый в Oracle Database тип данных VARCHAR2:

postgres=# select 'hello'::varchar2;

 varchar2

----------

 hello

(1 строка)

9) Расширение создаёт функции, которые используются в Oracle Database для отладочного вывода в составе пакета процедур dbms_output:

postgres=# SELECT dbms_output.serveroutput(true);

 serveroutput

--------------

 

(1 строка)

Аналог команды в Orcle Database "SET SERVEROUTPUT ON"

postgres=# SELECT dbms_output.put('aa');

 put

-----

 

(1 строка)

postgres=# SELECT dbms_output.put('bb');

 put

-----

 

(1 строка)

postgres=# SELECT * FROM dbms_output.get_lines(1);

 lines  | numlines

--------+----------

 {aabb} |        1

(1 строка)

postgres=# SELECT dbms_output.put('aa');

 put

-----

 

(1 строка)

postgres=# SELECT dbms_output.put('bb');

 put

-----

 

(1 строка)

postgres=# SELECT * FROM dbms_output.get_line();

 line | status

------+--------

 aabb |      0

(1 строка)

Результат get_line() и get_lines(1) одинаков.

Результат enable() и serveroutput(true) одинаков.

10) Верните значение параметра пути поиска к значению по умолчанию:

postgres=# reset search_path;

RESET


Расширение pg_variables

1) Расширение позволяет использовать переменные для хранения значений на уровне сессии. Расширение обеспечивает функционал, аналогичный переменным пакетов процедур в Oracle Database. Функционал также схож с атрибутами «application contexts» в Oracle Database. Создание переменных

Преимущество использования переменных: быстрота доступа. Переменные могут использоваться как более производительная и простая альтернатива временным таблицам.

Установите расширение:

postgres=# CREATE EXTENSION pg_variables;

CREATE EXTENSION

2) Установите значение 101 для переменной («атрибуту») int1 в «пакете» («контексте», группе переменных) с названием vars. Термин «пакет» используется в расширении для обозначения групп переменных.

postgres=# SELECT pgv_set('vars', 'int1', 101);

 pgv_set

---------

 

(1 строка)

3) Установите текстовую переменную в том же пакете:

postgres=# SELECT pgv_set('vars', 'text1', 'text variable'::text, true);

 pgv_set

---------

 

(1 строка)

4) Для получения значений используется функция pgv_get. Первый и второй параметры понятны: имя пакета и переменной. Третий аргумент - тип переменной. Выполните команду и посмотрите результат:

postgres=# SELECT pgv_get('vars', 'int1');

ERROR:  function pgv_get(unknown, unknown) does not exist

Ошибка означает, что значения по умолчанию у третьего параметра функции нет.

5) Пустое значение не передаётся:

postgres=# SELECT pgv_get('vars', 'int1', null);

ERROR:  function pgv_get(unknown, unknown, unknown) is not unique

6) Пакет знает тип переменной и сообщает его:

postgres=# SELECT pgv_get('vars', 'int1', null::numeric);

ERROR:  variable "int1" requires "integer" value

7) Передаём значение этого типа - функция возвращает значение:

postgres=# SELECT pgv_get('vars', 'int1', 0);

 pgv_get

---------

     101

(1 строка)

8) Можно использовать и пустое значение NULL::int заданного типа:

postgres=# SELECT pgv_get('vars', 'int1', NULL::int);

 pgv_get

---------

     101

(1 строка)

9) Создать две переменные с одинаковым названием но разных типов - нельзя:

postgres=# SELECT pgv_set('vars', 'int1', null::text);

ERROR:  variable "int1" requires "integer" value

10) Получение значения текстовой переменной:

postgres=# SELECT pgv_get('vars', 'text1', NULL::text);

    pgv_get    

---------------

 text variable

(1 строка)

11) Список переменных:

postgres=# SELECT * FROM pgv_list() order by package, name;

 package | name  | is_transactional 

---------+-------+------------------

 vars    | int1  | f

 vars    | text1 | f

(2 строки)

По умолчанию is_transactional=false и на а работу с переменными не влияет открыта транзакция или нет. Если is_transactional=true, то при откате транзакции в том числе до точек сохранения действия с переменными будут откатываться.

12) Транзакционность переменной задаётся четвертым параметром функции pgv_set в момент создания переменной. Переопределить его после создания нельзя:

postgres=# SELECT pgv_set('vars', 'text1', 'text variable'::text, true);

ERROR:  variable "text1" already created as NOT TRANSACTIONAL

13) Можно удалить переменную и создать с тем же именем заново:

postgres=# SELECT pgv_remove('vars', 'text1');

 pgv_remove

------------

 

(1 строка)

postgres=# SELECT pgv_set('vars', 'text1', 'text variable'::text, true);

 pgv_set

---------

 

(1 строка)

postgres=# SELECT * FROM pgv_list() order by package, name;

 package | name  | is_transactional

---------+-------+------------------

 vars    | int1  | f

 vars    | text1 | t

(2 строки)

14) Использование транзакционных переменных не приводит к накрутке счетчика транзакций. Проверим это. Текущий номер транзакции в кластере:

postgres=# SELECT pg_current_xact_id();

 pg_current_xact_id

--------------------

                871

(1 строка)

15) Откроем транзакцию, создадим транзакционную переменную и зафиксируем транзакцию:

postgres=# begin transaction;

BEGIN

postgres=*# SELECT pgv_set('vars', 'text2', 'text variable'::text, true);

 pgv_set

---------

 

(1 строка)

postgres=*# SELECT pg_current_xact_id_if_assigned();

 pg_current_xact_id_if_assigned

--------------------------------

                               

(1 строка)

Номер транзакции не назначен, используется виртуальный номер.

16) После фиксации транзакции функция получения номера транзакции выдаёт следующий номер:

postgres=*# commit;

COMMIT

postgres=# SELECT pg_current_xact_id();

 pg_current_xact_id

--------------------

                872

(1 строка)

postgres=# SELECT pg_current_xact_id();

 pg_current_xact_id

--------------------

                873

(1 строка)

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

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

17) Используемая память по пакетам:

postgres=# SELECT * FROM pgv_stats() order by package;

 package | allocated_memory

---------+------------------

 vars    |            16384

(1 строка)

18) Удаление переменной с названием int1:

postgres=# SELECT pgv_remove('vars', 'int1');

 pgv_remove

------------

 

(1 строка)

19) Удаление пакета с переменными этого пакета:

postgres=# SELECT pgv_remove('vars');

 pgv_remove

------------

 

(1 строка)

20) Удаление всех пакетов и всех переменных:

postgres=# SELECT pgv_free();

 pgv_free

----------

 

(1 строка)

В любом случае, срок жизни переменных - до окончания сессии.


Расширение page_repair

  1. Подготовка реплики        
  2. Подготовка таблицы        
  3. Восстановление страницы с помощью page_repair        
  4. Обнуление страницы

Часть 1. Подготовка реплики

Расширение page_repair включает в себя разделяемую библиотеку и две функции. Функции позволяют скопировать через сетевое соединение с физической реплики один блок на один вызов процедуры.

Для использования расширения нужна физическая реплика. Если она есть, то можно пропустить пункты её создания. Создание физической реплики рассматривалось в практике 8a.

Остановка кластера присутствующего в виртуальной машине:

postgres@tantor:~$ sudo systemctl stop tantor-se-server-16-replica

Остановка кластера, если он был создан и не пригоден к использованию как физическая реплика (стал мастером, не может накладывать журнальные данные из-за отсутствия файлов журналов):

postgres@tantor:~$ pg_ctl stop -D /var/lib/postgresql/tantor-se-16-replica/data1

postgres@tantor:~$ rm -rf /var/lib/postgresql/tantor-se-16-replica/data1

Создание реплики:

postgres@tantor:~$ rm /var/lib/postgresql/tantor-se-16/data/global/pg_store_plans.stat

postgres@tantor:~$ pg_basebackup -D /var/lib/postgresql/tantor-se-16-replica/data1 -P -R -C --slot=replica1 --checkpoint=fast

Если резервирование прервать, то нужно будет удалить директорию: rm -rf /var/lib/postgresql/tantor-se-16-replica/data1

и слот на мастере: psql -c "select pg_drop_replication_slot('replica1')"

postgres@tantor:~$ echo "port=5433" >> /var/lib/postgresql/tantor-se-16-replica/data1/postgresql.auto.conf

Запуск реплики:

postgres@tantor:~$ pg_ctl start -D /var/lib/postgresql/tantor-se-16-replica/data1 -l log_replica1.log

Проверка что репликация работает:

postgres@tantor:~$ psql -c "select * from pg_replication_slots"

В столбце status должно быть значение "t".

Часть 2. Подготовка таблицы

1) Создайте таблицу и заполните её данными:

postgres=# drop table if exists t;

NOTICE: table "t" does not exist, skipping

DROP TABLE

postgres=# CREATE TABLE t (id bigserial primary key, t text);

CREATE TABLE

postgres=# INSERT INTO t(t) SELECT encode((floor(random()*1000)::numeric ^

100::numeric)::text::bytea, 'base64') from generate_series(1,1000);

INSERT 0 1000

postgres=# update t set t = t || 'a';

UPDATE 1000

Была вставлена тысяча строк и обновлена тысяча строк. в страницах имеются актуальные и неактуальные версии строк пока не отработает автовакуум.

2) Размер файла таблицы:

postgres=# select  pg_relation_size('t');

 pg_relation_size

------------------

           802816

(1 строка)

3) Относительный путь к файлу со строками:

postgres=# SELECT pg_relation_filepath('t'::regclass);

 pg_relation_filepath

----------------------

 base/5/16622

(1 строка)

4) Префикс для получения абсолютного пути из относительного (он же PGDATA):

postgres=# \dconfig data_directory

             Список параметров конфигурации

    Параметр    |               Значение                

----------------+---------------------------------------

 data_directory | /var/lib/postgresql/tantor-se-16/data

(1 строка)

5) Номер блока в котором расположена строка с id=900:

postgres=# select ctid, id from t  where id=900;

  ctid  | id  

--------+-----

 (92,15) | 900

(1 строка)

6) Остановите экземпляр:

postgres@tantor:~$ pg_ctl stop -D /var/lib/postgresql/tantor-se-16/data

ожидание завершения работы сервера.... готово

сервер остановлен

7) Вставьте мусор в блок, в котором содержится строка с id=100:

postgres@tantor:~$ dd if=/dev/urandom conv=notrunc bs=8192 seek=92 count=1 of=/var/lib/postgresql/tantor-se-16/data/base/5/16622

1+0 записей получено

1+0 записей отправлено

8192 байт (8.2 kB, 8.0 KiB) скопирован, 0.000300021 s, 27.3 MB/s

8) Запустите кластер:

postgres@tantor:~$ sudo systemctl start tantor-se-server-16

9) Выполните команды, которые требуют обращения к поврежденной странице:

postgres=# select ctid, id from t where id=900;

ERROR:  invalid page in block 92 of relation base/5/16622

postgres=# select count(*) from t;             

ERROR:  invalid page in block 92 of relation base/5/16622

postgres=# analyze verbose t;

INFO:  analyzing "public.t"

ERROR:  invalid page in block 92 of relation base/5/16622

10) Заморозка не может быть выполнена:

postgres=# select pg_current_xact_id();

 pg_current_xact_id

--------------------

                797

(1 строка)

postgres=# vacuum freeze t;

ERROR:  invalid page in block 92 of relation base/5/16622

КОНТЕКСТ:  while scanning block 92 of relation "public.t"

postgres=# select relfrozenxid from pg_class where relname='t';

 relfrozenxid

--------------

          795

(1 строка)

11) Команды с полным сканированием таблицы дойдя до сбойного блока также прервут работу:

postgres=# explain update t set t = t || 'b' where id > 100;

                         QUERY PLAN                        

------------------------------------------------------------

 Update on t  (cost=0.00..112.75 rows=0 width=0)

   ->  Seq Scan on t  (cost=0.00..112.75 rows=900 width=38)

         Filter: (id > 100)

(3 строки)

postgres=# explain (analyze) update t set t = t || 'b' where id > 100;

ERROR:  invalid page in block 54 of relation base/5/16622

12) Команды с индексным доступом, не считывающие сбойный блок могут выполняться:

postgres=# update t set t = t || 'b' where id<500;

UPDATE 499

13) Вакуумирование, если обратится к поврежденному блоку (определяется по карте видимости) не может выполняться. Старые версии строк не будут очищаться, файлы таблиц будут увеличиваться в размерах.

postgres=# vacuum verbose t;

INFO:  vacuuming "postgres.public.t"

ERROR:  invalid page in block 92 of relation base/5/16622

КОНТЕКСТ:  while scanning block 92 of relation "public.t"

Часть 3. Восстановление страницы с помощью page_repair

1) Установите расширение в базу данных с таблицей в которой есть повреждённая страница:

postgres=# CREATE EXTENSION page_repair;

CREATE EXTENSION

2) Посмотрите определения двух функций, входящих в расширение:

postgres=# \df pg_repair_page

                                     Список функций

 Схема  |      Имя       | Тип данных результата |    Типы данных аргументов    |  Тип  

--------+----------------+-----------------------+------------------------------+-------

 public | pg_repair_page | boolean               | regclass, bigint, text       | функ.

 public | pg_repair_page | boolean               | regclass, bigint, text, text | функ.

(2 строки)

3) Вызовите функцию для восстановления страницы:

postgres=# select pg_repair_page('t'::regclass, 92, 'port=5433');

ERROR:  data checksums are not enabled

Расширению нужны контрольные суммы, включённые на кластере. Контрольные суммы нужны, чтобы отказать в восстановлении, если администратор захочет восстановить неповреждённый блок. Исследовать содержимое блока сложно, а по контрольной сумме просто.

4) Включите подсчет контрольных сумм на кластере с поврежденной таблицей:

postgres=# \q

postgres@tantor:~$ pg_ctl stop -D /var/lib/postgresql/tantor-se-16/data

ожидание завершения работы сервера.... готово

сервер остановлен

postgres@tantor:~$ rm /var/lib/postgresql/tantor-se-16/data/global/pg_store_plans.stat

postgres@tantor:~$ pg_checksums -e -D /var/lib/postgresql/tantor-se-16/data

Обработка контрольных сумм завершена

Просканировано файлов: 1271

Просканировано блоков: 27179

Записано файлов: 1055

Записано блоков: 27178

pg_checksums: синхронизация каталога данных

pg_checksums: модификация управляющего файла

Контрольные суммы в кластере включены

postgres@tantor:~$ sudo systemctl start tantor-se-server-16

5) Вызовите ещё раз функцию для восстановления страницы:

postgres@tantor:~$ psql -c "select pg_repair_page('t'::regclass, 92, 'port=5433')"

NOTICE:  skipping page repair of the given page --- page is not corrupted

 pg_repair_page

----------------

 t

(1 строка)

Функция успешно выполнилась сообщив что не стала восстанавливать страницу, так как по ее логике страница не повреждена.

6) Проверьте повреждена ли страница:

postgres@tantor:~$ psql -c "select ctid, id from t where id=900"

ERROR:  invalid page in block 92 of relation base/5/16622

Страница всё так же повреждена. Почему расширение считает страницу неповрежденной?

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

7) Чтобы затереть контрольную сумму повторите процедуру повреждения страницы:

pg_ctl stop -D /var/lib/postgresql/tantor-se-16/data

dd if=/dev/urandom conv=notrunc bs=8192 seek=92 count=1 of=/var/lib/postgresql/tantor-se-16/data/base/5/16622

sudo systemctl start tantor-se-server-16

8) Повторите процедуру восстановления страницы:

postgres@tantor:~$ psql -c "select pg_repair_page('t'::regclass, 92, 'port=5433')"

ERROR:  page on standby is also corrupted

Функция сообщает, что по её логике страница на реплике тоже повреждена.

9) Проверьте повреждена ли страница на реплике:

postgres@tantor:~$ psql -p 5433 -c "select ctid, id from t where id=900"

  ctid   | id  

---------+-----

 (92,15) | 900

(1 строка)

postgres@tantor:~$ psql -p 5433 -c "select count(*) from t"

 count

-------

  1000

(1 строка)

Страницы таблицы на реплике не повреждены. Почему расширение отказывается восстанавливать страницу?

Потому, что на реплике не включён подсчет контрольных сумм. Текст ошибки вводит в заблуждение.

10) Включите подсчёт контрольных сумм блоков данных на реплике:

postgres@tantor:~$ pg_ctl stop -D /var/lib/postgresql/tantor-se-16-replica/data1

ожидание завершения работы сервера.... готово

сервер остановлен

postgres@tantor:~$ rm /var/lib/postgresql/tantor-se-16-replica/data1/global/pg_store_plans.stat

postgres@tantor:~$ pg_checksums -e -D /var/lib/postgresql/tantor-se-16-replica/data1

Обработка контрольных сумм завершена

Просканировано файлов: 1271

Просканировано блоков: 27179

Записано файлов: 1055

Записано блоков: 27178

pg_checksums: синхронизация каталога данных

pg_checksums: модификация управляющего файла

Контрольные суммы в кластере включены

postgres@tantor:~$ pg_ctl start -D /var/lib/postgresql/tantor-se-16-replica/data1 -l log_replica1.log

11) Повторите процедуру восстановления страницы:

postgres@tantor:~$ psql -c "select pg_repair_page('t'::regclass, 92, 'port=5433')"

 pg_repair_page

----------------

 t

(1 строка)

12) Проверьте читаются ли страницы таблицы:

postgres@tantor:~$ psql -c "select count(*) from t"

 count

-------

  1000

(1 строка)

Страницы читаются, страница была восстановлена путём копирования с физической реплики.

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

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

Часть 4. Обнуление страницы

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

1) Повторите процедуру повреждения блока:

pg_ctl stop -D /var/lib/postgresql/tantor-se-16/data

dd if=/dev/urandom conv=notrunc bs=8192 seek=92 count=1 of=/var/lib/postgresql/tantor-se-16/data/base/5/16622

sudo systemctl start tantor-se-server-16

psql -c "select ctid, id from t where id=900"

WARNING:  page verification failed, calculated checksum 9494 but expected 37021

ERROR:  invalid page in block 92 of relation base/5/16622

При включённых контрольных суммах к ошибке добавилось предупреждение.

2) Включите на уровне сессии параметр:

postgres=# set zero_damaged_pages = on;

SET

3)  Выполните запрос к таблице:

postgres=# select count(*) from t;

 count

-------

  1000

(1 строка)

Число строк верное, ошибок нет. Почему?

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

postgres=# explain select count(*) from t;

                                  QUERY PLAN                                  

-------------------------------------------------------------------------------

 Aggregate  (cost=49.77..49.78 rows=1 width=8)

   ->  Index Only Scan using t_pkey on t  (cost=0.28..47.27 rows=1000 width=0)

(2 строки)

4) Выполните команду:

postgres=# select count(*) from t where t is not null;

WARNING:  page verification failed, calculated checksum 9494 but expected 37021

WARNING:  invalid page in block 92 of relation base/5/16622; zeroing out page

 count

-------

   980

(1 строка)

Число строк другое - на 20 строк меньше. В повреждённом блоке находилось 20 строк, они считаются исчезнувшими.

Предупреждающие сообщения - это результат установки параметра zero_damaged_pages = on и включенного подсчёта контрольных сумм. Если бы контрольные суммы были отключены, то предупреждений бы не было, но результат (980) был бы тот же.

5) Выполните команду:

postgres=# vacuum freeze t;

VACUUM

Вакуумирование проходит успешно, считая блок пустым.

При этом блок не менялся и не будет меняться в файле. Параметр zero_damaged_pages = on не меняет содержимое блока в файле.

pg_ctl stop -D /var/lib/postgresql/tantor-se-16/data

dd if=/dev/zero conv=notrunc bs=8192 seek=92 count=1 of=/var/lib/postgresql/tantor-se-16/data/base/5/16622

sudo systemctl start tantor-se-server-16

psql -c "select ctid, id from t where id=900"

 ctid | id

------+----

(0 строк)

Содержимое сбойного блока заполнено нулями. Контрольная сумма правильная - тоже нули. Блок считается неповреждённым, просто пустым.

6) Выполните команды:

postgres@tantor:~$ psql -c "select count(*) from t"

 count

-------

   999

(1 строка)

postgres@tantor:~$ psql -c "select ctid, id from t where id=901"

 ctid | id

------+----

(0 строк)

postgres@tantor:~$ psql -c "select count(*) from t"

 count

-------

   998

(1 строка)

Количество строк меняется в результате выборки.

Серверный процесс использует индексное сканирование (не Index Only Scan), проверяет содержимое блока и не обнаруживает строку:

postgres@tantor:~$ psql -c "explain select ctid, id from t where id=903"

                           QUERY PLAN                            

-----------------------------------------------------------------

 Index Scan using t_pkey on t  (cost=0.28..8.29 rows=1 width=14)

   Index Cond: (id = 903)

(2 строки)

7) Перестройте индексы:

postgres=# reindex (verbose) table t;

INFO:  index "t_pkey" was reindexed

ПОДРОБНОСТИ:  CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s

INFO:  index "pg_toast_16622_index" was reindexed

ПОДРОБНОСТИ:  CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s

REINDEX

postgres=# select count(*) from t;

 count

-------

   980

(1 строка)

8) Удалите таблицу:

postgres=# drop table t;

DROP TABLE


Отладка подпрограмм

  1. Установка расширения из исходных кодов на примере pldebugger        
  2. Отладка функции в pgAdmin        
  3. Отладка подпрограмм в DBeaver

Часть 1. Установка расширения из исходных кодов на примере pldebugger

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

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

Для отладки подпрограмм нужна поддержка со стороны сервера и графическое клиентское приложение (среда разработки), которое будет показывать исходный код подпрограммы, инициировать отладку и получать отладочную информацию. Функционал стандартный для отладчиков: устанавливать точки останова, пошаговое выполнение, наблюдение за переменными и их изменение.

Серверная часть - модуль, (библиотека и расширение) создано EnterpriseDB и свободно распространяется по адресу https://github.com/EnterpriseDB/pldebugger/releases/tag/v1.5 

Основное клиентское приложение pgAdmin. Другие клиентские приложения могут использовать серверную часть.

1) Переключитесь в root, так как он владелец программного обеспечения:

astra@tantor:~$ su -

Пароль: root

root@tantor:~#

2) Скачайте расширение pldebugger:

root@tantor:~# wget https://github.com/EnterpriseDB/pldebugger/archive/refs/tags/v1.5.zip

3) Распакуйте архив:

root@tantor:~# unzip pldebugger-1.5.zip

4) Перейдите в директорию в которую были распакованы исходные файлы расширения:

root@tantor:~# cd pldebugger-1.5

5) Добавьте в путь директорию с утилитой pg_config и переменную окружения указывающую утилите make использовать логику установки расширений PGXS:

root@tantor:~/pldebugger-1.5# export PATH=/opt/tantor/db/16/bin:$PATH

root@tantor:~/pldebugger-1.5# export USE_PGXS=1

6) В файле README.pldebugger написано как устанавливать расширение. Дайте первую команду:

root@tantor:~/pldebugger-1.5# make

Появится предупреждение:

plpgsql_debugger.c: In function 'is_datum_visible':

plpgsql_debugger.c:1270:11: warning: declaration of 'i' shadows a previous local [-Wshadow=compatible-local]

    int    i;

           ^

plpgsql_debugger.c:1246:11: note: shadowed declaration is here

показывающее качество написания кода расширения и ошибку, связанную с отсутствующим файлом заголовка:

In file included from pldbgapi.c:98:

/opt/tantor/db/16/include/postgresql/server/libpq/libpq-be.h:32:10:

fatal error: gssapi/gssapi.h: No such file or directory

 #include <gssapi/gssapi.h>

В строке 50 файла /opt/tantor/db/16/include/postgresql/server/pg_config.h

указано, что СУБД Тантор был скомпилирован с параметром --with-gssapi

также на это указывает утилита pg_config:

root@tantor:~/pldebugger-1.5# pg_config | grep gssapi

CONFIGURE =  '--prefix=/opt/tantor/db/16' '--enable-tap-tests' '--enable-nls=en ru' '--with-python' '--with-icu' '--with-lz4' '--with-zstd' '--with-ssl=openssl' '--with-ldap' '--with-pam' '--with-uuid=e2fs' '--with-libxml' '--with-libxslt' '--with-gssapi' '--with-selinux' '--with-systemd' '--with-llvm' 'CFLAGS=-O2 -pipe -Wno-missing-braces' 'LLVM_CONFIG=/usr/bin/llvm-config-11' 'CLANG=/usr/bin/clang-11' 'PYTHON=/usr/bin/python3'

LIBS = -lpgcommon -lpgport -lselinux -lzstd -llz4 -lxslt -lxml2 -lpam -lssl -lcrypto -lgssapi_krb5 -lz -lreadline -lpthread -lrt -ldl -lm

7) Обойдём ошибку (применим workaround). Сохраните файл:

root@tantor:~/pldebugger-1.5# cp /opt/tantor/db/16/include/postgresql/server/pg_config.h /opt/tantor/db/16/include/postgresql/server/pg_config.SAV

В 50 строке (она пустая) файла pg_config.h добавьте строку:

#define ENABLE_GSS 0

8) Повторите команду сборки модуля расширения из исходных кодов:

root@tantor:~/pldebugger-1.5# make

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

make: /usr/bin/clang-11: Command not found

9) Установите недостающий пакет:

root@tantor:~/pldebugger-1.5# apt-get install clang

10) Повторите команду сборки модуля расширения из исходных кодов:

root@tantor:~/pldebugger-1.5# make

Компиляция пройдёт успешно.

11) Следующая команда, описанная в файле README.pldebugger это копирование файлов расширения в стандартные директории программного обеспечения СУБД.

Выполните команду:

root@tantor:~/pldebugger-1.5# make install

root@tantor:~/pldebugger-1.5# make install

/usr/bin/mkdir -p '/opt/tantor/db/16/lib/postgresql'

/usr/bin/mkdir -p '/opt/tantor/db/16/share/postgresql/extension'

/usr/bin/mkdir -p '/opt/tantor/db/16/share/postgresql/extension'

/usr/bin/mkdir -p '/opt/tantor/db/16/share/doc/postgresql/extension'

/usr/bin/install -c -m 755  plugin_debugger.so '/opt/tantor/db/16/lib/postgresql/plugin_debugger.so'

/usr/bin/install -c -m 644 .//pldbgapi.control '/opt/tantor/db/16/share/postgresql/extension/'

/usr/bin/install -c -m 644 .//pldbgapi--1.1.sql .//pldbgapi--unpackaged--1.1.sql .//pldbgapi--1.0--1.1.sql  '/opt/tantor/db/16/share/postgresql/extension/'

/usr/bin/install -c -m 644 .//README.pldebugger '/opt/tantor/db/16/share/doc/postgresql/extension/'

/usr/bin/mkdir -p '/opt/tantor/db/16/lib/postgresql/bitcode/plugin_debugger'

/usr/bin/mkdir -p '/opt/tantor/db/16/lib/postgresql/bitcode'/plugin_debugger/

/usr/bin/install -c -m 644 plpgsql_debugger.bc '/opt/tantor/db/16/lib/postgresql/bitcode'/plugin_debugger/./

/usr/bin/install -c -m 644 plugin_debugger.bc '/opt/tantor/db/16/lib/postgresql/bitcode'/plugin_debugger/./

/usr/bin/install -c -m 644 dbgcomm.bc '/opt/tantor/db/16/lib/postgresql/bitcode'/plugin_debugger/./

/usr/bin/install -c -m 644 pldbgapi.bc '/opt/tantor/db/16/lib/postgresql/bitcode'/plugin_debugger/./

cd '/opt/tantor/db/16/lib/postgresql/bitcode' && /usr/lib/llvm-11/bin/llvm-lto -thinlto -thinlto-action=thinlink -o plugin_debugger.index.bc plugin_debugger/plpgsql_debugger.bc plugin_debugger/plugin_debugger.bc plugin_debugger/dbgcomm.bc plugin_debugger/pldbgapi.bc

12) Обновите список файлов для поиска:

root@tantor:~/pldebugger-1.5# updatedb

Поищите файл модуля:

root@tantor:~/pldebugger-1.5# locate plugin_debugger

/opt/tantor/db/16/lib/postgresql/plugin_debugger.so

/opt/tantor/db/16/lib/postgresql/bitcode/plugin_debugger

/opt/tantor/db/16/lib/postgresql/bitcode/plugin_debugger.index.bc

/opt/tantor/db/16/lib/postgresql/bitcode/plugin_debugger/dbgcomm.bc

/opt/tantor/db/16/lib/postgresql/bitcode/plugin_debugger/pldbgapi.bc

/opt/tantor/db/16/lib/postgresql/bitcode/plugin_debugger/plpgsql_debugger.bc

/opt/tantor/db/16/lib/postgresql/bitcode/plugin_debugger/plugin_debugger.bc

/root/pldebugger-1.5/plugin_debugger.bc

/root/pldebugger-1.5/plugin_debugger.c

/root/pldebugger-1.5/plugin_debugger.def

/root/pldebugger-1.5/plugin_debugger.o

/root/pldebugger-1.5/plugin_debugger.so

Этот пункт иллюстрирует один из способов быстрого поиска файлов в операционной системе.

Файл модуля был установлен в директории:

/opt/tantor/db/16/lib/postgresql/plugin_debugger.so

Название файла модуля нужно знать, чтобы загрузить библиотеку.

13) Вернитесь в терминал непривилегированного пользователя:

root@tantor:~/pldebugger-1.5# exit

logout

14) Проверьте, что расширение доступно для установки в базу:

astra@tantor:~$ psql

postgres=# select * from pg_available_extensions where name like '%dbg%';

   name   | default_version | installed_version |                       comment                        

----------+-----------------+-------------------+-------------------------------------------

 pldbgapi | 1.1             |                   | server-side support for debugging PL/pgSQL

                                                        functions

(1 строка)

15)

postgres=# \dconfig shared_preload_libraries 

                      Список параметров конфигурации

         Параметр         |                    Значение                    

--------------------------+------------------------------------------------

 shared_preload_libraries | pg_stat_statements,pg_store_plans,auto_explain

(1 строка)

16) Добавьте библиотеку:

postgres=# alter system set shared_preload_libraries = pg_stat_statements, pg_store_plans, auto_explain, plugin_debugger;

ALTER SYSTEM

Апострофы после знака равенства нельзя использовать, иначе команда добавит кавычки трактуя строку как название файла и экземпляр не запустится. Пример команды, которая выполнится, но экземпляр не запустится пока вручную не будет отредактирован файл postgresql.auto.conf, так как команду ALTER SYSTEM не выполняется на остановленном экземпляре:

alter system set shared_preload_libraries = 'pg_stat_statements, pg_store_plans, auto_explain, plugin_debugger';

postgres=# \q

postgres@tantor:~$ pg_ctl stop

waiting for server to shut down.... done

server stopped

postgres@tantor:~$ pg_ctl start

waiting for server to start....

ВАЖНО:  нет доступа к файлу "pg_stat_statements, pg_store_plans, auto_explain, plugin_debugger": Нет такого файла или каталога

СООБЩЕНИЕ:  система БД выключена

 stopped waiting

pg_ctl: could not start server

Examine the log output.

17) Рестартуйте экземпляр:

astra@tantor:~$ sudo systemctl start tantor-se-server-16

18) Библиотека отладчика была загружена. Создайте расширение в базе данных postgres:

astra@tantor:~$ psql

postgres=# create extension pldbgapi;

CREATE EXTENSION

19) Создайте функцию для тестирования отладчика:

CREATE OR REPLACE FUNCTION bobdef()

 RETURNS text

 LANGUAGE plpgsql

 SECURITY DEFINER

AS $function$

 BEGIN

  RAISE NOTICE 'search_path %', current_schemas(true);

  RAISE NOTICE 'current_user %', current_user;

  RAISE NOTICE 'session_user %', session_user;

  RAISE NOTICE 'user %', user;

  RETURN now();

 END;

$function$

;

Часть 2. Отладка функции в pgAdmin

1) Запустите pgAdmin.

При запросе пароля для доступа к паролям наберите tantor.

2) Раскройте Servers -> master -> Schemas (1) -> public -> Functions (..)

Если соединений с базой нет, создайте его и назовите master.

3) Для отладки выполнения подпрограммы в чужой сессии выберите функцию bobdef(). Нажмите правую кнопку мыши и выберите Debugging -> Set Breakpoint

4) Появится сообщение «Waiting for another session to invoke target».

В psql вызовите функцию:

postgres=# select bobdef();


5) Окно pgAdmin отвиснет и покажет исходный код подпрограммы. Точка останова - первая команда подпрограммы.

Можно нажать вторую слева в окне с текстом функции на иконку Step over - будет пошаговое выполнение. При этом в окне psql можно наблюдать вывод команд RAISE NOTICE.

Также можно устанавливать точки останова. Для их установки или удаления нужно кликнуть мышкой правее от номера строки. Справа от числа 6 на картинке виден красный кружок - в это место можно кликнуть и кружок обозначает точку останова.

6) Нажимая мышкой на иконку Step Over или Continue/Start дойдите до окончания выполнения функции.

7) Для выполнения отладки с вызовом подпрограммы в сессии pgAdmin можно выбрать в выпадающем меню Debugging -> Debug. В этом случае не потребуется запускать функцию в psql, она запустится в pgAdmin и в окне pgAdmin будут выдаваться client_messages (результат команд RAISE NOTICE).

Часть 3. Отладка подпрограмм в DBeaver

1) Запустите DBeaver. При первом запуске предложит создать Sample Database, её создавать не нужно, она не относится к постгрес.

2) Выберите иконку PostgreSQL:

3) Создайте, если не создано ранее соединение с базой данных. Обратите внимание, что лучше выбрать директорию локального клиента Тантор 16, создав определение клиента по пути /opt/tantor/db/16:


4) Убедитесь, что в поле версии продукта указано число:

5) DBeaver написан на java и предложит скачать jdbc драйвер:


6) Выберите подпрограмму для отладки и кликните на окно Исходный код:

7) Выберите в меню Справка -> Install New Software. Выберите для установки DBeaver Debug Extension:


8) Выберите Select All, установится галочка:

9) Выберите точку на radio button:


10) В появившемся окне нажмите Select All и кнопку Trust Selected

Если утилита повисла и кнопка не нажимается, убейте процесс. Утилита может подвиснуть если кликнуть в этом окне куда-либо кроме Select All и затем Trust Selected.

11) Утилита предложит перезапуститься, перезапустите её:


12) Проверьте, что после перезапуска, открыто окно с исходным кодом подпрограммы. Если не открыто, то выберите подпрограмму и нажмите на вкладку «Исходный код»:

13) Нажмите зелёную иконку на toolbar:


14) В появившемся окне настроек отладки можно установить параметры вызова подпрограммы. Нажмите «ОК»:

15) В появившемся окне выбора интерфейса отладчика нажмите No:


16) Кликая в начале строк исполняемого кода, можно устанавливать и снимать точки останова, они отображаются синими кружками. С точками останова можно продолжать выполнение кода нажимая на иконку с зелёным треугольником:

Все иконки на toolbar стандартны для отладчиков в графических средах разработки (IDE): Step into (F5), Step over (F6), Terminate (Ctrl+F2), Resume (F8).


Обработка строк большого размера - StringBuffer

1) Выполните команды:

drop table if exists t2;

create table t2 (c1 text, c2 text);

insert into t2 (c1)

VALUES (repeat('a', 1024*1024*512));

update t2 set c2 = c1;

select * from t2;

При выполнении команды select появится ошибка:

ERROR:  out of memory

ПОДРОБНОСТИ:  Cannot enlarge string buffer containing 536870922 bytes by 536870912 more bytes.

При выборке в строковый буфер выбиралось значение поля c1 плюс 10 байт. Для выборки значения второго поля c2 буфер пытался увеличиться на его размер поля c2.

2)  Попробуем с меньшими полями:

drop table if exists t1;

create table t1 (c1 text, c2 text, c3 text, c4 text);

insert into t1 (c1) VALUES (repeat('a', 1024*1024*256));

update t1 SET c2=c1;

update t1 SET c3=c1;

update t1 SET c4=c1;

select * from t1;

Появится ошибка:

ERROR:  out of memory

ПОДРОБНОСТИ:  Cannot enlarge string buffer containing 805306386 bytes by 268435456 more bytes.

При выборке в строковый буфер выбирались значения полей c1, c2, c3. Буфер достиг размера трёх полей плюс 18 байт. При увеличении размера буфера на размер поля c4 возникла ошибка превышения границы 1Гб.

         3) Выполните команду:

 postgres=# COPY t2 TO '/tmp/test';

ERROR:  out of memory

ПОДРОБНОСТИ:  Cannot enlarge string buffer containing 536870913 bytes by 536870912 more bytes.

Возникла та же самая ошибка.

4) Строки больше 1Гб можно выгрузить по отдельным столбцам. Тип данных text и другие типы данных имеют ограничение на размер поля 1Гб. Выполните команду, выгружающую содержимое одного столбца:

 postgres=# COPY t2 (c1) TO '/tmp/test';

COPY 1

 

postgres=# \! ls -al /tmp/test

-rw-r--r-- 1 postgres postgres 536870913 /tmp/test

 

postgres=# \! rm /tmp/test

Содержимое столбца было успешно выгружено.

5) Выполните:

 drop table if exists t2;

create table t2 (c1 text);

insert into t2 (c1) VALUES (repeat(E'a\n', 357913941));

COPY t2 TO '/tmp/test';

         Появится ошибка:

postgres=# COPY t2 TO '/tmp/test';

ERROR:  out of memory

ПОДРОБНОСТИ:  Cannot enlarge string buffer containing 1073741822 bytes by 1 more bytes.

         Было превышено на 1 байт ограничение на память строкового буфера.

         Размер поля - треть гигабайта с округлением в меньшую сторону.

При выгрузке в текстовом виде содержимое поля будет выглядеть так:

a\na\na\na\n и размер поля увеличится в три раза до 1073741823 байт, что на 1 байт превышает максимальную границу.

6) При использовании формата binary поле можно выгрузить:

postgres=# COPY t2 TO '/tmp/test' WITH BINARY;

COPY 1

 

postgres=# \! ls -al /tmp/test

-rw-r--r-- 1 postgres postgres 715827909 /tmp/test

 

postgres=# \! rm /tmp/test

         7) Посмотрите сколько памяти выделяет серверный процесс при обработке строки. Выполните команды:

drop table if exists t2;

create table t2 (c1 text, c2 text);

insert into t2 (c1) values (repeat('a', 1024*1024*1024-69));

В процессе выполнения команды insert, если успеть, то можно во втором окне терминала посмотреть как менялся объем занятой и свободной памяти (нажимая на клавиатуре клавишу <стрелка вверх> и <Enter>):

postgres@tantor:~$ free -b -w

              total        used        free      shared     buffers       cache   available

Mem:     4109729792   633286656  2788950016   148430848    80027648   607465472  3033432064

Swap:             0           0           0

postgres@tantor:~$ free -b -w

              total        used        free      shared     buffers       cache   available

Mem:     4109729792  1280106496  2164342784   148439040    80093184   585187328  2386747392

Swap:             0           0           0

postgres@tantor:~$ free -b -w

              total        used        free      shared     buffers       cache   available

Mem:     4109729792  1514721280  1929728000   148439040    80093184   585187328  2152132608

Swap:             0           0           0

postgres@tantor:~$ free -b -w

              total        used        free      shared     buffers       cache   available

Mem:     4109729792  1948651520  1495797760   148439040    80093184   585187328  1718202368

Swap:             0           0           0

postgres@tantor:~$ free -b -w

              total        used        free      shared     buffers       cache   available

Mem:     4109729792  2772905984   671543296   148439040    80093184   585187328   893947904

Swap:             0           0           0

postgres@tantor:~$ free -b -w

              total        used        free      shared     buffers       cache   available

Mem:     4109729792   656199680  2735239168   148439040    80093184   638197760  3010174976

Swap:             0           0           0

 

Память выделяется динамически.

Использование памяти увеличилось на ~2Гб (2125635584 байт). Свободной памяти осталось ~670Мб.

         8) Если на хосте (виртуальной машине) не хватает физической памяти для выделения буфера обработки строк, то экземпляр может аварийно остановиться. Выполните команды:

 update t2 set c2 = c1;

select * from t2;

сервер неожиданно закрыл соединение

        Скорее всего сервер прекратил работу из-за сбоя

        до или в процессе выполнения запроса.

Подключение к серверу потеряно. Попытка восстановления неудачна.

Подключение к серверу потеряно. Попытка восстановления неудачна.

!?> \q

postgres@tantor:~$ psql

psql (16.1)

Введите "help", чтобы получить справку.

Такая ошибка возникнет при нехватке физической памяти. Серверный процесс пытался выделить ~4Гб памяти, а свободной памяти было меньше 2.7Гб. oom-kill (out of memory killer) убил серверный процесс. Однако oom-kill может убить произвольные процессы. Процесс postgres остановил все процессы и запустил фоновые процессы.

В процессе динамического выделения памяти операционная система уменьшала размер кэша операционной системы. Если в кэше операционной системы было бы много страниц не записанных на диск, операционная система пыталась бы их записать и стала менее «отзывчивой».  

postgres@edu-tantordb-px-001:~$ free -b -w

              total        used        free      shared     buffers       cache   available

Mem:     4109729792  3190587392   145354752   148439040    80482304   693305344   474697728

Swap:             0           0           0

postgres@edu-tantordb-px-001:~$ free -b -w

              total        used        free      shared     buffers       cache   available

Mem:     4109729792  3805593600   117968896   148439040      237568   185929728    21350400

Swap:             0           0           0

postgres@edu-tantordb-px-001:~$ free -b -w

              total        used        free      shared     buffers       cache   available

Mem:     4109729792   629743616  3223060480   134189056     4390912   252534784  3134205952

Swap:             0           0           0

 

Сообщения в журнале операционной системы:

postgres@tantor:~$ sudo dmesg

[79734.048885] oom-kill: constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0-1,global_oom, task_memcg=/system.slice/tantor-se-server-16.service,task=postgres,pid=5041,uid=999

[79734.048904] Out of memory: Killed process 5041 (postgres) total-vm:4425648kB, anon-rss:3177400kB, file-rss:4kB, shmem-rss:34624kB, UID:999 pgtables:6444kB oom_score_adj:0

Сообщения в логе кластера:

postgres@tantor:~$ cat $PGDATA/current_logfiles

stderr log/postgresql-000000.log

postgres@tantor:~$ tail -n 15 $PGDATA/log/ postgresql-000000.log

 

[31030] LOG:  server process (PID 31038) was terminated by signal 9: Killed

[31030] DETAIL:  Failed process was running: select * from t2;

[31030] LOG:  terminating any other active server processes

[31030] LOG:  all server processes terminated; reinitializing

[31039] LOG:  database system was interrupted; last known up at 19:58:59 MSK

[31042] FATAL:  the database system is in recovery mode

Failed.

[31039] LOG:  database system was not properly shut down; automatic recovery in progress

[31039] LOG:  redo starts at 116/CE344C0

[31039] LOG:  invalid record length at 116/DF34798: expected at least 26, got 0

[31039] LOG:  redo done at 116/DF34770 system usage: CPU: user: 0.02 s, system: 0.12 s, elapsed: 0.15 s

[31040] LOG:  checkpoint starting: end-of-recovery immediate wait

[31040] LOG:  checkpoint complete: wrote 2105 buffers (12.8%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.025 s, sync=0.003 s, total=0.031 s; sync files=25, longest=0.001 s, average=0.001 s; distance=17408 kB, estimate=17408 kB; lsn=116/DF34798, redo lsn=116/DF34798

[31030] LOG:  database system is ready to accept connections

oom-kill послал сигнал 9 (SIGKILL) серверному процессу, который при выполнении команды select * from t2 пытался выделить много памяти, но oom-kill может послать сигнал 9 (SIGKILL) и другим процессам.

Процесс postgres останавливает все процессы и снова запускает процессы, как при запуске экземпляра.

 9) Удалите таблицы:

postgres=# drop table t1;

DROP TABLE

postgres=# drop table t2;

DROP TABLE

tantorlabs.ru

 страница  из