From 25d20294a03f91e7919a6095b5b736f78bdd0981 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年3月23日 08:08:12 +0300 Subject: [PATCH 001/145] IP list --- bashrc/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/bashrc/README.md b/bashrc/README.md index c7853f0..6174176 100644 --- a/bashrc/README.md +++ b/bashrc/README.md @@ -9,6 +9,7 @@ 1. дата и время с часовой зоной 1. пользователь (`root` отображается красным цветом) 1. хост +1. IP адреса 1. путь и каталог Часть приглашения на второй строке: From 647d432af6177624779947ad9f54672780e6640d Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年3月25日 09:32:18 +0300 Subject: [PATCH 002/145] Fibonacci sequence numbers with recursive in PostgreSQL --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index d00024e..017b73d 100644 --- a/README.md +++ b/README.md @@ -1177,6 +1177,18 @@ select max(x), count(x) from t ``` +[Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_sequence) numbers with recursive in PostgreSQL +``` +with recursive r(a, b) as ( + select 0::int, 1::int + union all + select b, a + b + from r + where b < 1000 +) +select a from r; +``` + ## Модификация пользовательских данных (DML) ### Как добавить или обновить записи одним запросом (UPSERT)? From 6ed67d278ce98deaf5a68478c40d35677fd5e978 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年3月25日 09:32:59 +0300 Subject: [PATCH 003/145] sql syntax --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 017b73d..37deb34 100644 --- a/README.md +++ b/README.md @@ -1178,7 +1178,7 @@ from t ``` [Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_sequence) numbers with recursive in PostgreSQL -``` +```sql with recursive r(a, b) as ( select 0::int, 1::int union all From 7b6f7e4d2c2fc3cc60530f7c7f307aa1b72b1a03 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年3月28日 11:31:08 +0300 Subject: [PATCH 004/145] Update README.md --- pg_receivewal/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_receivewal/README.md b/pg_receivewal/README.md index 33f0421..678cafa 100644 --- a/pg_receivewal/README.md +++ b/pg_receivewal/README.md @@ -60,7 +60,7 @@ postgresql: #on_stop: /bin/bash -c 'sudo /bin/systemctl stop pg_receivewal@14' # закомментировано, т.к. это сделано в настройках pg_receivewal@.service через PartOf= ``` -Файлы +Файлы * [`/etc/systemd/system/pg_receivewal@.service`](pg_receivewal@.service) * [`/etc/sudoers.d/permit_pgreceivewal`](permit_pgreceivewal) From 2e9d499e8808eae8f09c2f466d93d44e9d4daa0d Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年3月28日 11:39:48 +0300 Subject: [PATCH 005/145] Update README.md --- pg_receivewal/README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pg_receivewal/README.md b/pg_receivewal/README.md index 678cafa..145505f 100644 --- a/pg_receivewal/README.md +++ b/pg_receivewal/README.md @@ -10,7 +10,7 @@ i При архивировании WAL файлы сжимаются в формат `gzip` (≈ 66% от исходного размера, даже если включен параметр [wal_compression](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-WAL-COMPRESSION)). Это позволяет экономить место на сетевом диске и уменьшить нагрузку на ввод-вывод. -⚠ Удаление неактуальных WAL файлов сделано в сервисе резервного копирования! +⚠ Удаление неактуальных WAL файлов сделано в [сервисе резервного копирования](../pg_backup)! Преимущества сервиса: 1. Архивирование WAL файлов в реальном времени. Гарантируется, что ни одна транзакция не будет потеряна. @@ -60,7 +60,7 @@ postgresql: #on_stop: /bin/bash -c 'sudo /bin/systemctl stop pg_receivewal@14' # закомментировано, т.к. это сделано в настройках pg_receivewal@.service через PartOf= ``` -Файлы +**Файлы** * [`/etc/systemd/system/pg_receivewal@.service`](pg_receivewal@.service) * [`/etc/sudoers.d/permit_pgreceivewal`](permit_pgreceivewal) @@ -69,6 +69,16 @@ postgresql: * interprets several `%` prefixes as specifiers (escape `%` with `%%`) * parses `\` before some characters (escape `\` with `\\`) +## Вопросы и ответы + +### Сервис был временно остановлен. После его запуска продолжит ли он копирование WAL файлов с того места, где остановился? +Да, если на сервере СУБД хватит WAL файлов для исключения «разрыва цепочки». + +Иначе нужно сделать так: +1. в архивной папке удалить все WAL файлы +1. запустить сервис +1. сделать полную резервную копию СУБД + ## Что осталось доделать в сервисе? 1. Протестировать, что сервис перезагружается при перезагрузке Patroni / PostgreSQL. From 0f5dd9171556fbd71180476778753e9ab1fdf861 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年3月28日 12:04:34 +0300 Subject: [PATCH 006/145] Update README.md --- pg_receivewal/README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pg_receivewal/README.md b/pg_receivewal/README.md index 145505f..9a808b9 100644 --- a/pg_receivewal/README.md +++ b/pg_receivewal/README.md @@ -2,15 +2,18 @@ ## Введение -⚠ Для кластеров СУБД используется не этот сервис, а штатная функциональность [archive_command](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-ARCHIVE-COMMAND), чтобы везде было единообразно, это упрощает сопровождение. Обычно, при наличии синхронной реплики, требования по архивированию WAL файлов в реальном времени нет. - Для непрерывного архивирования [WAL файлов](https://postgrespro.ru/docs/postgresql/16/continuous-archiving) **в реальном времени** применяется [pg_receivewal](https://postgrespro.ru/docs/postgresql/16/app-pgreceivewal), а не [archive_command](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-ARCHIVE-COMMAND). +> [!CAUTION] +> При наличии синхронной реплики архивировать WAL файлов в реальном времени не требуется. Для кластеров СУБД используется не этот сервис, а штатная функциональность [archive_command](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-ARCHIVE-COMMAND), чтобы везде было единообразно, это упрощает сопровождение. + Сервис работает только с СУБД мастером, использует отдельный слот репликации и выглядит как ещё одна постоянно отстающая асинхронная реплика. -i При архивировании WAL файлы сжимаются в формат `gzip` (≈ 66% от исходного размера, даже если включен параметр [wal_compression](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-WAL-COMPRESSION)). Это позволяет экономить место на сетевом диске и уменьшить нагрузку на ввод-вывод. +> [!NOTE] +> При архивировании WAL файлы сжимаются в формат `gzip` (≈ 66% от исходного размера, даже если включен параметр [wal_compression](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-WAL-COMPRESSION)). Это позволяет экономить место на сетевом диске и уменьшить нагрузку на ввод-вывод. -⚠ Удаление неактуальных WAL файлов сделано в [сервисе резервного копирования](../pg_backup)! +> [!WARNING] +> Удаление неактуальных WAL файлов сделано в [сервисе резервного копирования](../pg_backup)! Преимущества сервиса: 1. Архивирование WAL файлов в реальном времени. Гарантируется, что ни одна транзакция не будет потеряна. From 81552ffad27955009d4a9e4163acdcda767491e6 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年3月28日 14:01:01 +0300 Subject: [PATCH 007/145] Update README.md --- pg_backup/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index 6055a92..c63f915 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -8,11 +8,13 @@ Если рез. копии создавать с одной из реплик, то есть риск значительного отставания (часы и дни). Это можно упустить (человеческий фактор) или не сразу возможно ликвидировать отставание и тогда будет создана неактуальная резервная копия! -i Резервная копия сжимается в формат `zstd` (16–25% от исходного размера файлов СУБД). Это позволяет экономить место на сетевом диске и уменьшить нагрузку на ввод-вывод. +> [!NOTE] +> Резервная копия сжимается в формат `zstd` (16–25% от исходного размера файлов СУБД). Это позволяет экономить место на сетевом диске и уменьшить нагрузку на ввод-вывод. -⚠ Внимание! -WAL файлы в резервную копию не копируются. -Для возможности восстановления СУБД из резервной копии должно быть настроено [непрерывное архивирование WAL файлов](https://postgrespro.ru/docs/postgresql/16/continuous-archiving) +> [!CAUTION] +> Внимание! +> WAL файлы в резервную копию не копируются. +> Для возможности восстановления СУБД из резервной копии должно быть настроено [непрерывное архивирование WAL файлов](https://postgrespro.ru/docs/postgresql/16/continuous-archiving) через [archive_command](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-ARCHIVE-COMMAND) или [pg_receivewal](https://postgrespro.ru/docs/postgresql/16/app-pgreceivewal). From 1d73cb634146962379e9824a1ebaa8372ac4505e Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年4月23日 10:41:32 +0300 Subject: [PATCH 008/145] zstd -f flag removed --- pg_backup/archive_command.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pg_backup/archive_command.sh b/pg_backup/archive_command.sh index 9de1fb1..532c217 100644 --- a/pg_backup/archive_command.sh +++ b/pg_backup/archive_command.sh @@ -25,5 +25,7 @@ STEP=3 ZSTD_LEVEL=$(echo "(9 * ${STEP} - ${WAL_FILES_QUEUE}) / ${STEP}" | bc) test "$ZSTD_LEVEL" -lt 1 && ZSTD_LEVEL=1 -# архивируем файл (без -B1M используются не все ядра из-за небольшого размера файла) -ionice -c2 -n7 nice -n19 zstd -q -f -${ZSTD_LEVEL} -T${ZSTD_THREADS} -B1M "$FILE_SRC" -o "$FILE_DST" +# архивируем файл +# в zstd без флага -B1M используются не все ядра (из-за небольшого размера файла?) +# в zstd флаг -f использовать нельзя, т.к. если файл существует, то должна быть ошибка (защита от дурака от перезаписи нужного файла) +ionice -c2 -n7 nice -n19 zstd -q -${ZSTD_LEVEL} -T${ZSTD_THREADS} -B1M "$FILE_SRC" -o "$FILE_DST" From 655655fd134a87929e722736dad8e0d1c1562297 Mon Sep 17 00:00:00 2001 From: Rinat Mukhtarov Date: 2025年4月29日 09:23:06 +0300 Subject: [PATCH 009/145] is_inn removed --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37deb34..a802747 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ WHERE email IS NOT NULL -- skip NULL ##### ИНН Домены: [`inn.sql`](domains/inn.sql), [`inn10.sql`](domains/inn10.sql), [`inn12.sql`](domains/inn12.sql). -Функции: [`is_inn.sql`](functions/is/is_inn.sql), [`is_inn10.sql`](functions/is/is_inn10.sql), [`is_inn12.sql`](functions/is/is_inn12.sql). +Функции: [`is_inn10.sql`](functions/is/is_inn10.sql), [`is_inn12.sql`](functions/is/is_inn12.sql). ##### КПП Домен: [`kpp.sql`](domains/kpp.sql). From dbbe95b53b8d9414a3cfd720b63ebde949d2c1bb Mon Sep 17 00:00:00 2001 From: Rinat Mukhtarov Date: 2025年4月29日 09:26:58 +0300 Subject: [PATCH 010/145] =?UTF-8?q?=D0=9A=D0=B0=D0=BA=20=D1=83=D1=81=D0=BA?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D1=82=D1=8C=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D1=80=D0=BE=D0=BA?= =?UTF-8?q?=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20`INSERT=20...=20VALUES=20..?= =?UTF-8?q?.`=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index a802747..c91b982 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ **[Модификация пользовательских данных (DML)](#модификация-пользовательских-данных-dml)** 1. [Как добавить или обновить записи одним запросом (UPSERT)?](#как-добавить-или-обновить-записи-одним-запросом-upsert) 1. [Как сделать `INSERT ... ON CONFLICT ...` без увеличения последовательности для дубликатов?](#как-сделать-insert--on-conflict--без-увеличения-последовательности-для-дубликатов) + 1. [Как ускорить добавление строк через `INSERT ... VALUES ...`?](как-ускорить-добавление-строк-через-insert-values) 1. [Как модифицировать данные в нескольких таблицах и вернуть id затронутых записей в одном запросе?](#как-модифицировать-данные-в-нескольких-таблицах-и-вернуть-id-затронутых-записей-в-одном-запросе) 1. [Как модифицировать данные в связанных таблицах одним запросом?](#как-модифицировать-данные-в-связанных-таблицах-одним-запросом) 1. [Как добавить запись с id, значение которого нужно сохранить ещё в другом поле в том же INSERT запросе?](#как-добавить-запись-с-id-значение-которого-нужно-сохранить-ещё-в-другом-поле-в-том-же-insert-запросе) @@ -1242,6 +1243,36 @@ returning id; table t1_id_seq; -- "last_value" is 3 ``` +### Как ускорить добавление строк через `INSERT ... VALUES ...`? + +Вместо запроса типа + +```sql +INSERT INTO t1 (col1, col2, col3) +VALUES + (1,ドル 2,ドル 3ドル), + (4,ドル 5,ドル 6ドル), + ..., + (2998,ドル 2999,ドル 3000ドル); +``` + +используйте запрос +```sql +INSERT INTO t1 (col1, col2, col3) + SELECT * + FROM unnest( + 1ドル::timestamptz[], + 2ドル::text[], + 3ドル::float8[] +) +``` + +Трюк в том, что на `INSERT VALUES` тратится много времени на планирование запроса (обрабатывается каждое значение), а на `INSERT UNNEST` нет. + +Детальная информация: +* https://www.timescale.com/blog/boosting-postgres-insert-performance +* https://www.timescale.com/blog/benchmarking-postgresql-batch-ingest + ### Как модифицировать данные в нескольких таблицах и вернуть id затронутых записей в одном запросе? ```sql From 4b26a61de3dbf0cbcf42c448806fea706aa6c7b9 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月12日 12:01:27 +0300 Subject: [PATCH 011/145] Create links.md --- functions/json/links.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 functions/json/links.md diff --git a/functions/json/links.md b/functions/json/links.md new file mode 100644 index 0000000..4eedb8f --- /dev/null +++ b/functions/json/links.md @@ -0,0 +1 @@ +* [Интересные случаи использования JSON (Иван Панченко, Postgres Professional)](https://pgconf.ru/media/2020/02/04/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%B5%D1%81%D0%BD%D1%8B%D0%B5%20%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B8%20JSON.pdf) From 98c41651b669745e35f65a89f79cccb9eb97d4d0 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月12日 12:03:33 +0300 Subject: [PATCH 012/145] Rename links.md to LINKS.md --- functions/json/{links.md => LINKS.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename functions/json/{links.md => LINKS.md} (100%) diff --git a/functions/json/links.md b/functions/json/LINKS.md similarity index 100% rename from functions/json/links.md rename to functions/json/LINKS.md From 7d7565627215c3b51a315c699dc7c94ea79d67b4 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月12日 12:03:55 +0300 Subject: [PATCH 013/145] Rename links.md to LINKS.md --- functions/bit/{links.md => LINKS.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename functions/bit/{links.md => LINKS.md} (56%) diff --git a/functions/bit/links.md b/functions/bit/LINKS.md similarity index 56% rename from functions/bit/links.md rename to functions/bit/LINKS.md index e8f2b30..2c258fe 100644 --- a/functions/bit/links.md +++ b/functions/bit/LINKS.md @@ -1,2 +1,2 @@ * http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit -* https://github.com/sean-/postgresql-varint \ No newline at end of file +* https://github.com/sean-/postgresql-varint From 6cc231f47250e11ded78eab4355eb72d4817b389 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月12日 12:04:25 +0300 Subject: [PATCH 014/145] Rename links.md to LINKS.md --- functions/fibonacci/{links.md => LINKS.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename functions/fibonacci/{links.md => LINKS.md} (100%) diff --git a/functions/fibonacci/links.md b/functions/fibonacci/LINKS.md similarity index 100% rename from functions/fibonacci/links.md rename to functions/fibonacci/LINKS.md From 703e5aca72bcd2b867fa41eb9f12340ae2624e95 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月13日 10:31:35 +0300 Subject: [PATCH 015/145] Delete TEMP.md --- TEMP.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 TEMP.md diff --git a/TEMP.md b/TEMP.md deleted file mode 100644 index 2248b30..0000000 --- a/TEMP.md +++ /dev/null @@ -1 +0,0 @@ -`test ! -f "$FILE_SRC" && echo "Error: file '$FILE_SRC' does not exist!">&2 && exit 1` From aceb3ad22ce0781f558b3400b3564a6744b27d7c Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月13日 11:23:24 +0300 Subject: [PATCH 016/145] TODO added --- functions/unicode_unescape.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/functions/unicode_unescape.sql b/functions/unicode_unescape.sql index 620da84..e7f1719 100644 --- a/functions/unicode_unescape.sql +++ b/functions/unicode_unescape.sql @@ -7,6 +7,8 @@ create or replace function public.unicode_unescape(text) set search_path = '' as $func$ + -- input string - only as \uXXXX sequence + -- TODO validate format and return NULL for invalid strings? select concat('"', 1,ドル '"')::jsonb->>0; $func$; From 861f6175adbfa05faed35f91cba299e32aedb21f Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月13日 11:26:38 +0300 Subject: [PATCH 017/145] Update unicode_unescape.sql --- functions/unicode_unescape.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/unicode_unescape.sql b/functions/unicode_unescape.sql index e7f1719..6bd48fb 100644 --- a/functions/unicode_unescape.sql +++ b/functions/unicode_unescape.sql @@ -8,7 +8,7 @@ create or replace function public.unicode_unescape(text) as $func$ -- input string - only as \uXXXX sequence - -- TODO validate format and return NULL for invalid strings? + -- TODO validate format and return NULL for invalid strings (see string_to_jsonb.sql)? select concat('"', 1,ドル '"')::jsonb->>0; $func$; From 5f906f559c545ba76d65614a8114668137c8365d Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月13日 11:30:05 +0300 Subject: [PATCH 018/145] Update unicode_unescape.sql --- functions/unicode_unescape.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/unicode_unescape.sql b/functions/unicode_unescape.sql index 6bd48fb..1fee57d 100644 --- a/functions/unicode_unescape.sql +++ b/functions/unicode_unescape.sql @@ -8,7 +8,7 @@ create or replace function public.unicode_unescape(text) as $func$ -- input string - only as \uXXXX sequence - -- TODO validate format and return NULL for invalid strings (see string_to_jsonb.sql)? + -- TODO validate format by regexp and return NULL for invalid strings? select concat('"', 1,ドル '"')::jsonb->>0; $func$; From b3fdaec731e802f7433b8eacc4ebd02d88d9f895 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月20日 19:27:08 +0300 Subject: [PATCH 019/145] Update README.md --- pg_receivewal/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pg_receivewal/README.md b/pg_receivewal/README.md index 9a808b9..9e9dada 100644 --- a/pg_receivewal/README.md +++ b/pg_receivewal/README.md @@ -92,6 +92,7 @@ postgresql: 1. https://www.cybertec-postgresql.com/en/never-lose-a-postgresql-transaction-with-pg_receivewal/ 1. SystemD + 1. https://systemd-by-example.com/ 1. https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html 1. https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html 1. https://www.youtube.com/watch?v=4s3mi-16vgI From b3cef775b994d738b5424ee97f4ed59ba8046e47 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月20日 19:40:11 +0300 Subject: [PATCH 020/145] Update README.md --- pg_archive_log/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index e9a3f54..af9b07b 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -2,7 +2,7 @@ ## Описание -Systemd сервис, который запускается 1 раз в сутки. +[Systemd](https://en.wikipedia.org/wiki/Systemd) сервис, который запускается 1 раз в сутки. 1. удаляет файлы старше N дней 2. удаляет файлы нулевого размера старше K дней 3. архивирует несжатые файлы старше М дней в формат `zstd`, если размер файла> S килобайт From a0214f29db031c7911c9d53b4d2cd1e557b09c1a Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月20日 19:41:00 +0300 Subject: [PATCH 021/145] RandomizedDelaySec added --- pg_archive_log/pg_archive_log.timer | 1 + 1 file changed, 1 insertion(+) diff --git a/pg_archive_log/pg_archive_log.timer b/pg_archive_log/pg_archive_log.timer index 0749caf..bd76063 100644 --- a/pg_archive_log/pg_archive_log.timer +++ b/pg_archive_log/pg_archive_log.timer @@ -4,6 +4,7 @@ Description=PostgreSQL archive log timer [Timer] Unit=pg_archive_log.service OnCalendar=*-*-* 05:15:00 +RandomizedDelaySec=3600 Persistent=true [Install] From 4ee32770ee299334cfc50fd8c6cf96bfc87b3205 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月20日 19:41:21 +0300 Subject: [PATCH 022/145] RandomizedDelaySec added --- pg_backup/pg_backup.timer | 1 + 1 file changed, 1 insertion(+) diff --git a/pg_backup/pg_backup.timer b/pg_backup/pg_backup.timer index 89df918..cee2ae4 100644 --- a/pg_backup/pg_backup.timer +++ b/pg_backup/pg_backup.timer @@ -4,6 +4,7 @@ Description=PostgreSQL backup timer [Timer] Unit=pg_backup.service OnCalendar=*-*-* 23:55:00 +RandomizedDelaySec=3600 Persistent=true [Install] From 03dc98bb3b157e1094e810cf687aa0e4d6d95e92 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月20日 19:43:04 +0300 Subject: [PATCH 023/145] systemd link added --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index c63f915..00db55b 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -2,7 +2,7 @@ ## Как это работает? -На каждом сервере СУБД по расписанию (обычно 1 раз в сутки) запускается сервис для создания резервных копий СУБД. +На каждом сервере СУБД по расписанию (обычно 1 раз в сутки) запускается [systemd](https://en.wikipedia.org/wiki/Systemd) сервис для создания резервных копий СУБД. Резервные копии создаются только с мастер СУБД, а реплики игнорируются. From c8d196f0879c63001750674c0d362c139e946508 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月21日 21:11:46 +0300 Subject: [PATCH 024/145] Update pg_archive_log.service --- pg_archive_log/pg_archive_log.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_archive_log/pg_archive_log.service b/pg_archive_log/pg_archive_log.service index d3a577b..b8bd524 100644 --- a/pg_archive_log/pg_archive_log.service +++ b/pg_archive_log/pg_archive_log.service @@ -19,7 +19,7 @@ ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +2 -size 0 -delete # архивируем несжатые файлы старше M дней (достаточно одного потока zstd, т.к. файлы относительно небольшие) # не ставьте большой уровень компрессии, это приводит к большому потреблению CPU, а экономия на размере файла несущественная -ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size +100k ! -name "*.zst" -exec ionice -c2 -n7 nice -n19 zstd -9 -q --rm {} \; +ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size +100k ! -name "*.zst" -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm {} \; [Install] WantedBy=multi-user.target From c6e9725becec6299685e9d036aad5821e9984c74 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月21日 21:17:12 +0300 Subject: [PATCH 025/145] pretect concurrent write WAL files to shared network folder --- pg_backup/archive_command.sh | 87 +++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/pg_backup/archive_command.sh b/pg_backup/archive_command.sh index 532c217..46ace33 100644 --- a/pg_backup/archive_command.sh +++ b/pg_backup/archive_command.sh @@ -1,31 +1,76 @@ #!/bin/bash - -# заглушка, т.к. для archive_mode = 'on' требуется перезагрузка СУБД + +# заглушка, т.к. при изменении значения параметра archive_mode требуется перезагрузка СУБД # exit 0 - -# проверяем, скрипт должен запускаться с двумя параметрами + +function log() { + # echo $(date --rfc-3339=ns) $(hostname -s) $(basename "$SRC_FILE") "1ドル" &>> "$WAL_DIR/archive_wal.log" + return 0 +} + +WAL_DIR="/mnt/backup_db/archive_wal/cluster" +SRC_FILE="2ドル" # откуда будем читать WAL файл +DST_FILE="$WAL_DIR/1ドル.zst" # куда будем сохранять WAL файл +LOCK_FILE="$WAL_DIR/1ドル.lock" # этот файл создаётся перед архиваций WAL файла и удаляется после + +log "check" + +# скрипт должен запускаться с двумя параметрами test "$#" -ne 2 && echo "Error: 2 number of parameters expected, $# given">&2 && exit 2 - -FILE_SRC="2ドル" -FILE_DST="/mnt/backup_db/archive_wal/cluster/1ドル.zst" - -test ! -f "$FILE_SRC" && echo "Error: file '$FILE_SRC' does not exist!">&2 && exit 1 -test -f "$FILE_DST" && exit - +test ! -f "$SRC_FILE" && echo "Error: WAL file '$SRC_FILE' does not exist!">&2 && exit 1 +test -f "$DST_FILE" && test ! -f "$LOCK_FILE" && log "exists" && exit 0 + +log "does not exist" + +: <<'comment' + Для надёжности архивирование WAL файлов могут настроить на мастере и репликах через параметр archive_mode=always. + Запрещаем от разных экземпляров СУБД конкурентную запись WAL файлов в общие сетевые папки. + Например, в основном и резервном ЦОДе могут быть разные сетевые папки. + Дополнительные файлы и проверки нужны для решения проблемы переполнения диска или недоступности сетевого соединения, + когда WAL файл может не записаться совсем или записаться только частично. +COMMENT + +SRV_FILE="$WAL_DIR/archive_server.$(hostname -s)" # сервер, на котором планируется архивирование WAL файла +touch -m "$SRV_FILE" || exit # создаём файл или обновляем дату модификации файла + +# разрешаем архивацию WAL файла только при наличии жёстких связанных ссылок (единый inode) между файлами $SRV_FILE и $LOCK_FILE +# для отладки и просмотра кол-ва жёстких ссылок на файл используйте утилиту stat (не используйте ls, она кеширует информацию) +if ln -T "$SRV_FILE" "$LOCK_FILE" &> /dev/null; then + # если удалось создать файл $LOCK_FILE, то только этот (основной) процесс будет архивировать WAL файл (первая попытка) + log "locked now" +elif test "$SRV_FILE" -ef "$LOCK_FILE"; then + # если файлы имеют одинаковый inode, то только этот (основной) процесс будет архивировать WAL файл (повторная попытка) + log "locked already" +else + # иначе архивировать WAL файл пытается конкурирующий процесс, ждём несколько секунд завершения работы основного процесса + for i in {1..50}; do + log "waiting i=$i" + sleep 0.2 + test -f "$DST_FILE" && test ! -f "$LOCK_FILE" && log "appeared" && exit 0 + done + # не дождались, значит основной процесс сломался, WAL файл мог сохраниться только частично + # передаём управление другому конкурирующему процессу, который станет основным (при повторном вызове этого скрипта) + rm -f "$DST_FILE" && rm -f "$LOCK_FILE" # очерёдность удаления файлов важна + MESSAGE="waiting timeout, deleted, unlocked" + log "$MESSAGE" + echo "Error: WAL file '$DST_FILE' $MESSAGE">&2 + exit 1 +fi + # кол-во потоков сжатия ZSTD_THREADS=$(echo "$(nproc) / 4 + 1" | bc) - -# кол-во файлов в очереди на архивирование -ARCHIVE_STATUS_DIR=$(dirname $FILE_SRC)/archive_status + +# подсчитываем кол-во WAL файлов в очереди на архивирование +ARCHIVE_STATUS_DIR=$(dirname "$SRC_FILE")/archive_status WAL_FILES_QUEUE=$(find "$ARCHIVE_STATUS_DIR" -maxdepth 1 -type f -name "*.ready" -printf "." | wc --bytes) - -# чем больше файлов в очереди, тем меньше степень сжатия (но больше скорость сжатия и размер сжатого файла) -STEP=3 + +STEP=2 +# чем больше WAL файлов в очереди, тем меньше степень сжатия (но больше скорость сжатия и размер сжатого файла) # не ставьте большой уровень компрессии, это приводит к большому потреблению CPU, а экономия на размере файла несущественная ZSTD_LEVEL=$(echo "(9 * ${STEP} - ${WAL_FILES_QUEUE}) / ${STEP}" | bc) test "$ZSTD_LEVEL" -lt 1 && ZSTD_LEVEL=1 - -# архивируем файл + +# архивируем WAL файл # в zstd без флага -B1M используются не все ядра (из-за небольшого размера файла?) -# в zstd флаг -f использовать нельзя, т.к. если файл существует, то должна быть ошибка (защита от дурака от перезаписи нужного файла) -ionice -c2 -n7 nice -n19 zstd -q -${ZSTD_LEVEL} -T${ZSTD_THREADS} -B1M "$FILE_SRC" -o "$FILE_DST" +ionice -c2 -n7 -- nice -n19 -- zstd -q -f -${ZSTD_LEVEL} -T${ZSTD_THREADS} -B1M "$SRC_FILE" -o "$DST_FILE" \ + && rm -f "$LOCK_FILE" && log "saved, unlocked" From 315b1d56a7c8e3c4a69ca4910715133af2aec469 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年5月23日 23:15:04 +0300 Subject: [PATCH 026/145] Update archive_command.sh --- pg_backup/archive_command.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/archive_command.sh b/pg_backup/archive_command.sh index 46ace33..c13e64a 100644 --- a/pg_backup/archive_command.sh +++ b/pg_backup/archive_command.sh @@ -31,7 +31,7 @@ log "does not exist" COMMENT SRV_FILE="$WAL_DIR/archive_server.$(hostname -s)" # сервер, на котором планируется архивирование WAL файла -touch -m "$SRV_FILE" || exit # создаём файл или обновляем дату модификации файла +touch -m "$SRV_FILE" || exit # создаём файл или обновляем дату модификации файла, если файл существует # разрешаем архивацию WAL файла только при наличии жёстких связанных ссылок (единый inode) между файлами $SRV_FILE и $LOCK_FILE # для отладки и просмотра кол-ва жёстких ссылок на файл используйте утилиту stat (не используйте ls, она кеширует информацию) From 86aef388aa048450d366d8f217c80ca00bc6ae71 Mon Sep 17 00:00:00 2001 From: Rinat Date: Wed, 4 Jun 2025 13:38:36 +0300 Subject: [PATCH 027/145] Discard millisecond part from timestamp and interval --- DBA/pg_basebackup_progress_bar.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DBA/pg_basebackup_progress_bar.sql b/DBA/pg_basebackup_progress_bar.sql index 995b9e0..3a8caba 100644 --- a/DBA/pg_basebackup_progress_bar.sql +++ b/DBA/pg_basebackup_progress_bar.sql @@ -7,17 +7,17 @@ select pg_size_pretty(b.backup_streamed) as pretty_backup_streamed, pg_size_pretty(b.backup_total) as pretty_backup_total, - a.query_start, + a.query_start::timestamp(0) as query_start, e.duration, round(e.progress_percent, 4) as progress_percent, bytes_per_second, - (e2.estimated_duration || 'sec')::interval as estimated_duration, - a.query_start + (e2.estimated_duration || 'sec')::interval as estimated_query_end + (e2.estimated_duration || 'sec')::interval(0) as estimated_duration, + a.query_start + (e2.estimated_duration || 'sec')::interval(0) as estimated_query_end from pg_stat_progress_basebackup as b inner join pg_stat_activity as a on a.pid = b.pid cross join lateral ( select - NOW() - a.query_start as duration, + (NOW() - a.query_start)::interval(0) as duration, b.backup_streamed * 100.0 / b.backup_total as progress_percent ) as e cross join lateral ( From 50b2dded7b18be63a1e3bf0302b37f211604202f Mon Sep 17 00:00:00 2001 From: Rinat Date: Fri, 6 Jun 2025 20:03:02 +0300 Subject: [PATCH 028/145] Update pg_archive_log.service --- pg_archive_log/pg_archive_log.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_archive_log/pg_archive_log.service b/pg_archive_log/pg_archive_log.service index b8bd524..a32fcaa 100644 --- a/pg_archive_log/pg_archive_log.service +++ b/pg_archive_log/pg_archive_log.service @@ -19,7 +19,7 @@ ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +2 -size 0 -delete # архивируем несжатые файлы старше M дней (достаточно одного потока zstd, т.к. файлы относительно небольшие) # не ставьте большой уровень компрессии, это приводит к большому потреблению CPU, а экономия на размере файла несущественная -ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size +100k ! -name "*.zst" -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm {} \; +ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size +100k ! -name "*.zst" -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; [Install] WantedBy=multi-user.target From ca1c42456756a3d48ae35d419e3f7a3680c52624 Mon Sep 17 00:00:00 2001 From: Rinat Date: Sun, 8 Jun 2025 20:54:11 +0300 Subject: [PATCH 029/145] zstd -1 no nice --- pg_backup/archive_command.sh | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pg_backup/archive_command.sh b/pg_backup/archive_command.sh index c13e64a..b284474 100644 --- a/pg_backup/archive_command.sh +++ b/pg_backup/archive_command.sh @@ -1,27 +1,27 @@ #!/bin/bash - + # заглушка, т.к. при изменении значения параметра archive_mode требуется перезагрузка СУБД # exit 0 - + function log() { # echo $(date --rfc-3339=ns) $(hostname -s) $(basename "$SRC_FILE") "1ドル" &>> "$WAL_DIR/archive_wal.log" return 0 } - + WAL_DIR="/mnt/backup_db/archive_wal/cluster" SRC_FILE="2ドル" # откуда будем читать WAL файл DST_FILE="$WAL_DIR/1ドル.zst" # куда будем сохранять WAL файл LOCK_FILE="$WAL_DIR/1ドル.lock" # этот файл создаётся перед архиваций WAL файла и удаляется после - + log "check" - + # скрипт должен запускаться с двумя параметрами test "$#" -ne 2 && echo "Error: 2 number of parameters expected, $# given">&2 && exit 2 test ! -f "$SRC_FILE" && echo "Error: WAL file '$SRC_FILE' does not exist!">&2 && exit 1 test -f "$DST_FILE" && test ! -f "$LOCK_FILE" && log "exists" && exit 0 - + log "does not exist" - + : <<'comment' Для надёжности архивирование WAL файлов могут настроить на мастере и репликах через параметр archive_mode=always. Запрещаем от разных экземпляров СУБД конкурентную запись WAL файлов в общие сетевые папки. @@ -32,7 +32,7 @@ COMMENT SRV_FILE="$WAL_DIR/archive_server.$(hostname -s)" # сервер, на котором планируется архивирование WAL файла touch -m "$SRV_FILE" || exit # создаём файл или обновляем дату модификации файла, если файл существует - + # разрешаем архивацию WAL файла только при наличии жёстких связанных ссылок (единый inode) между файлами $SRV_FILE и $LOCK_FILE # для отладки и просмотра кол-ва жёстких ссылок на файл используйте утилиту stat (не используйте ls, она кеширует информацию) if ln -T "$SRV_FILE" "$LOCK_FILE" &> /dev/null; then @@ -56,21 +56,22 @@ else echo "Error: WAL file '$DST_FILE' $MESSAGE">&2 exit 1 fi - + # кол-во потоков сжатия ZSTD_THREADS=$(echo "$(nproc) / 4 + 1" | bc) - + # подсчитываем кол-во WAL файлов в очереди на архивирование ARCHIVE_STATUS_DIR=$(dirname "$SRC_FILE")/archive_status WAL_FILES_QUEUE=$(find "$ARCHIVE_STATUS_DIR" -maxdepth 1 -type f -name "*.ready" -printf "." | wc --bytes) - + STEP=2 # чем больше WAL файлов в очереди, тем меньше степень сжатия (но больше скорость сжатия и размер сжатого файла) # не ставьте большой уровень компрессии, это приводит к большому потреблению CPU, а экономия на размере файла несущественная ZSTD_LEVEL=$(echo "(9 * ${STEP} - ${WAL_FILES_QUEUE}) / ${STEP}" | bc) test "$ZSTD_LEVEL" -lt 1 && ZSTD_LEVEL=1 - + # архивируем WAL файл # в zstd без флага -B1M используются не все ядра (из-за небольшого размера файла?) -ionice -c2 -n7 -- nice -n19 -- zstd -q -f -${ZSTD_LEVEL} -T${ZSTD_THREADS} -B1M "$SRC_FILE" -o "$DST_FILE" \ - && rm -f "$LOCK_FILE" && log "saved, unlocked" +COMMAND="zstd -q -f -${ZSTD_LEVEL} -T${ZSTD_THREADS} -B1M '$SRC_FILE' -o '$DST_FILE'" +test $ZSTD_LEVEL -gt 1 && COMMAND="ionice -c2 -n7 -- nice -n19 -- $COMMAND" +$COMMAND && rm -f "$LOCK_FILE" && log "saved, unlocked" \ No newline at end of file From 0340d38624d541a11f8c12b357b6f0b2f12b4e26 Mon Sep 17 00:00:00 2001 From: Rinat Date: Mon, 9 Jun 2025 11:28:11 +0300 Subject: [PATCH 030/145] Update README.md --- bashrc/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bashrc/README.md b/bashrc/README.md index 6174176..c5a72b7 100644 --- a/bashrc/README.md +++ b/bashrc/README.md @@ -25,6 +25,8 @@ ```bash # Last version and documentation: https://github.com/rin-nas/postgresql-patterns-library/tree/master/bashrc +alias patronictl='patronictl -c /etc/patroni/patroni.yaml' + export EDITOR=nano export HISTFILESIZE=5000 export HISTCONTROL="ignoredups" From 780d7566a92b91e9f283656dcf46eb90544fbef8 Mon Sep 17 00:00:00 2001 From: Rinat Date: Mon, 9 Jun 2025 11:28:59 +0300 Subject: [PATCH 031/145] Update README.md --- bashrc/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/bashrc/README.md b/bashrc/README.md index c5a72b7..fdad1c0 100644 --- a/bashrc/README.md +++ b/bashrc/README.md @@ -25,6 +25,7 @@ ```bash # Last version and documentation: https://github.com/rin-nas/postgresql-patterns-library/tree/master/bashrc +# https://patroni.readthedocs.io/ alias patronictl='patronictl -c /etc/patroni/patroni.yaml' export EDITOR=nano From f556cb72b46efee394f0e35056995880d6a70020 Mon Sep 17 00:00:00 2001 From: Rinat Date: Mon, 9 Jun 2025 11:34:28 +0300 Subject: [PATCH 032/145] Update README.md --- bashrc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bashrc/README.md b/bashrc/README.md index fdad1c0..102a811 100644 --- a/bashrc/README.md +++ b/bashrc/README.md @@ -26,7 +26,7 @@ # Last version and documentation: https://github.com/rin-nas/postgresql-patterns-library/tree/master/bashrc # https://patroni.readthedocs.io/ -alias patronictl='patronictl -c /etc/patroni/patroni.yaml' +alias patronictl='patronictl -c /etc/patroni/patroni.yml' export EDITOR=nano export HISTFILESIZE=5000 From 5643a23aeb489296043fc69cbceb7587a94cb824 Mon Sep 17 00:00:00 2001 From: Rinat Date: Mon, 9 Jun 2025 11:48:32 +0300 Subject: [PATCH 033/145] Update README.md --- bashrc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bashrc/README.md b/bashrc/README.md index 102a811..3debf7d 100644 --- a/bashrc/README.md +++ b/bashrc/README.md @@ -69,7 +69,7 @@ __prompt_command() { PS1+="${Cyan}@" PS1+="${Orange}\h" #host - PS1+="${Green}[$(hostname -I | xargs)] " # IP list + PS1+="${Green}[$(hostname -I | xargs)] " # IP list (useful for showing Virtual IP) PS1+="${Blue}\w" #directory PS1+="\n" From ada29064fcaed3894b11f238de9a05797891f424 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年6月10日 17:04:30 +0300 Subject: [PATCH 034/145] deadlock links added --- TODO.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 871fea0..6b06023 100644 --- a/TODO.md +++ b/TODO.md @@ -733,7 +733,9 @@ FROM WHERE tbl.ctid = lc.ctid; -- поиск по физической позиции записи ``` -https://habr.com/ru/companies/tensor/articles/567514/ +https://habr.com/ru/companies/tensor/articles/567514/ - Борем deadlock при пакетных UPDATE +https://dba.stackexchange.com/questions/323040/avoiding-deadlocks-when-locking-multiple-rows-without-using-nowait +https://stackoverflow.com/questions/22775150/how-to-simulate-deadlock-in-postgresql # Finding skewed data in Postgres From 3b8afafbcd5df2419b1728a1a123db44b70a1b3a Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年6月11日 19:01:21 +0300 Subject: [PATCH 035/145] error with quotation marks fixed, log command added, timeout 15s --- pg_backup/archive_command.sh | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/pg_backup/archive_command.sh b/pg_backup/archive_command.sh index b284474..d254c11 100644 --- a/pg_backup/archive_command.sh +++ b/pg_backup/archive_command.sh @@ -1,27 +1,27 @@ #!/bin/bash - + # заглушка, т.к. при изменении значения параметра archive_mode требуется перезагрузка СУБД # exit 0 - + function log() { # echo $(date --rfc-3339=ns) $(hostname -s) $(basename "$SRC_FILE") "1ドル" &>> "$WAL_DIR/archive_wal.log" return 0 } - + WAL_DIR="/mnt/backup_db/archive_wal/cluster" -SRC_FILE="2ドル" # откуда будем читать WAL файл +SRC_FILE="$(pwd)/2ドル" # откуда будем читать WAL файл DST_FILE="$WAL_DIR/1ドル.zst" # куда будем сохранять WAL файл LOCK_FILE="$WAL_DIR/1ドル.lock" # этот файл создаётся перед архиваций WAL файла и удаляется после - + log "check" - + # скрипт должен запускаться с двумя параметрами test "$#" -ne 2 && echo "Error: 2 number of parameters expected, $# given">&2 && exit 2 test ! -f "$SRC_FILE" && echo "Error: WAL file '$SRC_FILE' does not exist!">&2 && exit 1 test -f "$DST_FILE" && test ! -f "$LOCK_FILE" && log "exists" && exit 0 - + log "does not exist" - + : <<'comment' Для надёжности архивирование WAL файлов могут настроить на мастере и репликах через параметр archive_mode=always. Запрещаем от разных экземпляров СУБД конкурентную запись WAL файлов в общие сетевые папки. @@ -32,7 +32,7 @@ COMMENT SRV_FILE="$WAL_DIR/archive_server.$(hostname -s)" # сервер, на котором планируется архивирование WAL файла touch -m "$SRV_FILE" || exit # создаём файл или обновляем дату модификации файла, если файл существует - + # разрешаем архивацию WAL файла только при наличии жёстких связанных ссылок (единый inode) между файлами $SRV_FILE и $LOCK_FILE # для отладки и просмотра кол-ва жёстких ссылок на файл используйте утилиту stat (не используйте ls, она кеширует информацию) if ln -T "$SRV_FILE" "$LOCK_FILE" &> /dev/null; then @@ -43,7 +43,7 @@ elif test "$SRV_FILE" -ef "$LOCK_FILE"; then log "locked already" else # иначе архивировать WAL файл пытается конкурирующий процесс, ждём несколько секунд завершения работы основного процесса - for i in {1..50}; do + for i in {1..75}; do log "waiting i=$i" sleep 0.2 test -f "$DST_FILE" && test ! -f "$LOCK_FILE" && log "appeared" && exit 0 @@ -56,22 +56,23 @@ else echo "Error: WAL file '$DST_FILE' $MESSAGE">&2 exit 1 fi - + # кол-во потоков сжатия ZSTD_THREADS=$(echo "$(nproc) / 4 + 1" | bc) - + # подсчитываем кол-во WAL файлов в очереди на архивирование ARCHIVE_STATUS_DIR=$(dirname "$SRC_FILE")/archive_status WAL_FILES_QUEUE=$(find "$ARCHIVE_STATUS_DIR" -maxdepth 1 -type f -name "*.ready" -printf "." | wc --bytes) - + STEP=2 # чем больше WAL файлов в очереди, тем меньше степень сжатия (но больше скорость сжатия и размер сжатого файла) # не ставьте большой уровень компрессии, это приводит к большому потреблению CPU, а экономия на размере файла несущественная ZSTD_LEVEL=$(echo "(9 * ${STEP} - ${WAL_FILES_QUEUE}) / ${STEP}" | bc) test "$ZSTD_LEVEL" -lt 1 && ZSTD_LEVEL=1 - + # архивируем WAL файл # в zstd без флага -B1M используются не все ядра (из-за небольшого размера файла?) -COMMAND="zstd -q -f -${ZSTD_LEVEL} -T${ZSTD_THREADS} -B1M '$SRC_FILE' -o '$DST_FILE'" +COMMAND="zstd -q -f -${ZSTD_LEVEL} -T${ZSTD_THREADS} -B1M $SRC_FILE -o $DST_FILE" test $ZSTD_LEVEL -gt 1 && COMMAND="ionice -c2 -n7 -- nice -n19 -- $COMMAND" +log "command: $COMMAND" $COMMAND && rm -f "$LOCK_FILE" && log "saved, unlocked" \ No newline at end of file From f60c5c07a3bca0ec86ea777c9d0509dae7ef5dc4 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年6月11日 19:41:46 +0300 Subject: [PATCH 036/145] =?UTF-8?q?=D0=A0=D0=B5=D0=B7=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BE=D0=BF=D0=B8=D0=B8=20=D1=81?= =?UTF-8?q?=D0=BE=D0=B7=D0=B4=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20=D1=82=D0=B0?= =?UTF-8?q?=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pg_backup/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index 00db55b..bc5343a 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -4,9 +4,9 @@ На каждом сервере СУБД по расписанию (обычно 1 раз в сутки) запускается [systemd](https://en.wikipedia.org/wiki/Systemd) сервис для создания резервных копий СУБД. -Резервные копии создаются только с мастер СУБД, а реплики игнорируются. - -Если рез. копии создавать с одной из реплик, то есть риск значительного отставания (часы и дни). Это можно упустить (человеческий фактор) или не сразу возможно ликвидировать отставание и тогда будет создана неактуальная резервная копия! +Резервные копии создаются так: +* Если Patroni или jq не инсталлирован, то только с сервера СУБД мастер. +* Иначе только с одного сервера СУБД в каждом ЦОДе. Приоритет выбора сервера: синхронная реплика, мастер, асинхронная реплика (с отставанием не более 1000 МБ). > [!NOTE] > Резервная копия сжимается в формат `zstd` (16–25% от исходного размера файлов СУБД). Это позволяет экономить место на сетевом диске и уменьшить нагрузку на ввод-вывод. From fe99dc90c619f67831f7b07d8dcde45b24b82648 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年6月11日 19:42:19 +0300 Subject: [PATCH 037/145] Update pg_backup.service --- pg_backup/pg_backup.service | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pg_backup/pg_backup.service b/pg_backup/pg_backup.service index 0cf7a8c..979c0b2 100644 --- a/pg_backup/pg_backup.service +++ b/pg_backup/pg_backup.service @@ -1,13 +1,12 @@ [Unit] Description=PostgreSQL backup service - + [Service] User=postgres Group=postgres - -ExecCondition=echo "pg_backup: check if PostgreSQL is primary" -ExecCondition=/bin/bash -c "test f = $(psql --user=bkp_replicator --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align --command='select pg_is_in_recovery()')" + +ExecCondition=/bin/bash /var/lib/pgsql/pg_backup.sh ExecCondition ExecStart=/bin/bash /var/lib/pgsql/pg_backup.sh - + [Install] WantedBy=multi-user.target From d8cd3adfd45c6c8f7d8322fdcbf0374473155399 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年6月11日 19:43:01 +0300 Subject: [PATCH 038/145] Update pg_backup.sh --- pg_backup/pg_backup.sh | 106 ++++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 34 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index 7ef7cf2..6f1c3ff 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -1,19 +1,19 @@ #!/bin/bash # https://habr.com/ru/company/ruvds/blog/325522/ - Bash documentation - + # https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html # set -e - прекращает выполнение скрипта, если команда завершилась ошибкой # set -u - прекращает выполнение скрипта, если встретилась несуществующая переменная # set -x - выводит выполняемые команды в stdout перед выполнением (только для отладки, а то замусоривает журнал!) # set -o pipefail - прекращает выполнение скрипта, даже если одна из частей пайпа завершилась ошибкой set -euo pipefail - + SCRIPT_FILE=$(readlink -f "0ドル") SCRIPT_DIR=$(dirname "$SCRIPT_FILE") - + # Check syntax this file bash -n "${SCRIPT_FILE}" || exit - + # Colors Red='\e[1;31m' Green='\e[0;32m' @@ -25,13 +25,13 @@ Cyan='\e[0;36m' Gray='\e[0;37m' White='\e[1;37m' Reset='\e[0m' - + # Colored messages echoerr() { echo -e "${Red}$@${Reset}" 1>&2; } echowarn() { echo -e "${Yellow}$@${Reset}" 1>&2; } echoinfo() { echo -e "${White}$@${Reset}" ; } echosucc() { echo -e "${Green}$@${Reset}" ; } - + elapsed() { local time_start=1ドル #time_start=$(date +%s) local time_end=2ドル #time_end=$(date +%s) @@ -44,64 +44,102 @@ elapsed() { local ds=$(echo "$dt3-60*$dm" | bc) printf '%dd:%02d:%02d:%02d' $dd $dh $dm $ds #day:hh:mm:ss } - + # include source "$SCRIPT_DIR/pg_backup.conf" - + # calculated variables -PG_BIN_DIR="/usr/pgsql-${PG_MAJOR_VERSION}/bin" FILENAME=${BACKUP_DIR}/$(date +%Y-%m-%d.%H-%M-%S).$(hostname) - + if test $(whoami) != "postgres"; then - echoerr "PostgreSQL backup: run script as user postgres, not $(whoami)!" + echoerr "pg_backup: run script as user postgres, not $(whoami)!" exit 1 elif ! grep -q -P "\b${PG_USERNAME}\b" /var/lib/pgsql/.pgpass; then - echoerr "File /var/lib/pgsql/.pgpass must contain rule for user '${PG_USERNAME}'" + echoerr "pg_backup: file /var/lib/pgsql/.pgpass must contain rule for user '${PG_USERNAME}'" exit 1 fi - -echoinfo "PostgreSQL backup: creating started" + +# вычисляем, с какого сервера СУБД будем делать резервную копию (Systemd service ExecCondition) +if test "${1:-}" = "ExecCondition"; then + if ! (command -v patronictl &> /dev/null && command -v jq &> /dev/null); then + echo 'pg_backup: check candidate by psql if PostgreSQL is primary (Patroni or jq is not installed)' + # test ! -f $PGDATA/standby.signal # deprecated + test f = $(psql --user=bkp_replicator --no-password --dbname=postgres --quiet --no-psqlrc \ + --pset=null=¤ --tuples-only --no-align --command='select pg_is_in_recovery()') + exit + fi + + echo 'pg_backup: check candidate by Patroni if PostgreSQL is "Sync Standby", "Leader", "Replica" (in order of priority)' + # https://jqlang.org/manual/ + # https://stackoverflow.com/questions/46070012/how-to-filter-an-array-of-json-objects-with-jq + # https://stackoverflow.com/questions/76476166/jq-sorting-by-value + # https://stackoverflow.com/questions/35540294/sort-descending-by-multiple-keys-in-jq + # https://stackoverflow.com/questions/1952404/linux-bash-multiple-variable-assignment + DC=$(hostname | grep -oP "^\w+") # текущий ЦОД + IFS=',' read -r MEMBER HOST ROLE <<< $(patronictl -c /etc/patroni/patroni.yml list --format=json \ + | jq -Mcr --arg DC $DC ' + map(select( + (.Member | startswith($DC + "-")) and + .State == ("streaming", "running") and + ."Lag in MB" < 1000 + )) + | sort_by(.Role != ("Sync Standby", "Leader", "Replica"), .Host) + | .[0] # LIMIT 1 + | [.Member, .Host, .Role] + | join(",") + ') + test -z $HOST && echoerr "pg_backup: no candidate found" && exit 1 + echo "pg_backup: backup will be from '$MEMBER' [$HOST] ($ROLE)" + test $(hostname) = "$MEMBER" + exit +elif test -n "${1:-}"; then + echoerr "pg_backup: unknown first parameter '${1:-}'" + exit 2 +fi + +echoinfo "pg_backup: creating started" TIME_START=$(date +%s) #время в Unixtime - + # создаём директории, если их ещё нет mkdir -p ${BACKUP_DIR} ${WAL_DIR} - -# создаём физический бэкап + +# создаём физическую резервную копию # zstd adapt compression level depending on I/O conditions, mainly how fast it can write the output # В zstd v1.4.4 плохо работает адаптивный режим с кол-вом потоков> 1 (--adapt -T), постепенно увеличивается степень сжатия и длительность работы. -# Для многопоточного режима лучше явно поставить степень сжатия. +# Для многопоточного режима лучше явно поставить степень сжатия 5, которая получена опытным путём. +# Это баланс между скоростью работы zstd, размером сжатого файла, скоростью записи на сетевой диск с учётом его нагрузки другими процессами. ZSTD_THREADS=$(echo "$(nproc) / 2.5 + 1" | bc) ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar --pgdata=- \ - | ionice -c2 -n7 nice -n19 zstd -q -T${ZSTD_THREADS} -5 -o ${FILENAME}.pg_basebackup.tar.zst - -# создаём логический бэкап (deprecated) + | ionice -c2 -n7 -- nice -n19 -- zstd -q -T${ZSTD_THREADS} -5 -o ${FILENAME}.pg_basebackup.tar.zst + +# создаём логическую резервную копию (deprecated) # ${PG_BIN_DIR}/pg_dumpall --username=${PG_USERNAME} --no-password | zstd -q -T${ZSTD_THREADS} -5 -o ${FILENAME}.sql.zst - + TIME_END=$(date +%s) #время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) - -echosucc "PostgreSQL backup: created successfully, duration: $TIME_ELAPSED (day:hh:mm:ss)" - + +echosucc "pg_backup: created successfully, duration: $TIME_ELAPSED (day:hh:mm:ss)" + # удаляем архивные резервные копии старше N дней (папки и файлы рекурсивно) -echo "PostgreSQL backup: deleting backup files older than ${BACKUP_AGE_DAYS} days" +echo "pg_backup: deleting backup files older than ${BACKUP_AGE_DAYS} days" find ${BACKUP_DIR} -mindepth 1 -mtime +${BACKUP_AGE_DAYS} -delete - + # удаляем архивные WAL файлы старше N дней -echo "PostgreSQL backup: detect oldest kept WAL file for ${BACKUP_AGE_DAYS} days" +echo "pg_backup: detect oldest kept WAL file for ${BACKUP_AGE_DAYS} days" WAL_OLD_FILE=$(find ${WAL_DIR} -maxdepth 1 -mtime +${BACKUP_AGE_DAYS} -type f -printf "%C@ %f\n" | sort -n | tail -n 1 | cut -d" " -f2) if test -z "${WAL_OLD_FILE}"; then - echo "PostgreSQL backup: WAL old file is not found" + echo "pg_backup: WAL old file is not found" else - echo "PostgreSQL backup: WAL old file is ${WAL_OLD_FILE}" + echo "pg_backup: WAL old file is ${WAL_OLD_FILE}" WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP "^\S+") - echo "PostgreSQL backup: Before cleanup WAL dir size: ${WAL_DIR_SIZE}" + echo "pg_backup: Before cleanup WAL dir size: ${WAL_DIR_SIZE}" WAL_OLD_FILE_EXT=$(echo "${WAL_OLD_FILE}" | grep -oP "\.[a-z\d]+$") # compressed files support (.gz, .zst, .lz4) ${PG_BIN_DIR}/pg_archivecleanup -x "${WAL_OLD_FILE_EXT}" "${WAL_DIR}" "${WAL_OLD_FILE}" WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP "^\S+") - echo "PostgreSQL backup: After cleanup WAL dir size: ${WAL_DIR_SIZE}" + echo "pg_backup: After cleanup WAL dir size: ${WAL_DIR_SIZE}" fi - -echosucc "PostgreSQL backup: done" + +echosucc "pg_backup: done" From 83812cef74906f06c3a03a715f997ed2231d0881 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年6月27日 17:42:59 +0300 Subject: [PATCH 039/145] Update pg_dump_restore.md --- experiments/pg_dump_restore.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/experiments/pg_dump_restore.md b/experiments/pg_dump_restore.md index c0ff88f..2e604f0 100644 --- a/experiments/pg_dump_restore.md +++ b/experiments/pg_dump_restore.md @@ -1,8 +1,10 @@ -# Эксперимент по созданию и восстановлению дампов БД в разных форматах с разным сжатием +# Эксперимент по созданию и воссозданию дампов БД ## Введение -Здесь рассматривается логическая, а не физическая резервная копия БД +Рассматривается логическая, а не физическая резервная копия БД. + +Оцениваются разные форматы дампов, разные архиваторы, степени сжатия, однопоточное и многопоточное сжатие. ## Размер БД From 288edd83d6d104806aff6d7111a6917df7ec7dc6 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年6月27日 17:47:49 +0300 Subject: [PATCH 040/145] Update pg_dump_restore.md --- experiments/pg_dump_restore.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/experiments/pg_dump_restore.md b/experiments/pg_dump_restore.md index 2e604f0..d7fd34b 100644 --- a/experiments/pg_dump_restore.md +++ b/experiments/pg_dump_restore.md @@ -1,8 +1,12 @@ -# Эксперимент по созданию и воссозданию дампов БД +# Эксперимент по созданию дампов БД и воссозданию БД из дампов ## Введение -Рассматривается логическая, а не физическая резервная копия БД. +Рассматривается логическая, а не физическая резервная копия (бекап) БД. + +Логический бекап +* использует транзакцию с уровнем изоляции Repeatable Read и удерживает снимок СУБД (snapshot) +* выгружает из СУБД схему и данные в виде SQL команд в текстовые файлы Оцениваются разные форматы дампов, разные архиваторы, степени сжатия, однопоточное и многопоточное сжатие. From 829ce1b02d5f4d324831dcd6d423828057529196 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年6月27日 17:49:36 +0300 Subject: [PATCH 041/145] Update pg_dump_restore.md --- experiments/pg_dump_restore.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experiments/pg_dump_restore.md b/experiments/pg_dump_restore.md index d7fd34b..f071b24 100644 --- a/experiments/pg_dump_restore.md +++ b/experiments/pg_dump_restore.md @@ -6,7 +6,7 @@ Логический бекап * использует транзакцию с уровнем изоляции Repeatable Read и удерживает снимок СУБД (snapshot) -* выгружает из СУБД схему и данные в виде SQL команд в текстовые файлы +* выгружает из СУБД схему и данные в виде SQL команд в текстовые файлы (дамп) Оцениваются разные форматы дампов, разные архиваторы, степени сжатия, однопоточное и многопоточное сжатие. From 2b945ed4f9d8d2ae90bb5fdddb56c79322371db6 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年6月27日 22:25:20 +0300 Subject: [PATCH 042/145] Update pg_dump_restore.md --- experiments/pg_dump_restore.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experiments/pg_dump_restore.md b/experiments/pg_dump_restore.md index f071b24..03e0dbb 100644 --- a/experiments/pg_dump_restore.md +++ b/experiments/pg_dump_restore.md @@ -4,7 +4,7 @@ Рассматривается логическая, а не физическая резервная копия (бекап) БД. -Логический бекап +Логическое бекапирование * использует транзакцию с уровнем изоляции Repeatable Read и удерживает снимок СУБД (snapshot) * выгружает из СУБД схему и данные в виде SQL команд в текстовые файлы (дамп) From eb599239791ea4beae42340b0e908b62a95a6624 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年6月27日 23:21:55 +0300 Subject: [PATCH 043/145] Update archive_command.sh --- pg_backup/archive_command.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pg_backup/archive_command.sh b/pg_backup/archive_command.sh index d254c11..921a2f0 100644 --- a/pg_backup/archive_command.sh +++ b/pg_backup/archive_command.sh @@ -64,7 +64,7 @@ ZSTD_THREADS=$(echo "$(nproc) / 4 + 1" | bc) ARCHIVE_STATUS_DIR=$(dirname "$SRC_FILE")/archive_status WAL_FILES_QUEUE=$(find "$ARCHIVE_STATUS_DIR" -maxdepth 1 -type f -name "*.ready" -printf "." | wc --bytes) -STEP=2 +STEP=3 # чем больше WAL файлов в очереди, тем меньше степень сжатия (но больше скорость сжатия и размер сжатого файла) # не ставьте большой уровень компрессии, это приводит к большому потреблению CPU, а экономия на размере файла несущественная ZSTD_LEVEL=$(echo "(9 * ${STEP} - ${WAL_FILES_QUEUE}) / ${STEP}" | bc) @@ -75,4 +75,4 @@ test "$ZSTD_LEVEL" -lt 1 && ZSTD_LEVEL=1 COMMAND="zstd -q -f -${ZSTD_LEVEL} -T${ZSTD_THREADS} -B1M $SRC_FILE -o $DST_FILE" test $ZSTD_LEVEL -gt 1 && COMMAND="ionice -c2 -n7 -- nice -n19 -- $COMMAND" log "command: $COMMAND" -$COMMAND && rm -f "$LOCK_FILE" && log "saved, unlocked" \ No newline at end of file +$COMMAND && rm -f "$LOCK_FILE" && log "saved, unlocked" From 1da7df47a0fdc3fa8028b06442346fd9ba24f3a2 Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 1 Jul 2025 19:32:38 +0300 Subject: [PATCH 044/145] Update archive_command.sh From 202b0a05e233339c2a2b97d6d9aae7feb07acba8 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年7月25日 11:33:05 +0300 Subject: [PATCH 045/145] Update README.md --- pg_receivewal/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pg_receivewal/README.md b/pg_receivewal/README.md index 9e9dada..702a446 100644 --- a/pg_receivewal/README.md +++ b/pg_receivewal/README.md @@ -29,8 +29,8 @@ **Инсталляция сервиса** ```bash # создаём файлы -sudo su - postgres -c "nano ~/.pgpass && chmod 600 ~/.pgpass" # в файле нужно сохранить пароль для пользователя bkp_replicator -sudo nano /etc/systemd/system/pg_receivewal@.service +sudo su - postgres -c "nano -c ~/.pgpass && chmod 600 ~/.pgpass" # в файле нужно сохранить пароль для пользователя bkp_replicator +sudo nano -c /etc/systemd/system/pg_receivewal@.service # PostgreSQL v14 sudo systemctl daemon-reload \ @@ -49,7 +49,7 @@ sudo systemctl status pg_receivewal@16 **Интеграция с Patroni** ```bash # разрешаем перезапускать сервис под пользователем postgres без пароля -sudo nano /etc/sudoers.d/permit_pgreceivewal +sudo nano -c /etc/sudoers.d/permit_pgreceivewal sudo su postgres -c "sudo /bin/systemctl restart pg_receivewal@14" # тестируем перезапуск # редактируем конфигурацию Patroni From d0b94a723fa069bd5730a6a7ccc87770d1a4868a Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年7月28日 21:55:15 +0300 Subject: [PATCH 046/145] Update LINKS.md --- LINKS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/LINKS.md b/LINKS.md index 3cd4bd4..6c0e77d 100644 --- a/LINKS.md +++ b/LINKS.md @@ -42,6 +42,7 @@ 1. https://github.com/supabase/pg_jsonschema - PostgreSQL extension providing JSON Schema validation 1. https://github.com/tembo-io/pgmq - Postgres Message Queue (PGMQ) 1. https://github.com/benbjohnson/litestream - standalone disaster recovery tool for SQLite + 1. https://github.com/ossc-db/pg_store_plans - Store execution plans like pg_stat_statements does for queries # StackOverflow 1. https://stackoverflow.com/questions/7923237/return-pre-update-column-values-using-sql-only From 9e3362cc61a9e81940f3893021a8e9cd244da247 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:22:26 +0300 Subject: [PATCH 047/145] Update README.md --- pg_archive_log/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index af9b07b..5a3ea2a 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -35,7 +35,8 @@ sudo systemctl status pg_archive_log.timer && \ sudo systemctl status pg_archive_log # получаем список активных таймеров, для pg_archive_log.timer д.б. указана дата-время следующего запуска! -systemctl list-timers +# получаем список активных таймеров, д.б. указана дата-время следующего запуска! +systemctl list-timers | grep -P 'NEXT|pg_archive_log' ``` **Файлы** From 60d824918a8d3bdbfefca44461ed209443b914c0 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:22:43 +0300 Subject: [PATCH 048/145] Update README.md --- pg_archive_log/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index 5a3ea2a..7cd9577 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -34,7 +34,6 @@ sudo systemctl start pg_archive_log sudo systemctl status pg_archive_log.timer && \ sudo systemctl status pg_archive_log -# получаем список активных таймеров, для pg_archive_log.timer д.б. указана дата-время следующего запуска! # получаем список активных таймеров, д.б. указана дата-время следующего запуска! systemctl list-timers | grep -P 'NEXT|pg_archive_log' ``` From 24f23eca8ddfe42bfb4626f11616d0dfd601220f Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:23:16 +0300 Subject: [PATCH 049/145] Update pg_archive_log.timer --- pg_archive_log/pg_archive_log.timer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_archive_log/pg_archive_log.timer b/pg_archive_log/pg_archive_log.timer index bd76063..ee900e6 100644 --- a/pg_archive_log/pg_archive_log.timer +++ b/pg_archive_log/pg_archive_log.timer @@ -3,7 +3,7 @@ Description=PostgreSQL archive log timer [Timer] Unit=pg_archive_log.service -OnCalendar=*-*-* 05:15:00 +OnCalendar=*-*-* 06:00:00 RandomizedDelaySec=3600 Persistent=true From ccd9ade45d463b5228c762b36b9ac91f7ada3ef9 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:23:41 +0300 Subject: [PATCH 050/145] Update pg_archive_log.service --- pg_archive_log/pg_archive_log.service | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pg_archive_log/pg_archive_log.service b/pg_archive_log/pg_archive_log.service index a32fcaa..406b71a 100644 --- a/pg_archive_log/pg_archive_log.service +++ b/pg_archive_log/pg_archive_log.service @@ -9,12 +9,12 @@ Environment="LOG_DIR=/var/log/postgresql/16" # создаём папки, если их ещё не было ExecStartPre=mkdir -p ${LOG_DIR} -ExecStartPre=chmod 700 ${LOG_DIR} +ExecStartPre=chmod 750 ${LOG_DIR} # удаляем файлы старше N дней ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +30 -delete - -# удаляем файлы нулевой длины старше K дней + +# удаляем файлы нулевого размера старше K дней ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +2 -size 0 -delete # архивируем несжатые файлы старше M дней (достаточно одного потока zstd, т.к. файлы относительно небольшие) From 07ffe1f88c7e2ef6ec64b44bae321f0c1ba2a8e2 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:29:01 +0300 Subject: [PATCH 051/145] Update archive_command.md --- pg_backup/archive_command.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pg_backup/archive_command.md b/pg_backup/archive_command.md index 2a995bd..bee4802 100644 --- a/pg_backup/archive_command.md +++ b/pg_backup/archive_command.md @@ -1,10 +1,10 @@ -# PostgreSQL: архивирование WAL файлов (archive_command) +# PostgreSQL: копирование WAL файлов в архив (archive_command) ## Введение [Документация](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#RUNTIME-CONFIG-WAL-ARCHIVING) -i При архивировании WAL файлы сжимаются в формат `zstd` (52–62% от исходного размера, даже если включен параметр wal_compression). Это позволяет экономить место на сетевом диске и уменьшить нагрузку на ввод-вывод. +i При архивировании [WAL файлы](https://postgrespro.ru/docs/postgresql/16/continuous-archiving) сжимаются в формат `zstd` (52–62% от исходного размера, даже если включен параметр [wal_compression](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-WAL-COMPRESSION)). Это позволяет экономить место на сетевом диске и уменьшить нагрузку на ввод-вывод. ⚠ Удаление неактуальных WAL файлов сделано в сервисе резервного копирования, см. "[Инсталляция сервиса резервного копирования PostgreSQL](README.md)" @@ -15,13 +15,13 @@ # создайте файл archive_command.sh sudo mkdir -p /mnt/backup_db/ && sudo chown postgres:postgres /mnt/backup_db/ \ && sudo su - postgres -c "mkdir -p /mnt/backup_db/archive_wal/cluster/ && chmod 700 /mnt/backup_db/archive_wal/{,cluster/}" \ - && sudo su - postgres -c "nano ~/archive_command.sh && chmod 700 ~/archive_command.sh && bash -n ~/archive_command.sh" \ - && sudo su - postgres -c "nano \$PGDATA/postgresql.conf" - + && sudo su - postgres -c "nano -c ~/archive_command.sh && chmod 700 ~/archive_command.sh && bash -n ~/archive_command.sh" \ + && sudo su - postgres -c "nano -c \$PGDATA/postgresql.conf" + # pg_hba.conf and postgresql.conf syntax check test -z "$(psql --user=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ --command='select * from pg_hba_file_rules where error is not null; select * from pg_file_settings where error is not null')" - + sudo systemctl restart postgresql-16 sudo systemctl status postgresql-16 ``` From d27cc8bc18e3782ad68e20a875af4b2d3d025f80 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:30:35 +0300 Subject: [PATCH 052/145] Update archive_command.sh From d8d60ed0f1f9756f0d07aeaef863a8c508fbfd23 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:31:50 +0300 Subject: [PATCH 053/145] Update pg_backup.timer --- pg_backup/pg_backup.timer | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pg_backup/pg_backup.timer b/pg_backup/pg_backup.timer index cee2ae4..a9ce9a0 100644 --- a/pg_backup/pg_backup.timer +++ b/pg_backup/pg_backup.timer @@ -1,11 +1,11 @@ [Unit] Description=PostgreSQL backup timer - + [Timer] Unit=pg_backup.service -OnCalendar=*-*-* 23:55:00 -RandomizedDelaySec=3600 +OnCalendar=*-*-* 00:00:00 +RandomizedDelaySec=7200 Persistent=true - + [Install] WantedBy=timers.target From 44d392925bb153285b85df9982c664b194f4cbaf Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:32:09 +0300 Subject: [PATCH 054/145] Update pg_backup.service From 055fdf6ed01501733fd62584268b4176d2731dc9 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:32:56 +0300 Subject: [PATCH 055/145] Create pg_backup_validate.timer --- pg_backup/pg_backup_validate.timer | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 pg_backup/pg_backup_validate.timer diff --git a/pg_backup/pg_backup_validate.timer b/pg_backup/pg_backup_validate.timer new file mode 100644 index 0000000..1901bda --- /dev/null +++ b/pg_backup/pg_backup_validate.timer @@ -0,0 +1,11 @@ +[Unit] +Description=PostgreSQL backup validate timer + +[Timer] +Unit=pg_backup_validate.service +OnCalendar=Wed *-*-* 03:00:00 +RandomizedDelaySec=7200 +Persistent=true + +[Install] +WantedBy=timers.target From 9702f59f82b623fe62f99fa943c546d0bea8f3f7 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:33:28 +0300 Subject: [PATCH 056/145] Create pg_backup_validate.service --- pg_backup/pg_backup_validate.service | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 pg_backup/pg_backup_validate.service diff --git a/pg_backup/pg_backup_validate.service b/pg_backup/pg_backup_validate.service new file mode 100644 index 0000000..8c3956c --- /dev/null +++ b/pg_backup/pg_backup_validate.service @@ -0,0 +1,12 @@ +[Unit] +Description=PostgreSQL backup validate service + +[Service] +User=postgres +Group=postgres + +ExecCondition=/bin/bash /var/lib/pgsql/pg_backup.sh ExecCondition standby +ExecStart=/bin/bash /var/lib/pgsql/pg_backup.sh validate + +[Install] +WantedBy=multi-user.target From 1fdef4f0345837638be3de6873a75bf70735b4bd Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:34:05 +0300 Subject: [PATCH 057/145] Update pg_backup.sh --- pg_backup/pg_backup.sh | 235 ++++++++++++++++++++++++++++++----------- 1 file changed, 176 insertions(+), 59 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index 6f1c3ff..2c7e2e5 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -1,19 +1,18 @@ #!/bin/bash # https://habr.com/ru/company/ruvds/blog/325522/ - Bash documentation - + # https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html # set -e - прекращает выполнение скрипта, если команда завершилась ошибкой # set -u - прекращает выполнение скрипта, если встретилась несуществующая переменная # set -x - выводит выполняемые команды в stdout перед выполнением (только для отладки, а то замусоривает журнал!) # set -o pipefail - прекращает выполнение скрипта, даже если одна из частей пайпа завершилась ошибкой set -euo pipefail - + SCRIPT_FILE=$(readlink -f "0ドル") SCRIPT_DIR=$(dirname "$SCRIPT_FILE") - -# Check syntax this file -bash -n "${SCRIPT_FILE}" || exit - + +bash -n "${SCRIPT_FILE}" || exit # Check syntax this file + # Colors Red='\e[1;31m' Green='\e[0;32m' @@ -25,13 +24,13 @@ Cyan='\e[0;36m' Gray='\e[0;37m' White='\e[1;37m' Reset='\e[0m' - + # Colored messages echoerr() { echo -e "${Red}$@${Reset}" 1>&2; } echowarn() { echo -e "${Yellow}$@${Reset}" 1>&2; } echoinfo() { echo -e "${White}$@${Reset}" ; } echosucc() { echo -e "${Green}$@${Reset}" ; } - + elapsed() { local time_start=1ドル #time_start=$(date +%s) local time_end=2ドル #time_end=$(date +%s) @@ -44,38 +43,40 @@ elapsed() { local ds=$(echo "$dt3-60*$dm" | bc) printf '%dd:%02d:%02d:%02d' $dd $dh $dm $ds #day:hh:mm:ss } - -# include -source "$SCRIPT_DIR/pg_backup.conf" - -# calculated variables -FILENAME=${BACKUP_DIR}/$(date +%Y-%m-%d.%H-%M-%S).$(hostname) - + +# меняем приоритет этого процесса ($$ - это его pid) на минимальный (дочерние процессы наследуют значение приоритета родительского процесса) +ionice -c 2 -n 7 -p $$ +renice -n 19 -p $$ + +source "$SCRIPT_DIR/pg_backup.conf" # include + +PG_PASS_FILE="$SCRIPT_DIR/.pgpass" if test $(whoami) != "postgres"; then echoerr "pg_backup: run script as user postgres, not $(whoami)!" exit 1 -elif ! grep -q -P "\b${PG_USERNAME}\b" /var/lib/pgsql/.pgpass; then - echoerr "pg_backup: file /var/lib/pgsql/.pgpass must contain rule for user '${PG_USERNAME}'" +elif ! grep -q -w "$PG_USERNAME" "$PG_PASS_FILE"; then + echoerr "pg_backup: file '$PG_PASS_FILE' must contain record for user '$PG_USERNAME'" exit 1 fi - -# вычисляем, с какого сервера СУБД будем делать резервную копию (Systemd service ExecCondition) + +# вычисляем, с какого сервера СУБД будем создавать или проверять резервную копию (Systemd service ExecCondition) if test "${1:-}" = "ExecCondition"; then if ! (command -v patronictl &> /dev/null && command -v jq &> /dev/null); then - echo 'pg_backup: check candidate by psql if PostgreSQL is primary (Patroni or jq is not installed)' - # test ! -f $PGDATA/standby.signal # deprecated - test f = $(psql --user=bkp_replicator --no-password --dbname=postgres --quiet --no-psqlrc \ - --pset=null=¤ --tuples-only --no-align --command='select pg_is_in_recovery()') + # test ! -f "$PGDATA/standby.signal" # deprecated + PG_ROLE=$(psql --user=bkp_replicator --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ + --command="select case when pg_is_in_recovery() then 'standby' else 'primary' end") + echo "pg_backup: candidate role is $PG_ROLE (checked by psql)" + test ${2:='primary'} = "$PG_ROLE" exit fi - - echo 'pg_backup: check candidate by Patroni if PostgreSQL is "Sync Standby", "Leader", "Replica" (in order of priority)' + + echo 'pg_backup: check candidate role is "Sync Standby", "Leader", "Replica" (in order of priority)' # https://jqlang.org/manual/ # https://stackoverflow.com/questions/46070012/how-to-filter-an-array-of-json-objects-with-jq # https://stackoverflow.com/questions/76476166/jq-sorting-by-value # https://stackoverflow.com/questions/35540294/sort-descending-by-multiple-keys-in-jq # https://stackoverflow.com/questions/1952404/linux-bash-multiple-variable-assignment - DC=$(hostname | grep -oP "^\w+") # текущий ЦОД + DC=$(hostname | grep -oP '^\w+') # текущий ЦОД IFS=',' read -r MEMBER HOST ROLE <<< $(patronictl -c /etc/patroni/patroni.yml list --format=json \ | jq -Mcr --arg DC $DC ' map(select( @@ -88,58 +89,174 @@ if test "${1:-}" = "ExecCondition"; then | [.Member, .Host, .Role] | join(",") ') - test -z $HOST && echoerr "pg_backup: no candidate found" && exit 1 - echo "pg_backup: backup will be from '$MEMBER' [$HOST] ($ROLE)" + test -z "$HOST" && echoerr "pg_backup: no candidate found" && exit 1 + echo "pg_backup: perform will be from '$MEMBER' [$HOST] ($ROLE)" test $(hostname) = "$MEMBER" exit +# проверяем восстанавливаемость PostgreSQL из резервной копии +elif test "${1:-}" = "validate"; then + TIME_START=$(date +%s) # время в Unixtime + + echo "Получаем название предпоследнего или последнего файла с архивом резервной копии (сортировка по дате модификации)" + BACKUP_FILE=$(find $BACKUP_DIR -maxdepth 2 -type f \( -name "*.pg_basebackup.tar.*" -o -path "*.pg_basebackup/base.tar.*" \) \ + -printf "%T@ %p\n" | sort -n | tail -2 | head -1 | cut -d" " -f2) + test -z "$BACKUP_FILE" && echoerr "pg_backup validate: no backup archive file found in directory '$BACKUP_DIR'" && exit 1 + echo "pg_backup validate: archive file '$BACKUP_FILE' selected" + + PG_DATA_TEST_DIR=$(dirname $(dirname $BACKUP_DIR))/pgdata_validate + echo "Создаём тестовую папку '$PG_DATA_TEST_DIR' для данных СУБД, удаляем старые данные (защита от предыдущего неудачного запуска скрипта)" + test -d "$PG_DATA_TEST_DIR" && rm -r $PG_DATA_TEST_DIR && echo "pg_backup validate: old temporary directory '$PG_DATA_TEST_DIR' deleted" + mkdir $PG_DATA_TEST_DIR + echo "pg_backup validate: temporary directory '$PG_DATA_TEST_DIR' created" + + echo "Проверяем, что у папки '$PG_DATA_TEST_DIR' права доступа 750 или 700, иначе PostgreSQL не запустится" + chmod 750 $PG_DATA_TEST_DIR + # chmod не гарантирует изменение прав доступа на SMB/CIFS + stat -c "%a" $PG_DATA_TEST_DIR | grep -qP '^7[05]0$' \ + || (echoerr "pg_backup validate: directory '$PG_DATA_TEST_DIR' permission must be 750 or 700" && exit 1) + + echo "Распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_TEST_DIR'" + # определяем архиватор по расширению файла + BACKUP_FILE_EXT=$(echo "$BACKUP_FILE" | grep -oP '\.\K[a-z\d]+$') # compressed file type (zst, lz4) + if test "$BACKUP_FILE_EXT" = "zst"; then + COMPRESS_PROGRAM="unzstd" + elif test "$BACKUP_FILE_EXT" = "lz4"; then + COMPRESS_PROGRAM="unlz4" + else + echoerr "pg_backup validate: no compress program found" + exit 1 + fi + tar -xf $BACKUP_FILE --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR + + BACKUP_BASE_DIR=$(echo "$BACKUP_FILE" | grep -qP '\.pg_basebackup/base\.tar\.[a-z\d]+$' && dirname "$BACKUP_FILE" || true) + if test -z "$BACKUP_BASE_DIR"; then + echo "Проверяем существование папки '$WAL_DIR', из неё могут быть прочитаны дополнительные WAL файлы" + test ! -d "$WAL_DIR" && echowarn "pg_backup validate: directory '$WAL_DIR' does not exist" + else + echo "Копируем '$BACKUP_BASE_DIR/backup_manifest' в '$PG_DATA_TEST_DIR'" + cp $BACKUP_BASE_DIR/backup_manifest $PG_DATA_TEST_DIR + if test -f "$BACKUP_BASE_DIR/pg_wal.tar"; then + echo "Распаковываем архив '$BACKUP_BASE_DIR/pg_wal.tar' в '$PG_DATA_TEST_DIR/pg_wal'" + tar -xf $BACKUP_BASE_DIR/pg_wal.tar --directory=$PG_DATA_TEST_DIR/pg_wal + elif test -f "$BACKUP_BASE_DIR/pg_wal.tar.$BACKUP_FILE_EXT"; then + echo "Распаковываем архив '$BACKUP_BASE_DIR/pg_wal.tar.$BACKUP_FILE_EXT' в '$PG_DATA_TEST_DIR/pg_wal'" + tar -xf $BACKUP_BASE_DIR/pg_wal.tar.$BACKUP_FILE_EXT --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR/pg_wal + else + echoerr "В папке '$BACKUP_BASE_DIR' файлы 'pg_wal.tar' или 'pg_wal.tar.$BACKUP_FILE_EXT' не найдены" && exit 1 + fi + fi + + DIR_SIZE=$(du -sh "$PG_DATA_TEST_DIR" | grep -oP '^\S+') + echo "pg_backup validate: archive file extracted to directory '$PG_DATA_TEST_DIR' (total size: $DIR_SIZE)" + + echo "Проверяем целостность копии кластера СУБД, сделанной программой pg_basebackup, по манифесту backup_manifest" + $PG_BIN_DIR/pg_verifybackup --no-parse-wal --exit-on-error --quiet $PG_DATA_TEST_DIR + echo "pg_backup validate: '$PG_DATA_TEST_DIR' backup verify OK" + + echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" + rm -f -r -v $PG_DATA_TEST_DIR/*.{signal,backup,old} $PG_DATA_TEST_DIR/log/* + + echo "(Ре)стартуем сервер СУБД в роли мастер (рестарт - это защита от предыдущего неудачного запуска скрипта)" + touch $PG_DATA_TEST_DIR/recovery.signal + PG_PORT=55432 + $PG_BIN_DIR/pg_ctl restart --pgdata=$PG_DATA_TEST_DIR --silent \ + --options="-p $PG_PORT -B 128MB --cluster_name=BACKUP_VALIDATE --log_directory=log --archive_mode=off" + echo "pg_backup validate: server started (port $PG_PORT)" + + echo "Проверяем подключение к СУБД" + psql --port=$PG_PORT --user=bkp_replicator --no-password --dbname=postgres --no-psqlrc --command='\conninfo' + echo "pg_backup validate: server connection OK" + + echo "Останавливаем сервер СУБД" + $PG_BIN_DIR/pg_ctl stop --pgdata=$PG_DATA_TEST_DIR --silent + echo "pg_backup validate: server stopped" + + for LOG_FILE in $PG_DATA_TEST_DIR/log/*; do + echo "Проверяем отсутствие ошибок в файле $LOG_FILE" + grep -P '\b(WARNING|ERROR|FATAL|PANIC)\b' $LOG_FILE && exit 1 || true + done + echo "pg_backup validate: no problems found in log files" + + echo "Проверяем контрольные суммы данных в кластере СУБД" + $PG_BIN_DIR/pg_checksums --check --pgdata=$PG_DATA_TEST_DIR + echo "pg_backup validate: '$PG_DATA_TEST_DIR' checksums OK" + + LOG_FILE=$(dirname $BACKUP_FILE)/$(basename $BACKUP_FILE .tar.$BACKUP_FILE_EXT).validated.log + echo "Сохраняем управляющую информацию кластера СУБД в файл '$LOG_FILE'" + $PG_BIN_DIR/pg_controldata --pgdata=$PG_DATA_TEST_DIR &> $LOG_FILE + + echo "Удаляем папку '$PG_DATA_TEST_DIR', она больше не нужна" + rm -r $PG_DATA_TEST_DIR + echo "pg_backup validate: temporary directory '$PG_DATA_TEST_DIR' deleted" + + TIME_END=$(date +%s) # время в Unixtime + TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) + echosucc "pg_backup validate: success, duration: $TIME_ELAPSED (day:hh:mm:ss)" + exit 0 elif test -n "${1:-}"; then echoerr "pg_backup: unknown first parameter '${1:-}'" exit 2 fi - + +# ----------------------------------------------------------------------------------------------------------------------- echoinfo "pg_backup: creating started" -TIME_START=$(date +%s) #время в Unixtime - -# создаём директории, если их ещё нет -mkdir -p ${BACKUP_DIR} ${WAL_DIR} - -# создаём физическую резервную копию -# zstd adapt compression level depending on I/O conditions, mainly how fast it can write the output -# В zstd v1.4.4 плохо работает адаптивный режим с кол-вом потоков> 1 (--adapt -T), постепенно увеличивается степень сжатия и длительность работы. -# Для многопоточного режима лучше явно поставить степень сжатия 5, которая получена опытным путём. -# Это баланс между скоростью работы zstd, размером сжатого файла, скоростью записи на сетевой диск с учётом его нагрузки другими процессами. +TIME_START=$(date +%s) # время в Unixtime +BASE_NAME=${BACKUP_DIR}/$(date +%Y-%m-%d.%H%M%S).$(hostname) ZSTD_THREADS=$(echo "$(nproc) / 2.5 + 1" | bc) -${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar --pgdata=- \ - | ionice -c2 -n7 -- nice -n19 -- zstd -q -T${ZSTD_THREADS} -5 -o ${FILENAME}.pg_basebackup.tar.zst - +mkdir -p ${BACKUP_DIR} ${WAL_DIR} # создаём директории, если их ещё нет + +# Для многопоточного режима zstd используется степень сжатия 5, которая получена опытным путём. +# Это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск с учётом его нагрузки другими процессами. + +echo 'Проверяем необходимость бекапирования WAL файлов (зависит от настройки параметра archive_mode и роли СУБД primary/standby)' +IS_BACKUP_WAL=$(psql --user=bkp_replicator --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ + --command="select setting='off' or (pg_is_in_recovery() and setting='on') from pg_settings where name='archive_mode'") + +if test "$IS_BACKUP_WAL" = "f"; then + echo 'Создаём физическую резервную копию (без WAL файлов)' + ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar \ + --pgdata=- | zstd -q -T${ZSTD_THREADS} -5 -o ${BASE_NAME}.pg_basebackup.tar.zst +else + echo 'Создаём физическую резервную копию (с WAL файлами)' + # в библиотеке libzstd многопоточность поддерживается с версии 1.5.0 + LIBZSTD_VER=$(rpm -q libzstd | grep -oP '^libzstd-\K\d+\.\d+') + test -z "$LIBZSTD_VER" && echoerr "pg_backup: cannot get libzstd version, it is installed?" && exit 1 + OPT_COMPRESS="server-zstd:level=1" + printf '%s\n' "1.5" "$LIBZSTD_VER" | sort -V -C && OPT_COMPRESS="server-zstd:level=5,workers=${ZSTD_THREADS}" + ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --compress=${OPT_COMPRESS} --checkpoint=fast --format=tar \ + --pgdata=${BASE_NAME}.pg_basebackup + zstd -q -T${ZSTD_THREADS} -5 --rm ${BASE_NAME}.pg_basebackup/pg_wal.tar +fi + # создаём логическую резервную копию (deprecated) -# ${PG_BIN_DIR}/pg_dumpall --username=${PG_USERNAME} --no-password | zstd -q -T${ZSTD_THREADS} -5 -o ${FILENAME}.sql.zst - -TIME_END=$(date +%s) #время в Unixtime +# ${PG_BIN_DIR}/pg_dumpall --username=${PG_USERNAME} --no-password | zstd -q -T${ZSTD_THREADS} -5 -o ${BASE_NAME}.sql.zst + +TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) - + echosucc "pg_backup: created successfully, duration: $TIME_ELAPSED (day:hh:mm:ss)" - + +# ----------------------------------------------------------------------------------------------------------------------- # удаляем архивные резервные копии старше N дней (папки и файлы рекурсивно) echo "pg_backup: deleting backup files older than ${BACKUP_AGE_DAYS} days" find ${BACKUP_DIR} -mindepth 1 -mtime +${BACKUP_AGE_DAYS} -delete - -# удаляем архивные WAL файлы старше N дней + +# удаляем архивные WAL файлы старше N дней (сортировка по дате модификации) echo "pg_backup: detect oldest kept WAL file for ${BACKUP_AGE_DAYS} days" -WAL_OLD_FILE=$(find ${WAL_DIR} -maxdepth 1 -mtime +${BACKUP_AGE_DAYS} -type f -printf "%C@ %f\n" | sort -n | tail -n 1 | cut -d" " -f2) +WAL_OLD_FILE=$(find ${WAL_DIR} -maxdepth 1 -mtime +${BACKUP_AGE_DAYS} -type f ! -size 0 \ + ! -name "*.history" ! -name "*.history.*" -printf "%T@ %f\n" \ + | sort -n | tail -1 | cut -d" " -f2) if test -z "${WAL_OLD_FILE}"; then echo "pg_backup: WAL old file is not found" else echo "pg_backup: WAL old file is ${WAL_OLD_FILE}" - - WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP "^\S+") - echo "pg_backup: Before cleanup WAL dir size: ${WAL_DIR_SIZE}" - - WAL_OLD_FILE_EXT=$(echo "${WAL_OLD_FILE}" | grep -oP "\.[a-z\d]+$") # compressed files support (.gz, .zst, .lz4) + WAL_OLD_FILE_EXT=$(echo "${WAL_OLD_FILE}" | grep -oP '\.[a-z\d]+$') # compressed files support (.gz, .zst, .lz4) + + BEFORE_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') ${PG_BIN_DIR}/pg_archivecleanup -x "${WAL_OLD_FILE_EXT}" "${WAL_DIR}" "${WAL_OLD_FILE}" - - WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP "^\S+") - echo "pg_backup: After cleanup WAL dir size: ${WAL_DIR_SIZE}" + + AFTER_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') + echo "pg_backup: WAL dir size reducing: ${BEFORE_WAL_DIR_SIZE} (before cleanup) -> ${AFTER_WAL_DIR_SIZE} (after cleanup)" fi - + echosucc "pg_backup: done" From 1d4b5f2b144de2c8b8ba191d593e11abe402becd Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:34:32 +0300 Subject: [PATCH 058/145] Update pg_backup.conf --- pg_backup/pg_backup.conf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pg_backup/pg_backup.conf b/pg_backup/pg_backup.conf index 78b19e3..210f5ee 100644 --- a/pg_backup/pg_backup.conf +++ b/pg_backup/pg_backup.conf @@ -1,17 +1,17 @@ # имя пользователя для подключения к СУБД PG_USERNAME="bkp_replicator" - -# номер основной версии СУБД -PG_MAJOR_VERSION=16 - + +# путь к исполняемым файлам утилит СУБД +PG_BIN_DIR="/usr/pgsql-16/bin" + # папка для хранения файлов с резервными копиями # для корректной работы системы резервного копирования внутри папок /mnt/backup_db/{active_full,archive_wal}/ должна быть папка с названием БД # но в PostgreSQL можно сделать физическую резервную копию только для всего кластера, поэтому папка называется cluster BACKUP_DIR="/mnt/backup_db/active_full/cluster" - + # папка для хранения WAL файлов WAL_DIR="/mnt/backup_db/archive_wal/cluster" - + # сколько времени хранить резервные копии СУБД и WAL файлы (старые файлы будут автоматически удаляться) # для тестовой среды (ПСИ, НТ, ИФТ) установить в 2 BACKUP_AGE_DAYS=14 From 29bbddac682a146064c5de83de6d3c7d3d79a494 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:50:06 +0300 Subject: [PATCH 059/145] Update README.md --- pg_backup/README.md | 119 ++++++++++++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 33 deletions(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index bc5343a..b664182 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -5,57 +5,110 @@ На каждом сервере СУБД по расписанию (обычно 1 раз в сутки) запускается [systemd](https://en.wikipedia.org/wiki/Systemd) сервис для создания резервных копий СУБД. Резервные копии создаются так: -* Если Patroni или jq не инсталлирован, то только с сервера СУБД мастер. +* Если [Patroni](https://patroni.readthedocs.io/en/latest/) или [jq](https://jqlang.org/) не инсталлирован, то только с сервера СУБД мастер. * Иначе только с одного сервера СУБД в каждом ЦОДе. Приоритет выбора сервера: синхронная реплика, мастер, асинхронная реплика (с отставанием не более 1000 МБ). > [!NOTE] -> Резервная копия сжимается в формат `zstd` (16–25% от исходного размера файлов СУБД). Это позволяет экономить место на сетевом диске и уменьшить нагрузку на ввод-вывод. +> Резервная копия сжимается в формат [`zstd`](https://github.com/facebook/zstd) (16–25% от исходного размера файлов СУБД). Это позволяет экономить место на сетевом диске и уменьшить нагрузку на ввод-вывод. > [!CAUTION] > Внимание! -> WAL файлы в резервную копию не копируются. -> Для возможности восстановления СУБД из резервной копии должно быть настроено [непрерывное архивирование WAL файлов](https://postgrespro.ru/docs/postgresql/16/continuous-archiving) -через [archive_command](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-ARCHIVE-COMMAND) -или [pg_receivewal](https://postgrespro.ru/docs/postgresql/16/app-pgreceivewal). +> 1. Наличие WAL файлов в резервной копии зависит от настройки [`archive_mode`](https://postgrespro.ru/docs/postgresql/17/runtime-config-wal#GUC-ARCHIVE-MODE) и текущей роли сервера (мастер, реплика). +> 2. Для возможности восстановления СУБД из резервной копии, созданной без WAL файлов, должно быть настроено [непрерывное архивирование WAL файлов](https://postgrespro.ru/docs/postgresql/16/continuous-archiving) +через [`archive_command`](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-ARCHIVE-COMMAND) +или [`pg_receivewal`](https://postgrespro.ru/docs/postgresql/16/app-pgreceivewal). +> 3. Следует учесть [ограничения создания резервной копии с реплики](https://postgrespro.ru/docs/postgresql/16/app-pgbasebackup)! ## Настройка создания резервных копий СУБД -**Инсталляция сервиса** -```bash +**Шаг 1. Выполнить на терминальном сервере Windows (PowerShell)** +```powershell +# создаём папку и переходим в неё +$path = "$home\pg_install"; mkdir -force $path; cd $path + # создаём файлы -sudo su - postgres -c "nano ~/.pgpass && chmod 600 ~/.pgpass" # в файле нужно сохранить пароль для пользователя bkp_replicator -sudo su - postgres -c "nano ~/pg_backup.sh && chmod 700 ~/pg_backup.sh && bash -n ~/pg_backup.sh" -sudo su - postgres -c "nano ~/pg_backup.conf && chmod 600 ~/pg_backup.conf && bash -n ~/pg_backup.conf" - -sudo nano /etc/systemd/system/pg_backup.service && \ -sudo nano /etc/systemd/system/pg_backup.timer - -# активируем и добавляем в автозагрузку -sudo systemctl daemon-reload && \ -sudo systemctl enable pg_backup.timer && \ -sudo systemctl enable pg_backup +# ВНИМАНИЕ! кодировка файлов должна быть UTF8 без BOM, переносы строк в формате Unix (LF) +C:\"Program Files"\Notepad++\notepad++.exe ` + pg_backup.sh pg_backup.conf ` + pg_backup.timer pg_backup.service ` + pg_backup_validate.timer pg_backup_validate.service ` + archive_command.sh restore_command.sh + +# замените xx на код АС (или ФП?), yy на код среды (ps, nt, if, te), NN на порядковый номер +$db_hosts='sp-xx-db-yyNN', 'sp-xx-db-yyNN', 'sc-xx-db-yyNN', 'sc-xx-db-yyNN' + +# копируем локальную папку с файлами на удалённые серверы СУБД в домашнюю папку (Windows -> Linux) +foreach ($db_host in $db_hosts) { + Write-Host "`n$db_host" -ForegroundColor white -BackgroundColor blue + pscp -r $path ${env:username}@${db_host}: +} +``` +**Шаг 2. Выполнить на каждом сервере СУБД Linux (Bash)** +```bash +sudo -i + +AUTH_USER=$(who -m | cut -f1 -d' ') && \ +HOME_DIR=$(eval echo ~$AUTH_USER) && \ +cd ~postgres + +# создаём файлы (1) +nano -c .pgpass # в файле нужно сохранить пароль для пользователя bkp_replicator +(cp --update $HOME_DIR/pg_install/pg_backup.sh . || nano -c pg_backup.sh) && \ +(cp --update $HOME_DIR/pg_install/pg_backup.conf . || nano -c pg_backup.conf) && \ +(cp --update $HOME_DIR/pg_install/archive_command.sh . || nano -c archive_command.sh) && \ +(cp --update $HOME_DIR/pg_install/restore_command.sh . || nano -c restore_command.sh) +# выставляем нужные права и владельца +chmod 600 .pgpass pg_backup.conf && \ +chmod 700 {pg_backup,{archive,restore}_command}.sh && \ +chown postgres:postgres .pgpass {pg_backup,{archive,restore}_command}.sh pg_backup.conf + +# создаём файлы (2) +(cp --update $HOME_DIR/pg_install/pg_backup.timer /etc/systemd/system || nano -c /etc/systemd/system/pg_backup.timer) && \ +(cp --update $HOME_DIR/pg_install/pg_backup.service /etc/systemd/system || nano -c /etc/systemd/system/pg_backup.service) && \ +(cp --update $HOME_DIR/pg_install/pg_backup_validate.timer /etc/systemd/system || nano -c /etc/systemd/system/pg_backup_validate.timer) && \ +(cp --update $HOME_DIR/pg_install/pg_backup_validate.service /etc/systemd/system || nano -c /etc/systemd/system/pg_backup_validate.service) && \ +systemctl daemon-reload # активируем + +# добавляем в автозагрузку +systemctl enable pg_backup.timer && \ +systemctl enable pg_backup.service && \ +systemctl enable pg_backup_validate.timer && \ +systemctl enable pg_backup_validate.service + # проверяем работоспособность (отладка) -# time sudo su - postgres -c "~/pg_backup.sh" # сделает резервную копию СУБД, выведет сообщения на экран - -# запускаем -sudo systemctl start pg_backup.timer && \ -sudo systemctl start pg_backup # сделает резервную копию СУБД только на мастере, НЕ выведет сообщения на экран - -# проверяем статус -sudo systemctl status pg_backup.timer && \ -sudo systemctl status pg_backup +sudo -i -u postgres -- ./pg_backup.sh ExecCondition # будем ли создавать или проверять резервную копию с текущего сервера СУБД (см. код возврата)? +sudo -i -u postgres -- ./pg_backup.sh # создаст резервную копию текущего сервера СУБД (и выведет сообщения на экран) +sudo -i -u postgres -- ./pg_backup.sh validate # проверит восстанавливаемость резервной копии СУБД (и выведет сообщения на экран) -# получаем список активных таймеров, для pg_backup.timer д.б. указана дата-время следующего запуска! -systemctl list-timers +# запускаем; сделает резервную копию СУБД, если условие ExecCondition выполнится (НЕ выведет сообщения на экран) +systemctl start pg_backup.timer && \ +systemctl start pg_backup.service && \ +systemctl start pg_backup_validate.timer && \ +systemctl start pg_backup_validate.service + +# проверяем статус (1) +systemctl status pg_backup.timer && \ +systemctl status pg_backup.service + +# проверяем статус (2) +systemctl status pg_backup_validate.timer && \ +systemctl status pg_backup_validate.service + +# получаем список активных таймеров, д.б. указана дата-время следующего запуска! +systemctl list-timers | grep -P 'NEXT|pg_backup' ``` Файлы -* [`/etc/systemd/system/pg_backup.service`](pg_backup.service) * [`/etc/systemd/system/pg_backup.timer`](pg_backup.timer) +* [`/etc/systemd/system/pg_backup.service`](pg_backup.service) +* [`/etc/systemd/system/pg_backup_validate.timer`](pg_backup_validate.timer) +* [`/etc/systemd/system/pg_backup_validate.service`](pg_backup_validate.service) * [`/var/lib/pgsql/pg_backup.sh`](pg_backup.sh) * [`/var/lib/pgsql/pg_backup.conf`](pg_backup.conf) +* [`/var/lib/pgsql/archive_command.sh`](archive_command.sh) +* [`/var/lib/pgsql/restore_command.sh`](restore_command.sh) ## Ссылки по теме -* [PostgreSQL: архивирование WAL файлов (archive_command)](archive_command.md) -* [PostgreSQL: восстановление WAL файлов (restore_command)](restore_command.md) +* [PostgreSQL: копирование WAL файлов в архив (archive_command)](archive_command.md) +* [PostgreSQL: восстановление WAL файлов из архива (restore_command)](restore_command.md) From 944360e7d27c7232117c385117dcab6a280e24fe Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 14:50:53 +0300 Subject: [PATCH 060/145] Update restore_command.md --- pg_backup/restore_command.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/restore_command.md b/pg_backup/restore_command.md index bf0a352..233e4bf 100644 --- a/pg_backup/restore_command.md +++ b/pg_backup/restore_command.md @@ -1,4 +1,4 @@ -# PostgreSQL: восстановление WAL файлов (restore_command) +# PostgreSQL: восстановление WAL файлов из архива (restore_command) ## Введение From 09a1c8dbed01db98066bef9c7a95af6d70d5a712 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月20日 20:19:40 +0300 Subject: [PATCH 061/145] Update README.md --- pg_receivewal/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_receivewal/README.md b/pg_receivewal/README.md index 702a446..855583b 100644 --- a/pg_receivewal/README.md +++ b/pg_receivewal/README.md @@ -5,7 +5,7 @@ Для непрерывного архивирования [WAL файлов](https://postgrespro.ru/docs/postgresql/16/continuous-archiving) **в реальном времени** применяется [pg_receivewal](https://postgrespro.ru/docs/postgresql/16/app-pgreceivewal), а не [archive_command](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-ARCHIVE-COMMAND). > [!CAUTION] -> При наличии синхронной реплики архивировать WAL файлов в реальном времени не требуется. Для кластеров СУБД используется не этот сервис, а штатная функциональность [archive_command](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-ARCHIVE-COMMAND), чтобы везде было единообразно, это упрощает сопровождение. +> При наличии синхронной реплики архивировать WAL файлы в реальном времени не нужно. Для кластеров СУБД используется не этот сервис, а штатная функциональность [archive_command](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-ARCHIVE-COMMAND), чтобы везде было единообразно, это упрощает сопровождение. Сервис работает только с СУБД мастером, использует отдельный слот репликации и выглядит как ещё одна постоянно отстающая асинхронная реплика. From d0fe50327a872207f5b7143d30beae63913766ec Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月23日 15:32:12 +0300 Subject: [PATCH 062/145] pg_amcheck added --- pg_backup/pg_backup.sh | 48 +++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index 2c7e2e5..8db0f7a 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -63,7 +63,7 @@ fi if test "${1:-}" = "ExecCondition"; then if ! (command -v patronictl &> /dev/null && command -v jq &> /dev/null); then # test ! -f "$PGDATA/standby.signal" # deprecated - PG_ROLE=$(psql --user=bkp_replicator --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ + PG_ROLE=$(psql --user=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ --command="select case when pg_is_in_recovery() then 'standby' else 'primary' end") echo "pg_backup: candidate role is $PG_ROLE (checked by psql)" test ${2:='primary'} = "$PG_ROLE" @@ -103,6 +103,19 @@ elif test "${1:-}" = "validate"; then test -z "$BACKUP_FILE" && echoerr "pg_backup validate: no backup archive file found in directory '$BACKUP_DIR'" && exit 1 echo "pg_backup validate: archive file '$BACKUP_FILE' selected" + # определяем архиватор по расширению файла + BACKUP_FILE_EXT=$(echo "$BACKUP_FILE" | grep -oP '\.\K[a-z\d]+$') # compressed file type (zst, lz4) + if test "$BACKUP_FILE_EXT" = "zst"; then + COMPRESS_PROGRAM="unzstd" + elif test "$BACKUP_FILE_EXT" = "lz4"; then + COMPRESS_PROGRAM="unlz4" + else + echoerr "pg_backup validate: no compress program found" + exit 1 + fi + LOG_FILE_PREFIX=$(dirname $BACKUP_FILE)/$(basename $BACKUP_FILE .tar.$BACKUP_FILE_EXT) + touch $LOG_FILE_PREFIX.validate-selected.log + PG_DATA_TEST_DIR=$(dirname $(dirname $BACKUP_DIR))/pgdata_validate echo "Создаём тестовую папку '$PG_DATA_TEST_DIR' для данных СУБД, удаляем старые данные (защита от предыдущего неудачного запуска скрипта)" test -d "$PG_DATA_TEST_DIR" && rm -r $PG_DATA_TEST_DIR && echo "pg_backup validate: old temporary directory '$PG_DATA_TEST_DIR' deleted" @@ -116,16 +129,6 @@ elif test "${1:-}" = "validate"; then || (echoerr "pg_backup validate: directory '$PG_DATA_TEST_DIR' permission must be 750 or 700" && exit 1) echo "Распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_TEST_DIR'" - # определяем архиватор по расширению файла - BACKUP_FILE_EXT=$(echo "$BACKUP_FILE" | grep -oP '\.\K[a-z\d]+$') # compressed file type (zst, lz4) - if test "$BACKUP_FILE_EXT" = "zst"; then - COMPRESS_PROGRAM="unzstd" - elif test "$BACKUP_FILE_EXT" = "lz4"; then - COMPRESS_PROGRAM="unlz4" - else - echoerr "pg_backup validate: no compress program found" - exit 1 - fi tar -xf $BACKUP_FILE --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR BACKUP_BASE_DIR=$(echo "$BACKUP_FILE" | grep -qP '\.pg_basebackup/base\.tar\.[a-z\d]+$' && dirname "$BACKUP_FILE" || true) @@ -150,12 +153,15 @@ elif test "${1:-}" = "validate"; then echo "pg_backup validate: archive file extracted to directory '$PG_DATA_TEST_DIR' (total size: $DIR_SIZE)" echo "Проверяем целостность копии кластера СУБД, сделанной программой pg_basebackup, по манифесту backup_manifest" - $PG_BIN_DIR/pg_verifybackup --no-parse-wal --exit-on-error --quiet $PG_DATA_TEST_DIR + $PG_BIN_DIR/pg_verifybackup --no-parse-wal --exit-on-error --quiet $PG_DATA_TEST_DIR &> $LOG_FILE_PREFIX.pg_verifybackup.log echo "pg_backup validate: '$PG_DATA_TEST_DIR' backup verify OK" echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" rm -f -r -v $PG_DATA_TEST_DIR/*.{signal,backup,old} $PG_DATA_TEST_DIR/log/* + echo "Разрешаем локальному пользователю postgres аутентифицироваться методом peer" + sed -i '1i local all postgres peer' $PG_DATA_TEST_DIR/pg_hba.conf # добавляем строчку в начало файла + echo "(Ре)стартуем сервер СУБД в роли мастер (рестарт - это защита от предыдущего неудачного запуска скрипта)" touch $PG_DATA_TEST_DIR/recovery.signal PG_PORT=55432 @@ -164,9 +170,14 @@ elif test "${1:-}" = "validate"; then echo "pg_backup validate: server started (port $PG_PORT)" echo "Проверяем подключение к СУБД" - psql --port=$PG_PORT --user=bkp_replicator --no-password --dbname=postgres --no-psqlrc --command='\conninfo' + psql --port=$PG_PORT --user=$PG_USERNAME --no-password --dbname=postgres --no-psqlrc --command='\conninfo' echo "pg_backup validate: server connection OK" + echo "Проверяем логическую целостность таблиц и индексов (amcheck)" + $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* \ + --rootdescend --on-error-stop &> $LOG_FILE_PREFIX.pg_amcheck.log + echo "pg_backup validate: amcheck OK" + echo "Останавливаем сервер СУБД" $PG_BIN_DIR/pg_ctl stop --pgdata=$PG_DATA_TEST_DIR --silent echo "pg_backup validate: server stopped" @@ -178,10 +189,10 @@ elif test "${1:-}" = "validate"; then echo "pg_backup validate: no problems found in log files" echo "Проверяем контрольные суммы данных в кластере СУБД" - $PG_BIN_DIR/pg_checksums --check --pgdata=$PG_DATA_TEST_DIR + $PG_BIN_DIR/pg_checksums --check --pgdata=$PG_DATA_TEST_DIR &> $LOG_FILE_PREFIX.pg_checksums.log echo "pg_backup validate: '$PG_DATA_TEST_DIR' checksums OK" - LOG_FILE=$(dirname $BACKUP_FILE)/$(basename $BACKUP_FILE .tar.$BACKUP_FILE_EXT).validated.log + LOG_FILE=$LOG_FILE_PREFIX.pg_controldata.log echo "Сохраняем управляющую информацию кластера СУБД в файл '$LOG_FILE'" $PG_BIN_DIR/pg_controldata --pgdata=$PG_DATA_TEST_DIR &> $LOG_FILE @@ -191,6 +202,9 @@ elif test "${1:-}" = "validate"; then TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) + LOG_FILE=$LOG_FILE_PREFIX.validate-success.log + echo "Total size: $DIR_SIZE">> $LOG_FILE + echo "Validate duration: $TIME_ELAPSED (day:hh:mm:ss)">> $LOG_FILE echosucc "pg_backup validate: success, duration: $TIME_ELAPSED (day:hh:mm:ss)" exit 0 elif test -n "${1:-}"; then @@ -209,7 +223,7 @@ mkdir -p ${BACKUP_DIR} ${WAL_DIR} # создаём директории, есл # Это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск с учётом его нагрузки другими процессами. echo 'Проверяем необходимость бекапирования WAL файлов (зависит от настройки параметра archive_mode и роли СУБД primary/standby)' -IS_BACKUP_WAL=$(psql --user=bkp_replicator --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ +IS_BACKUP_WAL=$(psql --user=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ --command="select setting='off' or (pg_is_in_recovery() and setting='on') from pg_settings where name='archive_mode'") if test "$IS_BACKUP_WAL" = "f"; then @@ -222,7 +236,7 @@ else LIBZSTD_VER=$(rpm -q libzstd | grep -oP '^libzstd-\K\d+\.\d+') test -z "$LIBZSTD_VER" && echoerr "pg_backup: cannot get libzstd version, it is installed?" && exit 1 OPT_COMPRESS="server-zstd:level=1" - printf '%s\n' "1.5" "$LIBZSTD_VER" | sort -V -C && OPT_COMPRESS="server-zstd:level=5,workers=${ZSTD_THREADS}" + test $(echo "$LIBZSTD_VER>= 1.5" | bc -l) = 1 && OPT_COMPRESS="server-zstd:level=5,workers=${ZSTD_THREADS}" ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --compress=${OPT_COMPRESS} --checkpoint=fast --format=tar \ --pgdata=${BASE_NAME}.pg_basebackup zstd -q -T${ZSTD_THREADS} -5 --rm ${BASE_NAME}.pg_basebackup/pg_wal.tar From b9e61b010d8d45f58cf24d5a20c21b6fa4faec6f Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月26日 23:40:48 +0300 Subject: [PATCH 063/145] link added --- db_copy/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/db_copy/README.md b/db_copy/README.md index b0b01b5..4c134f0 100644 --- a/db_copy/README.md +++ b/db_copy/README.md @@ -15,3 +15,7 @@ nano ~/db_restore.sh && chmod +x ~/db_restore.sh ``` Файл [`db_restore.sh`](db_restore.sh) + +## Ссылки по теме + +* https://github.com/dimitri/pgcopydb From e06823064eddb1d6ad95b3dda1904e57a5c3f377 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月30日 00:19:17 +0300 Subject: [PATCH 064/145] Update pg_backup.conf --- pg_backup/pg_backup.conf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pg_backup/pg_backup.conf b/pg_backup/pg_backup.conf index 210f5ee..1255a65 100644 --- a/pg_backup/pg_backup.conf +++ b/pg_backup/pg_backup.conf @@ -12,6 +12,13 @@ BACKUP_DIR="/mnt/backup_db/active_full/cluster" # папка для хранения WAL файлов WAL_DIR="/mnt/backup_db/archive_wal/cluster" +# как часто делать резервные копии с WAL файлами? +# текущий день с начала года должен делиться на BACKUP_WAL_DOY_DIVIDER без остатка (1 - ежедневно, 2 - каждый второй день и т.д.) +BACKUP_WAL_DOY_DIVIDER=5 + +# пароль для шифрования/дешифрования файлов резервных копий +GPG_PASSPHRASE="*censored*" + # сколько времени хранить резервные копии СУБД и WAL файлы (старые файлы будут автоматически удаляться) # для тестовой среды (ПСИ, НТ, ИФТ) установить в 2 BACKUP_AGE_DAYS=14 From 7dd92c4b7334ef89b3b87d87d554aa17747afdbb Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月30日 00:21:22 +0300 Subject: [PATCH 065/145] encrypt by GPG, restore command --- pg_backup/pg_backup.sh | 233 ++++++++++++++++++++++++++++------------- 1 file changed, 160 insertions(+), 73 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index 8db0f7a..2934986 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -1,18 +1,18 @@ #!/bin/bash # https://habr.com/ru/company/ruvds/blog/325522/ - Bash documentation - + # https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html # set -e - прекращает выполнение скрипта, если команда завершилась ошибкой # set -u - прекращает выполнение скрипта, если встретилась несуществующая переменная # set -x - выводит выполняемые команды в stdout перед выполнением (только для отладки, а то замусоривает журнал!) # set -o pipefail - прекращает выполнение скрипта, даже если одна из частей пайпа завершилась ошибкой set -euo pipefail - + SCRIPT_FILE=$(readlink -f "0ドル") SCRIPT_DIR=$(dirname "$SCRIPT_FILE") - + bash -n "${SCRIPT_FILE}" || exit # Check syntax this file - + # Colors Red='\e[1;31m' Green='\e[0;32m' @@ -24,13 +24,13 @@ Cyan='\e[0;36m' Gray='\e[0;37m' White='\e[1;37m' Reset='\e[0m' - + # Colored messages echoerr() { echo -e "${Red}$@${Reset}" 1>&2; } echowarn() { echo -e "${Yellow}$@${Reset}" 1>&2; } echoinfo() { echo -e "${White}$@${Reset}" ; } echosucc() { echo -e "${Green}$@${Reset}" ; } - + elapsed() { local time_start=1ドル #time_start=$(date +%s) local time_end=2ドル #time_end=$(date +%s) @@ -43,22 +43,25 @@ elapsed() { local ds=$(echo "$dt3-60*$dm" | bc) printf '%dd:%02d:%02d:%02d' $dd $dh $dm $ds #day:hh:mm:ss } - + # меняем приоритет этого процесса ($$ - это его pid) на минимальный (дочерние процессы наследуют значение приоритета родительского процесса) ionice -c 2 -n 7 -p $$ renice -n 19 -p $$ - + source "$SCRIPT_DIR/pg_backup.conf" # include - + PG_PASS_FILE="$SCRIPT_DIR/.pgpass" if test $(whoami) != "postgres"; then - echoerr "pg_backup: run script as user postgres, not $(whoami)!" - exit 1 + echoerr "pg_backup: run script as user postgres, not $(whoami)!" + exit 1 elif ! grep -q -w "$PG_USERNAME" "$PG_PASS_FILE"; then - echoerr "pg_backup: file '$PG_PASS_FILE' must contain record for user '$PG_USERNAME'" - exit 1 + echoerr "pg_backup: file '$PG_PASS_FILE' must contain record for user '$PG_USERNAME'" + exit 1 +elif test "$GPG_PASSPHRASE" = "*censored*"; then + echoerr "pg_backup: change value of \$GPG_PASSPHRASE in '$SCRIPT_DIR/pg_backup.conf'!" + exit 1 fi - + # вычисляем, с какого сервера СУБД будем создавать или проверять резервную копию (Systemd service ExecCondition) if test "${1:-}" = "ExecCondition"; then if ! (command -v patronictl &> /dev/null && command -v jq &> /dev/null); then @@ -69,7 +72,7 @@ if test "${1:-}" = "ExecCondition"; then test ${2:='primary'} = "$PG_ROLE" exit fi - + echo 'pg_backup: check candidate role is "Sync Standby", "Leader", "Replica" (in order of priority)' # https://jqlang.org/manual/ # https://stackoverflow.com/questions/46070012/how-to-filter-an-array-of-json-objects-with-jq @@ -93,113 +96,179 @@ if test "${1:-}" = "ExecCondition"; then echo "pg_backup: perform will be from '$MEMBER' [$HOST] ($ROLE)" test $(hostname) = "$MEMBER" exit -# проверяем восстанавливаемость PostgreSQL из резервной копии +# восстанавливаем PostgreSQL из резервной копии +elif test "${1:-}" = "restore"; then + TIME_START=$(date +%s) # время в Unixtime + + # скрипт должен запускаться с тремя параметрами + test "$#" -ne 3 && echoinfo "Usage: 0ドル restore SOURCE_BACKUP_FILE_OR_DIR TARGET_PG_DATA_DIR" && exit 2 + + BACKUP_FILE_OR_DIR="2ドル" + if test -f "$BACKUP_FILE_OR_DIR"; then + BACKUP_FILE="$BACKUP_FILE_OR_DIR" + elif test -d "$BACKUP_FILE_OR_DIR"; then + BACKUP_FILE=$(find $BACKUP_FILE_OR_DIR -maxdepth 1 -type f -name "base.tar.*" -printf "%p") + test ! -f "$BACKUP_FILE" \ + && echoerr "pg_backup restore: source backup archive file '$BACKUP_FILE_OR_DIR/base.tar.*' does not exist!" && exit 1 + else + echoerr "pg_backup restore: source backup archive file/directory '$BACKUP_FILE_OR_DIR' does not exist!" + exit 1 + fi + + PG_DATA_DIR="3ドル" + test ! -d "$PG_DATA_DIR" && echoerr "pg_backup restore: target directory '$PG_DATA_DIR' does not exist!" && exit 1 + + # определяем архиватор по расширению файла + BACKUP_FILE_EXT=$(echo "$BACKUP_FILE" | grep -oP '\.\Ktar\..*$') + ARCHIVE_TYPE=$(echo "$BACKUP_FILE_EXT" | cut -d. -f2) + if test "$ARCHIVE_TYPE" = "zst"; then + COMPRESS_PROGRAM="unzstd" + elif test "$ARCHIVE_TYPE" = "lz4"; then + COMPRESS_PROGRAM="unlz4" + else + echoerr "pg_backup validate: no compress program found" + exit 1 + fi + + echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_DIR'" + pv -treb $BACKUP_FILE \ + | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch \ + | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_DIR + + if test -d "$BACKUP_FILE_OR_DIR"; then + FILE="$BACKUP_FILE_OR_DIR/pg_wal.$BACKUP_FILE_EXT" + test ! -f "$FILE" && echoerr "Файл '$FILE' не найден" && exit 1 + echo "Расшифровываем и распаковываем архив '$FILE' в папку '$PG_DATA_DIR/pg_wal'" + pv -treb $FILE \ + | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch \ + | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_DIR/pg_wal + fi + + echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" + rm -f -r -v $PG_DATA_DIR/*.{signal,{backup,old}{,.*}} $PG_DATA_DIR/log/* + + TIME_END=$(date +%s) # время в Unixtime + TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) + echosucc "pg_backup restore: success, duration: $TIME_ELAPSED (day:hh:mm:ss)" + + cd $PG_DATA_DIR + read -p "Укажите роль создаваемого сервера (primary/standby): " PG_ROLE + if test "$PG_ROLE" = "primary"; then + touch recovery.signal && echo "Создан файл recovery.signal" + elif test "$PG_ROLE" = "standby"; then + touch standby.signal && echo "Создан файл standby.signal" + else + echoerr "Роль указана неверно, ожидается primary/standby" + exit 1 + fi + echowarn "Донастройте postgresql.conf и запустите кластер СУБД!" + exit 0 +# проверяем корректность и восстанавливаемость PostgreSQL из резервной копии elif test "${1:-}" = "validate"; then TIME_START=$(date +%s) # время в Unixtime - + echo "Получаем название предпоследнего или последнего файла с архивом резервной копии (сортировка по дате модификации)" - BACKUP_FILE=$(find $BACKUP_DIR -maxdepth 2 -type f \( -name "*.pg_basebackup.tar.*" -o -path "*.pg_basebackup/base.tar.*" \) \ + BACKUP_FILE=$(find $BACKUP_DIR -maxdepth 2 -type f \( -name "*.pg_backup.tar.*" -o -path "*.pg_backup/base.tar.*" \) \ -printf "%T@ %p\n" | sort -n | tail -2 | head -1 | cut -d" " -f2) test -z "$BACKUP_FILE" && echoerr "pg_backup validate: no backup archive file found in directory '$BACKUP_DIR'" && exit 1 echo "pg_backup validate: archive file '$BACKUP_FILE' selected" - + # определяем архиватор по расширению файла - BACKUP_FILE_EXT=$(echo "$BACKUP_FILE" | grep -oP '\.\K[a-z\d]+$') # compressed file type (zst, lz4) - if test "$BACKUP_FILE_EXT" = "zst"; then + BACKUP_FILE_EXT=$(echo "$BACKUP_FILE" | grep -oP '\.\Ktar\..*$') + ARCHIVE_TYPE=$(echo "$BACKUP_FILE_EXT" | cut -d. -f2) + if test "$ARCHIVE_TYPE" = "zst"; then COMPRESS_PROGRAM="unzstd" - elif test "$BACKUP_FILE_EXT" = "lz4"; then + elif test "$ARCHIVE_TYPE" = "lz4"; then COMPRESS_PROGRAM="unlz4" else echoerr "pg_backup validate: no compress program found" exit 1 fi - LOG_FILE_PREFIX=$(dirname $BACKUP_FILE)/$(basename $BACKUP_FILE .tar.$BACKUP_FILE_EXT) + LOG_FILE_PREFIX=$(dirname $BACKUP_FILE)/$(basename $BACKUP_FILE .$BACKUP_FILE_EXT) touch $LOG_FILE_PREFIX.validate-selected.log - + PG_DATA_TEST_DIR=$(dirname $(dirname $BACKUP_DIR))/pgdata_validate echo "Создаём тестовую папку '$PG_DATA_TEST_DIR' для данных СУБД, удаляем старые данные (защита от предыдущего неудачного запуска скрипта)" test -d "$PG_DATA_TEST_DIR" && rm -r $PG_DATA_TEST_DIR && echo "pg_backup validate: old temporary directory '$PG_DATA_TEST_DIR' deleted" mkdir $PG_DATA_TEST_DIR echo "pg_backup validate: temporary directory '$PG_DATA_TEST_DIR' created" - + echo "Проверяем, что у папки '$PG_DATA_TEST_DIR' права доступа 750 или 700, иначе PostgreSQL не запустится" chmod 750 $PG_DATA_TEST_DIR # chmod не гарантирует изменение прав доступа на SMB/CIFS stat -c "%a" $PG_DATA_TEST_DIR | grep -qP '^7[05]0$' \ || (echoerr "pg_backup validate: directory '$PG_DATA_TEST_DIR' permission must be 750 or 700" && exit 1) - - echo "Распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_TEST_DIR'" - tar -xf $BACKUP_FILE --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR - - BACKUP_BASE_DIR=$(echo "$BACKUP_FILE" | grep -qP '\.pg_basebackup/base\.tar\.[a-z\d]+$' && dirname "$BACKUP_FILE" || true) + + echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_TEST_DIR'" + gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $BACKUP_FILE \ + | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR + + BACKUP_BASE_DIR=$(echo "$BACKUP_FILE" | grep -qP '\.pg_backup/base\.tar\.' && dirname "$BACKUP_FILE" || true) if test -z "$BACKUP_BASE_DIR"; then echo "Проверяем существование папки '$WAL_DIR', из неё могут быть прочитаны дополнительные WAL файлы" test ! -d "$WAL_DIR" && echowarn "pg_backup validate: directory '$WAL_DIR' does not exist" else - echo "Копируем '$BACKUP_BASE_DIR/backup_manifest' в '$PG_DATA_TEST_DIR'" + echo "Копируем '$BACKUP_BASE_DIR/backup_manifest' в папку '$PG_DATA_TEST_DIR'" cp $BACKUP_BASE_DIR/backup_manifest $PG_DATA_TEST_DIR - if test -f "$BACKUP_BASE_DIR/pg_wal.tar"; then - echo "Распаковываем архив '$BACKUP_BASE_DIR/pg_wal.tar' в '$PG_DATA_TEST_DIR/pg_wal'" - tar -xf $BACKUP_BASE_DIR/pg_wal.tar --directory=$PG_DATA_TEST_DIR/pg_wal - elif test -f "$BACKUP_BASE_DIR/pg_wal.tar.$BACKUP_FILE_EXT"; then - echo "Распаковываем архив '$BACKUP_BASE_DIR/pg_wal.tar.$BACKUP_FILE_EXT' в '$PG_DATA_TEST_DIR/pg_wal'" - tar -xf $BACKUP_BASE_DIR/pg_wal.tar.$BACKUP_FILE_EXT --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR/pg_wal - else - echoerr "В папке '$BACKUP_BASE_DIR' файлы 'pg_wal.tar' или 'pg_wal.tar.$BACKUP_FILE_EXT' не найдены" && exit 1 - fi + + FILE="$BACKUP_BASE_DIR/pg_wal.$BACKUP_FILE_EXT" + test ! -f "$FILE" && echoerr "Файл '$FILE' не найден" && exit 1 + echo "Расшифровываем и распаковываем архив '$FILE' в папку '$PG_DATA_TEST_DIR/pg_wal'" + gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $FILE \ + | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR/pg_wal fi - + DIR_SIZE=$(du -sh "$PG_DATA_TEST_DIR" | grep -oP '^\S+') echo "pg_backup validate: archive file extracted to directory '$PG_DATA_TEST_DIR' (total size: $DIR_SIZE)" - + echo "Проверяем целостность копии кластера СУБД, сделанной программой pg_basebackup, по манифесту backup_manifest" $PG_BIN_DIR/pg_verifybackup --no-parse-wal --exit-on-error --quiet $PG_DATA_TEST_DIR &> $LOG_FILE_PREFIX.pg_verifybackup.log echo "pg_backup validate: '$PG_DATA_TEST_DIR' backup verify OK" - + echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" - rm -f -r -v $PG_DATA_TEST_DIR/*.{signal,backup,old} $PG_DATA_TEST_DIR/log/* - + rm -f -r -v $PG_DATA_TEST_DIR/*.{signal,{backup,old}{,.*}} $PG_DATA_TEST_DIR/log/* + echo "Разрешаем локальному пользователю postgres аутентифицироваться методом peer" sed -i '1i local all postgres peer' $PG_DATA_TEST_DIR/pg_hba.conf # добавляем строчку в начало файла - + echo "(Ре)стартуем сервер СУБД в роли мастер (рестарт - это защита от предыдущего неудачного запуска скрипта)" touch $PG_DATA_TEST_DIR/recovery.signal PG_PORT=55432 $PG_BIN_DIR/pg_ctl restart --pgdata=$PG_DATA_TEST_DIR --silent \ --options="-p $PG_PORT -B 128MB --cluster_name=BACKUP_VALIDATE --log_directory=log --archive_mode=off" echo "pg_backup validate: server started (port $PG_PORT)" - + echo "Проверяем подключение к СУБД" psql --port=$PG_PORT --user=$PG_USERNAME --no-password --dbname=postgres --no-psqlrc --command='\conninfo' echo "pg_backup validate: server connection OK" - + echo "Проверяем логическую целостность таблиц и индексов (amcheck)" $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* \ --rootdescend --on-error-stop &> $LOG_FILE_PREFIX.pg_amcheck.log echo "pg_backup validate: amcheck OK" - + echo "Останавливаем сервер СУБД" $PG_BIN_DIR/pg_ctl stop --pgdata=$PG_DATA_TEST_DIR --silent echo "pg_backup validate: server stopped" - + for LOG_FILE in $PG_DATA_TEST_DIR/log/*; do echo "Проверяем отсутствие ошибок в файле $LOG_FILE" grep -P '\b(WARNING|ERROR|FATAL|PANIC)\b' $LOG_FILE && exit 1 || true done echo "pg_backup validate: no problems found in log files" - + echo "Проверяем контрольные суммы данных в кластере СУБД" $PG_BIN_DIR/pg_checksums --check --pgdata=$PG_DATA_TEST_DIR &> $LOG_FILE_PREFIX.pg_checksums.log echo "pg_backup validate: '$PG_DATA_TEST_DIR' checksums OK" - + LOG_FILE=$LOG_FILE_PREFIX.pg_controldata.log echo "Сохраняем управляющую информацию кластера СУБД в файл '$LOG_FILE'" $PG_BIN_DIR/pg_controldata --pgdata=$PG_DATA_TEST_DIR &> $LOG_FILE - + echo "Удаляем папку '$PG_DATA_TEST_DIR', она больше не нужна" rm -r $PG_DATA_TEST_DIR echo "pg_backup validate: temporary directory '$PG_DATA_TEST_DIR' deleted" - + TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) LOG_FILE=$LOG_FILE_PREFIX.validate-success.log @@ -211,25 +280,29 @@ elif test -n "${1:-}"; then echoerr "pg_backup: unknown first parameter '${1:-}'" exit 2 fi - + # ----------------------------------------------------------------------------------------------------------------------- echoinfo "pg_backup: creating started" TIME_START=$(date +%s) # время в Unixtime -BASE_NAME=${BACKUP_DIR}/$(date +%Y-%m-%d.%H%M%S).$(hostname) +BASE_NAME=${BACKUP_DIR}/$(date +%Y-%m-%d.%H%M%S).$(hostname).pg_backup ZSTD_THREADS=$(echo "$(nproc) / 2.5 + 1" | bc) mkdir -p ${BACKUP_DIR} ${WAL_DIR} # создаём директории, если их ещё нет - + # Для многопоточного режима zstd используется степень сжатия 5, которая получена опытным путём. # Это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск с учётом его нагрузки другими процессами. - -echo 'Проверяем необходимость бекапирования WAL файлов (зависит от настройки параметра archive_mode и роли СУБД primary/standby)' + +echo 'Проверяем необходимость бекапирования WAL файлов' +# зависит от текущего дня, настройки параметра archive_mode и роли СУБД primary/standby IS_BACKUP_WAL=$(psql --user=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ - --command="select setting='off' or (pg_is_in_recovery() and setting='on') from pg_settings where name='archive_mode'") - + --command="select extract(doy from now())%${BACKUP_WAL_DOY_DIVIDER}=0 + or setting='off' or (pg_is_in_recovery() and setting='on') + from pg_settings where name='archive_mode'") + if test "$IS_BACKUP_WAL" = "f"; then echo 'Создаём физическую резервную копию (без WAL файлов)' - ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar \ - --pgdata=- | zstd -q -T${ZSTD_THREADS} -5 -o ${BASE_NAME}.pg_basebackup.tar.zst + ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar --pgdata=- \ + | zstd -q -T${ZSTD_THREADS} -5 \ + | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o ${BASE_NAME}.tar.zst.gpg else echo 'Создаём физическую резервную копию (с WAL файлами)' # в библиотеке libzstd многопоточность поддерживается с версии 1.5.0 @@ -238,23 +311,37 @@ else OPT_COMPRESS="server-zstd:level=1" test $(echo "$LIBZSTD_VER>= 1.5" | bc -l) = 1 && OPT_COMPRESS="server-zstd:level=5,workers=${ZSTD_THREADS}" ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --compress=${OPT_COMPRESS} --checkpoint=fast --format=tar \ - --pgdata=${BASE_NAME}.pg_basebackup - zstd -q -T${ZSTD_THREADS} -5 --rm ${BASE_NAME}.pg_basebackup/pg_wal.tar + --pgdata=${BASE_NAME} + + FILES="${BASE_NAME}/base.tar ${BASE_NAME}/pg_wal.tar" + for FILE in $FILES; do + if test -f "$FILE"; then + echo "Сжимаем и шифруем '$FILE'" + zstd -c -q -T${ZSTD_THREADS} -5 $FILE | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.zst.gpg + rm -f $FILE + elif test -f "$FILE.zst"; then + echo "Шифруем '$FILE.zst'" + gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none $FILE.zst + rm -f $FILE.zst + else + echoerr "Файл '$FILE' или '$FILE.zst' не найден" && exit 1 + fi + done fi - + # создаём логическую резервную копию (deprecated) # ${PG_BIN_DIR}/pg_dumpall --username=${PG_USERNAME} --no-password | zstd -q -T${ZSTD_THREADS} -5 -o ${BASE_NAME}.sql.zst - + TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) - + echosucc "pg_backup: created successfully, duration: $TIME_ELAPSED (day:hh:mm:ss)" - + # ----------------------------------------------------------------------------------------------------------------------- # удаляем архивные резервные копии старше N дней (папки и файлы рекурсивно) echo "pg_backup: deleting backup files older than ${BACKUP_AGE_DAYS} days" find ${BACKUP_DIR} -mindepth 1 -mtime +${BACKUP_AGE_DAYS} -delete - + # удаляем архивные WAL файлы старше N дней (сортировка по дате модификации) echo "pg_backup: detect oldest kept WAL file for ${BACKUP_AGE_DAYS} days" WAL_OLD_FILE=$(find ${WAL_DIR} -maxdepth 1 -mtime +${BACKUP_AGE_DAYS} -type f ! -size 0 \ @@ -265,12 +352,12 @@ if test -z "${WAL_OLD_FILE}"; then else echo "pg_backup: WAL old file is ${WAL_OLD_FILE}" WAL_OLD_FILE_EXT=$(echo "${WAL_OLD_FILE}" | grep -oP '\.[a-z\d]+$') # compressed files support (.gz, .zst, .lz4) - + BEFORE_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') ${PG_BIN_DIR}/pg_archivecleanup -x "${WAL_OLD_FILE_EXT}" "${WAL_DIR}" "${WAL_OLD_FILE}" - + AFTER_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') echo "pg_backup: WAL dir size reducing: ${BEFORE_WAL_DIR_SIZE} (before cleanup) -> ${AFTER_WAL_DIR_SIZE} (after cleanup)" fi - + echosucc "pg_backup: done" From 2db7e9b6840ab6245f141ec2dd2f08192812bf56 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月30日 00:27:21 +0300 Subject: [PATCH 066/145] encrypt by GPG, restore command --- pg_backup/README.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index b664182..1a14ef1 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -1,23 +1,31 @@ # Инсталляция сервиса резервного копирования PostgreSQL +## Функциональность +1. Создание полной резервной копии СУБД +1. Валидация корректности и восстанавливаемости резервной копии СУБД +1. Проверка необходимости запуска команд (п. 1 и 2) с текущего сервера +1. Восстановление резервной копии СУБД + ## Как это работает? -На каждом сервере СУБД по расписанию (обычно 1 раз в сутки) запускается [systemd](https://en.wikipedia.org/wiki/Systemd) сервис для создания резервных копий СУБД. +На каждом сервере СУБД по расписанию запускаются [systemd](https://en.wikipedia.org/wiki/Systemd) сервисы: +1. Создание резервной копии СУБД (обычно 1 раз в сутки) +1. Валидация резервной копий СУБД (обычно 1 раз в неделю) -Резервные копии создаются так: +Условие запуска сервисов: * Если [Patroni](https://patroni.readthedocs.io/en/latest/) или [jq](https://jqlang.org/) не инсталлирован, то только с сервера СУБД мастер. * Иначе только с одного сервера СУБД в каждом ЦОДе. Приоритет выбора сервера: синхронная реплика, мастер, асинхронная реплика (с отставанием не более 1000 МБ). > [!NOTE] -> Резервная копия сжимается в формат [`zstd`](https://github.com/facebook/zstd) (16–25% от исходного размера файлов СУБД). Это позволяет экономить место на сетевом диске и уменьшить нагрузку на ввод-вывод. +> Резервная копия сжимается в формат [`zstd`](https://github.com/facebook/zstd) (16–25% от исходного размера файлов СУБД), затем шифруется. Это позволяет экономить место на сетевом диске, уменьшить нагрузку на ввод-вывод, увеличить безопасность. > [!CAUTION] > Внимание! -> 1. Наличие WAL файлов в резервной копии зависит от настройки [`archive_mode`](https://postgrespro.ru/docs/postgresql/17/runtime-config-wal#GUC-ARCHIVE-MODE) и текущей роли сервера (мастер, реплика). -> 2. Для возможности восстановления СУБД из резервной копии, созданной без WAL файлов, должно быть настроено [непрерывное архивирование WAL файлов](https://postgrespro.ru/docs/postgresql/16/continuous-archiving) +> 1. Наличие WAL файлов в резервной копии зависит от текущего дня (по умолчанию каждый 5-й день), настройки [`archive_mode`](https://postgrespro.ru/docs/postgresql/17/runtime-config-wal#GUC-ARCHIVE-MODE) и текущей роли сервера (мастер, реплика). +> 1. Для возможности восстановления СУБД из резервной копии, созданной без WAL файлов, должно быть настроено [непрерывное архивирование WAL файлов](https://postgrespro.ru/docs/postgresql/16/continuous-archiving) через [`archive_command`](https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-ARCHIVE-COMMAND) или [`pg_receivewal`](https://postgrespro.ru/docs/postgresql/16/app-pgreceivewal). -> 3. Следует учесть [ограничения создания резервной копии с реплики](https://postgrespro.ru/docs/postgresql/16/app-pgbasebackup)! +> 1. Следует учесть [ограничения создания резервной копии с реплики](https://postgrespro.ru/docs/postgresql/16/app-pgbasebackup)! ## Настройка создания резервных копий СУБД @@ -76,10 +84,11 @@ systemctl enable pg_backup.service && \ systemctl enable pg_backup_validate.timer && \ systemctl enable pg_backup_validate.service -# проверяем работоспособность (отладка) +# проверяем работоспособность (отладка), выводим сообщения на экран sudo -i -u postgres -- ./pg_backup.sh ExecCondition # будем ли создавать или проверять резервную копию с текущего сервера СУБД (см. код возврата)? -sudo -i -u postgres -- ./pg_backup.sh # создаст резервную копию текущего сервера СУБД (и выведет сообщения на экран) -sudo -i -u postgres -- ./pg_backup.sh validate # проверит восстанавливаемость резервной копии СУБД (и выведет сообщения на экран) +sudo -i -u postgres -- ./pg_backup.sh # создаст резервную копию текущего сервера СУБД +sudo -i -u postgres -- ./pg_backup.sh validate # проверит корректность и восстанавливаемость резервной копии СУБД +sudo -i -u postgres -- ./pg_backup.sh restore SOURCE_BACKUP_FILE_OR_DIR TARGET_PG_DATA_DIR # восстановит резервную копию СУБД # запускаем; сделает резервную копию СУБД, если условие ExecCondition выполнится (НЕ выведет сообщения на экран) systemctl start pg_backup.timer && \ From 72d05ed347e2ad3c0a24bbfa4addc9cc3fb17c8c Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月30日 00:29:08 +0300 Subject: [PATCH 067/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index 1a14ef1..ab3cf94 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -3,7 +3,7 @@ ## Функциональность 1. Создание полной резервной копии СУБД 1. Валидация корректности и восстанавливаемости резервной копии СУБД -1. Проверка необходимости запуска команд (п. 1 и 2) с текущего сервера +1. Проверка необходимости запуска команд (п. 1 и 2) с текущего сервера кластера СУБД 1. Восстановление резервной копии СУБД ## Как это работает? From c0a272ec7485c321400e9d12d593016dd3afbb65 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月30日 00:30:03 +0300 Subject: [PATCH 068/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index ab3cf94..26d9db9 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -27,7 +27,7 @@ или [`pg_receivewal`](https://postgrespro.ru/docs/postgresql/16/app-pgreceivewal). > 1. Следует учесть [ограничения создания резервной копии с реплики](https://postgrespro.ru/docs/postgresql/16/app-pgbasebackup)! -## Настройка создания резервных копий СУБД +## Инсталляция **Шаг 1. Выполнить на терминальном сервере Windows (PowerShell)** ```powershell From 98856c8564a9a9c6dc5c48814ea5cc3f9a7a29a2 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月30日 00:33:05 +0300 Subject: [PATCH 069/145] Update README.md --- pg_backup/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pg_backup/README.md b/pg_backup/README.md index 26d9db9..5611473 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -2,6 +2,7 @@ ## Функциональность 1. Создание полной резервной копии СУБД +1. Удаление старых резервных копий и WAL файлов из архива 1. Валидация корректности и восстанавливаемости резервной копии СУБД 1. Проверка необходимости запуска команд (п. 1 и 2) с текущего сервера кластера СУБД 1. Восстановление резервной копии СУБД From 5632b705610f6ead1ca8cb2df759a0d7f6fa970a Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年8月30日 00:47:41 +0300 Subject: [PATCH 070/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index 5611473..edb9ed6 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -4,7 +4,7 @@ 1. Создание полной резервной копии СУБД 1. Удаление старых резервных копий и WAL файлов из архива 1. Валидация корректности и восстанавливаемости резервной копии СУБД -1. Проверка необходимости запуска команд (п. 1 и 2) с текущего сервера кластера СУБД +1. Проверка необходимости запуска команд (п. 1-3) с текущего сервера кластера СУБД 1. Восстановление резервной копии СУБД ## Как это работает? From b6424a6ac6eae2a7e28318c7a34fc0c04297f11e Mon Sep 17 00:00:00 2001 From: Rinat Date: Mon, 1 Sep 2025 11:40:18 +0300 Subject: [PATCH 071/145] Update pg_backup.sh --- pg_backup/pg_backup.sh | 126 ++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index 2934986..a73d8d1 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -1,18 +1,18 @@ #!/bin/bash # https://habr.com/ru/company/ruvds/blog/325522/ - Bash documentation - + # https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html # set -e - прекращает выполнение скрипта, если команда завершилась ошибкой # set -u - прекращает выполнение скрипта, если встретилась несуществующая переменная # set -x - выводит выполняемые команды в stdout перед выполнением (только для отладки, а то замусоривает журнал!) # set -o pipefail - прекращает выполнение скрипта, даже если одна из частей пайпа завершилась ошибкой set -euo pipefail - + SCRIPT_FILE=$(readlink -f "0ドル") SCRIPT_DIR=$(dirname "$SCRIPT_FILE") - + bash -n "${SCRIPT_FILE}" || exit # Check syntax this file - + # Colors Red='\e[1;31m' Green='\e[0;32m' @@ -24,13 +24,13 @@ Cyan='\e[0;36m' Gray='\e[0;37m' White='\e[1;37m' Reset='\e[0m' - + # Colored messages echoerr() { echo -e "${Red}$@${Reset}" 1>&2; } echowarn() { echo -e "${Yellow}$@${Reset}" 1>&2; } echoinfo() { echo -e "${White}$@${Reset}" ; } echosucc() { echo -e "${Green}$@${Reset}" ; } - + elapsed() { local time_start=1ドル #time_start=$(date +%s) local time_end=2ドル #time_end=$(date +%s) @@ -43,13 +43,13 @@ elapsed() { local ds=$(echo "$dt3-60*$dm" | bc) printf '%dd:%02d:%02d:%02d' $dd $dh $dm $ds #day:hh:mm:ss } - + # меняем приоритет этого процесса ($$ - это его pid) на минимальный (дочерние процессы наследуют значение приоритета родительского процесса) ionice -c 2 -n 7 -p $$ renice -n 19 -p $$ - + source "$SCRIPT_DIR/pg_backup.conf" # include - + PG_PASS_FILE="$SCRIPT_DIR/.pgpass" if test $(whoami) != "postgres"; then echoerr "pg_backup: run script as user postgres, not $(whoami)!" @@ -61,7 +61,7 @@ elif test "$GPG_PASSPHRASE" = "*censored*"; then echoerr "pg_backup: change value of \$GPG_PASSPHRASE in '$SCRIPT_DIR/pg_backup.conf'!" exit 1 fi - + # вычисляем, с какого сервера СУБД будем создавать или проверять резервную копию (Systemd service ExecCondition) if test "${1:-}" = "ExecCondition"; then if ! (command -v patronictl &> /dev/null && command -v jq &> /dev/null); then @@ -72,7 +72,7 @@ if test "${1:-}" = "ExecCondition"; then test ${2:='primary'} = "$PG_ROLE" exit fi - + echo 'pg_backup: check candidate role is "Sync Standby", "Leader", "Replica" (in order of priority)' # https://jqlang.org/manual/ # https://stackoverflow.com/questions/46070012/how-to-filter-an-array-of-json-objects-with-jq @@ -98,28 +98,28 @@ if test "${1:-}" = "ExecCondition"; then exit # восстанавливаем PostgreSQL из резервной копии elif test "${1:-}" = "restore"; then - TIME_START=$(date +%s) # время в Unixtime - + TIME_START=$(date +%s) # время в Unixtime + # скрипт должен запускаться с тремя параметрами test "$#" -ne 3 && echoinfo "Usage: 0ドル restore SOURCE_BACKUP_FILE_OR_DIR TARGET_PG_DATA_DIR" && exit 2 - - BACKUP_FILE_OR_DIR="2ドル" + + BACKUP_FILE_OR_DIR="2ドル" if test -f "$BACKUP_FILE_OR_DIR"; then BACKUP_FILE="$BACKUP_FILE_OR_DIR" elif test -d "$BACKUP_FILE_OR_DIR"; then - BACKUP_FILE=$(find $BACKUP_FILE_OR_DIR -maxdepth 1 -type f -name "base.tar.*" -printf "%p") - test ! -f "$BACKUP_FILE" \ + BACKUP_FILE=$(find $BACKUP_FILE_OR_DIR -maxdepth 1 -type f -name "base.tar.*" -printf "%p") + test ! -f "$BACKUP_FILE" \ && echoerr "pg_backup restore: source backup archive file '$BACKUP_FILE_OR_DIR/base.tar.*' does not exist!" && exit 1 else echoerr "pg_backup restore: source backup archive file/directory '$BACKUP_FILE_OR_DIR' does not exist!" exit 1 fi - + PG_DATA_DIR="3ドル" test ! -d "$PG_DATA_DIR" && echoerr "pg_backup restore: target directory '$PG_DATA_DIR' does not exist!" && exit 1 - + # определяем архиватор по расширению файла - BACKUP_FILE_EXT=$(echo "$BACKUP_FILE" | grep -oP '\.\Ktar\..*$') + BACKUP_FILE_EXT=$(basename "$BACKUP_FILE" | grep -oP '\.\Ktar\..*$') ARCHIVE_TYPE=$(echo "$BACKUP_FILE_EXT" | cut -d. -f2) if test "$ARCHIVE_TYPE" = "zst"; then COMPRESS_PROGRAM="unzstd" @@ -129,28 +129,28 @@ elif test "${1:-}" = "restore"; then echoerr "pg_backup validate: no compress program found" exit 1 fi - + echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_DIR'" - pv -treb $BACKUP_FILE \ + pv -treb $BACKUP_FILE \ | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_DIR - + if test -d "$BACKUP_FILE_OR_DIR"; then FILE="$BACKUP_FILE_OR_DIR/pg_wal.$BACKUP_FILE_EXT" test ! -f "$FILE" && echoerr "Файл '$FILE' не найден" && exit 1 echo "Расшифровываем и распаковываем архив '$FILE' в папку '$PG_DATA_DIR/pg_wal'" - pv -treb $FILE \ + pv -treb $FILE \ | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_DIR/pg_wal fi - + echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" rm -f -r -v $PG_DATA_DIR/*.{signal,{backup,old}{,.*}} $PG_DATA_DIR/log/* - + TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) - echosucc "pg_backup restore: success, duration: $TIME_ELAPSED (day:hh:mm:ss)" - + echosucc "pg_backup restore: success, duration: $TIME_ELAPSED (day:hh:mm:ss)" + cd $PG_DATA_DIR read -p "Укажите роль создаваемого сервера (primary/standby): " PG_ROLE if test "$PG_ROLE" = "primary"; then @@ -161,20 +161,20 @@ elif test "${1:-}" = "restore"; then echoerr "Роль указана неверно, ожидается primary/standby" exit 1 fi - echowarn "Донастройте postgresql.conf и запустите кластер СУБД!" + echowarn "Донастройте postgresql.conf и запустите кластер СУБД!" exit 0 # проверяем корректность и восстанавливаемость PostgreSQL из резервной копии elif test "${1:-}" = "validate"; then TIME_START=$(date +%s) # время в Unixtime - + echo "Получаем название предпоследнего или последнего файла с архивом резервной копии (сортировка по дате модификации)" BACKUP_FILE=$(find $BACKUP_DIR -maxdepth 2 -type f \( -name "*.pg_backup.tar.*" -o -path "*.pg_backup/base.tar.*" \) \ -printf "%T@ %p\n" | sort -n | tail -2 | head -1 | cut -d" " -f2) test -z "$BACKUP_FILE" && echoerr "pg_backup validate: no backup archive file found in directory '$BACKUP_DIR'" && exit 1 echo "pg_backup validate: archive file '$BACKUP_FILE' selected" - + # определяем архиватор по расширению файла - BACKUP_FILE_EXT=$(echo "$BACKUP_FILE" | grep -oP '\.\Ktar\..*$') + BACKUP_FILE_EXT=$(basename "$BACKUP_FILE" | grep -oP '\.\Ktar\..*$') ARCHIVE_TYPE=$(echo "$BACKUP_FILE_EXT" | cut -d. -f2) if test "$ARCHIVE_TYPE" = "zst"; then COMPRESS_PROGRAM="unzstd" @@ -186,23 +186,23 @@ elif test "${1:-}" = "validate"; then fi LOG_FILE_PREFIX=$(dirname $BACKUP_FILE)/$(basename $BACKUP_FILE .$BACKUP_FILE_EXT) touch $LOG_FILE_PREFIX.validate-selected.log - + PG_DATA_TEST_DIR=$(dirname $(dirname $BACKUP_DIR))/pgdata_validate echo "Создаём тестовую папку '$PG_DATA_TEST_DIR' для данных СУБД, удаляем старые данные (защита от предыдущего неудачного запуска скрипта)" test -d "$PG_DATA_TEST_DIR" && rm -r $PG_DATA_TEST_DIR && echo "pg_backup validate: old temporary directory '$PG_DATA_TEST_DIR' deleted" mkdir $PG_DATA_TEST_DIR echo "pg_backup validate: temporary directory '$PG_DATA_TEST_DIR' created" - + echo "Проверяем, что у папки '$PG_DATA_TEST_DIR' права доступа 750 или 700, иначе PostgreSQL не запустится" chmod 750 $PG_DATA_TEST_DIR # chmod не гарантирует изменение прав доступа на SMB/CIFS stat -c "%a" $PG_DATA_TEST_DIR | grep -qP '^7[05]0$' \ || (echoerr "pg_backup validate: directory '$PG_DATA_TEST_DIR' permission must be 750 or 700" && exit 1) - + echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_TEST_DIR'" gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $BACKUP_FILE \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR - + BACKUP_BASE_DIR=$(echo "$BACKUP_FILE" | grep -qP '\.pg_backup/base\.tar\.' && dirname "$BACKUP_FILE" || true) if test -z "$BACKUP_BASE_DIR"; then echo "Проверяем существование папки '$WAL_DIR', из неё могут быть прочитаны дополнительные WAL файлы" @@ -210,65 +210,65 @@ elif test "${1:-}" = "validate"; then else echo "Копируем '$BACKUP_BASE_DIR/backup_manifest' в папку '$PG_DATA_TEST_DIR'" cp $BACKUP_BASE_DIR/backup_manifest $PG_DATA_TEST_DIR - + FILE="$BACKUP_BASE_DIR/pg_wal.$BACKUP_FILE_EXT" test ! -f "$FILE" && echoerr "Файл '$FILE' не найден" && exit 1 echo "Расшифровываем и распаковываем архив '$FILE' в папку '$PG_DATA_TEST_DIR/pg_wal'" gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $FILE \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR/pg_wal fi - + DIR_SIZE=$(du -sh "$PG_DATA_TEST_DIR" | grep -oP '^\S+') echo "pg_backup validate: archive file extracted to directory '$PG_DATA_TEST_DIR' (total size: $DIR_SIZE)" - + echo "Проверяем целостность копии кластера СУБД, сделанной программой pg_basebackup, по манифесту backup_manifest" $PG_BIN_DIR/pg_verifybackup --no-parse-wal --exit-on-error --quiet $PG_DATA_TEST_DIR &> $LOG_FILE_PREFIX.pg_verifybackup.log echo "pg_backup validate: '$PG_DATA_TEST_DIR' backup verify OK" - + echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" rm -f -r -v $PG_DATA_TEST_DIR/*.{signal,{backup,old}{,.*}} $PG_DATA_TEST_DIR/log/* - + echo "Разрешаем локальному пользователю postgres аутентифицироваться методом peer" sed -i '1i local all postgres peer' $PG_DATA_TEST_DIR/pg_hba.conf # добавляем строчку в начало файла - + echo "(Ре)стартуем сервер СУБД в роли мастер (рестарт - это защита от предыдущего неудачного запуска скрипта)" touch $PG_DATA_TEST_DIR/recovery.signal PG_PORT=55432 $PG_BIN_DIR/pg_ctl restart --pgdata=$PG_DATA_TEST_DIR --silent \ --options="-p $PG_PORT -B 128MB --cluster_name=BACKUP_VALIDATE --log_directory=log --archive_mode=off" echo "pg_backup validate: server started (port $PG_PORT)" - + echo "Проверяем подключение к СУБД" psql --port=$PG_PORT --user=$PG_USERNAME --no-password --dbname=postgres --no-psqlrc --command='\conninfo' echo "pg_backup validate: server connection OK" - + echo "Проверяем логическую целостность таблиц и индексов (amcheck)" $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* \ --rootdescend --on-error-stop &> $LOG_FILE_PREFIX.pg_amcheck.log echo "pg_backup validate: amcheck OK" - + echo "Останавливаем сервер СУБД" $PG_BIN_DIR/pg_ctl stop --pgdata=$PG_DATA_TEST_DIR --silent echo "pg_backup validate: server stopped" - + for LOG_FILE in $PG_DATA_TEST_DIR/log/*; do echo "Проверяем отсутствие ошибок в файле $LOG_FILE" grep -P '\b(WARNING|ERROR|FATAL|PANIC)\b' $LOG_FILE && exit 1 || true done echo "pg_backup validate: no problems found in log files" - + echo "Проверяем контрольные суммы данных в кластере СУБД" $PG_BIN_DIR/pg_checksums --check --pgdata=$PG_DATA_TEST_DIR &> $LOG_FILE_PREFIX.pg_checksums.log echo "pg_backup validate: '$PG_DATA_TEST_DIR' checksums OK" - + LOG_FILE=$LOG_FILE_PREFIX.pg_controldata.log echo "Сохраняем управляющую информацию кластера СУБД в файл '$LOG_FILE'" $PG_BIN_DIR/pg_controldata --pgdata=$PG_DATA_TEST_DIR &> $LOG_FILE - + echo "Удаляем папку '$PG_DATA_TEST_DIR', она больше не нужна" rm -r $PG_DATA_TEST_DIR echo "pg_backup validate: temporary directory '$PG_DATA_TEST_DIR' deleted" - + TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) LOG_FILE=$LOG_FILE_PREFIX.validate-success.log @@ -280,24 +280,24 @@ elif test -n "${1:-}"; then echoerr "pg_backup: unknown first parameter '${1:-}'" exit 2 fi - + # ----------------------------------------------------------------------------------------------------------------------- echoinfo "pg_backup: creating started" TIME_START=$(date +%s) # время в Unixtime BASE_NAME=${BACKUP_DIR}/$(date +%Y-%m-%d.%H%M%S).$(hostname).pg_backup ZSTD_THREADS=$(echo "$(nproc) / 2.5 + 1" | bc) mkdir -p ${BACKUP_DIR} ${WAL_DIR} # создаём директории, если их ещё нет - + # Для многопоточного режима zstd используется степень сжатия 5, которая получена опытным путём. # Это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск с учётом его нагрузки другими процессами. - + echo 'Проверяем необходимость бекапирования WAL файлов' # зависит от текущего дня, настройки параметра archive_mode и роли СУБД primary/standby IS_BACKUP_WAL=$(psql --user=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ --command="select extract(doy from now())%${BACKUP_WAL_DOY_DIVIDER}=0 or setting='off' or (pg_is_in_recovery() and setting='on') from pg_settings where name='archive_mode'") - + if test "$IS_BACKUP_WAL" = "f"; then echo 'Создаём физическую резервную копию (без WAL файлов)' ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar --pgdata=- \ @@ -312,7 +312,7 @@ else test $(echo "$LIBZSTD_VER>= 1.5" | bc -l) = 1 && OPT_COMPRESS="server-zstd:level=5,workers=${ZSTD_THREADS}" ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --compress=${OPT_COMPRESS} --checkpoint=fast --format=tar \ --pgdata=${BASE_NAME} - + FILES="${BASE_NAME}/base.tar ${BASE_NAME}/pg_wal.tar" for FILE in $FILES; do if test -f "$FILE"; then @@ -328,20 +328,20 @@ else fi done fi - + # создаём логическую резервную копию (deprecated) # ${PG_BIN_DIR}/pg_dumpall --username=${PG_USERNAME} --no-password | zstd -q -T${ZSTD_THREADS} -5 -o ${BASE_NAME}.sql.zst - + TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) - + echosucc "pg_backup: created successfully, duration: $TIME_ELAPSED (day:hh:mm:ss)" - + # ----------------------------------------------------------------------------------------------------------------------- # удаляем архивные резервные копии старше N дней (папки и файлы рекурсивно) echo "pg_backup: deleting backup files older than ${BACKUP_AGE_DAYS} days" find ${BACKUP_DIR} -mindepth 1 -mtime +${BACKUP_AGE_DAYS} -delete - + # удаляем архивные WAL файлы старше N дней (сортировка по дате модификации) echo "pg_backup: detect oldest kept WAL file for ${BACKUP_AGE_DAYS} days" WAL_OLD_FILE=$(find ${WAL_DIR} -maxdepth 1 -mtime +${BACKUP_AGE_DAYS} -type f ! -size 0 \ @@ -352,12 +352,12 @@ if test -z "${WAL_OLD_FILE}"; then else echo "pg_backup: WAL old file is ${WAL_OLD_FILE}" WAL_OLD_FILE_EXT=$(echo "${WAL_OLD_FILE}" | grep -oP '\.[a-z\d]+$') # compressed files support (.gz, .zst, .lz4) - + BEFORE_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') ${PG_BIN_DIR}/pg_archivecleanup -x "${WAL_OLD_FILE_EXT}" "${WAL_DIR}" "${WAL_OLD_FILE}" - + AFTER_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') echo "pg_backup: WAL dir size reducing: ${BEFORE_WAL_DIR_SIZE} (before cleanup) -> ${AFTER_WAL_DIR_SIZE} (after cleanup)" fi - + echosucc "pg_backup: done" From 15768ea5445cdedf0f13253ec09b73bd2ad6d02e Mon Sep 17 00:00:00 2001 From: Rinat Date: Mon, 1 Sep 2025 17:00:04 +0300 Subject: [PATCH 072/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index edb9ed6..fe8b58c 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -18,7 +18,7 @@ * Иначе только с одного сервера СУБД в каждом ЦОДе. Приоритет выбора сервера: синхронная реплика, мастер, асинхронная реплика (с отставанием не более 1000 МБ). > [!NOTE] -> Резервная копия сжимается в формат [`zstd`](https://github.com/facebook/zstd) (16–25% от исходного размера файлов СУБД), затем шифруется. Это позволяет экономить место на сетевом диске, уменьшить нагрузку на ввод-вывод, увеличить безопасность. +> Резервная копия сжимается в формат [`zstd`](https://github.com/facebook/zstd) (16–25% от исходного размера файлов СУБД) и шифруется. Это позволяет экономить место на сетевом диске, уменьшить нагрузку на ввод-вывод, увеличить безопасность. > [!CAUTION] > Внимание! From ee8b7ce3a01bfa394eeddb7de18392511ffe2250 Mon Sep 17 00:00:00 2001 From: Rinat Date: Mon, 1 Sep 2025 17:31:44 +0300 Subject: [PATCH 073/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index fe8b58c..a553b6f 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -18,7 +18,7 @@ * Иначе только с одного сервера СУБД в каждом ЦОДе. Приоритет выбора сервера: синхронная реплика, мастер, асинхронная реплика (с отставанием не более 1000 МБ). > [!NOTE] -> Резервная копия сжимается в формат [`zstd`](https://github.com/facebook/zstd) (16–25% от исходного размера файлов СУБД) и шифруется. Это позволяет экономить место на сетевом диске, уменьшить нагрузку на ввод-вывод, увеличить безопасность. +> Резервная копия сжимается в формат [`zstd`](https://github.com/facebook/zstd) (16–30% от исходного размера файлов СУБД) и шифруется. Это позволяет экономить место на сетевом диске, уменьшить нагрузку на ввод-вывод, увеличить безопасность. > [!CAUTION] > Внимание! From 56a0a0589f3a956cacb4c8fcadb5aebecf11d40c Mon Sep 17 00:00:00 2001 From: Rinat Date: Mon, 1 Sep 2025 17:34:28 +0300 Subject: [PATCH 074/145] Update pg_backup.sh --- pg_backup/pg_backup.sh | 125 +++++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index a73d8d1..17ba9c3 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -1,18 +1,18 @@ #!/bin/bash # https://habr.com/ru/company/ruvds/blog/325522/ - Bash documentation - + # https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html # set -e - прекращает выполнение скрипта, если команда завершилась ошибкой # set -u - прекращает выполнение скрипта, если встретилась несуществующая переменная # set -x - выводит выполняемые команды в stdout перед выполнением (только для отладки, а то замусоривает журнал!) # set -o pipefail - прекращает выполнение скрипта, даже если одна из частей пайпа завершилась ошибкой set -euo pipefail - + SCRIPT_FILE=$(readlink -f "0ドル") SCRIPT_DIR=$(dirname "$SCRIPT_FILE") - + bash -n "${SCRIPT_FILE}" || exit # Check syntax this file - + # Colors Red='\e[1;31m' Green='\e[0;32m' @@ -24,13 +24,13 @@ Cyan='\e[0;36m' Gray='\e[0;37m' White='\e[1;37m' Reset='\e[0m' - + # Colored messages echoerr() { echo -e "${Red}$@${Reset}" 1>&2; } echowarn() { echo -e "${Yellow}$@${Reset}" 1>&2; } echoinfo() { echo -e "${White}$@${Reset}" ; } echosucc() { echo -e "${Green}$@${Reset}" ; } - + elapsed() { local time_start=1ドル #time_start=$(date +%s) local time_end=2ドル #time_end=$(date +%s) @@ -43,13 +43,13 @@ elapsed() { local ds=$(echo "$dt3-60*$dm" | bc) printf '%dd:%02d:%02d:%02d' $dd $dh $dm $ds #day:hh:mm:ss } - + # меняем приоритет этого процесса ($$ - это его pid) на минимальный (дочерние процессы наследуют значение приоритета родительского процесса) ionice -c 2 -n 7 -p $$ renice -n 19 -p $$ - + source "$SCRIPT_DIR/pg_backup.conf" # include - + PG_PASS_FILE="$SCRIPT_DIR/.pgpass" if test $(whoami) != "postgres"; then echoerr "pg_backup: run script as user postgres, not $(whoami)!" @@ -61,7 +61,7 @@ elif test "$GPG_PASSPHRASE" = "*censored*"; then echoerr "pg_backup: change value of \$GPG_PASSPHRASE in '$SCRIPT_DIR/pg_backup.conf'!" exit 1 fi - + # вычисляем, с какого сервера СУБД будем создавать или проверять резервную копию (Systemd service ExecCondition) if test "${1:-}" = "ExecCondition"; then if ! (command -v patronictl &> /dev/null && command -v jq &> /dev/null); then @@ -72,7 +72,7 @@ if test "${1:-}" = "ExecCondition"; then test ${2:='primary'} = "$PG_ROLE" exit fi - + echo 'pg_backup: check candidate role is "Sync Standby", "Leader", "Replica" (in order of priority)' # https://jqlang.org/manual/ # https://stackoverflow.com/questions/46070012/how-to-filter-an-array-of-json-objects-with-jq @@ -98,26 +98,26 @@ if test "${1:-}" = "ExecCondition"; then exit # восстанавливаем PostgreSQL из резервной копии elif test "${1:-}" = "restore"; then - TIME_START=$(date +%s) # время в Unixtime - + TIME_START=$(date +%s) # время в Unixtime + # скрипт должен запускаться с тремя параметрами test "$#" -ne 3 && echoinfo "Usage: 0ドル restore SOURCE_BACKUP_FILE_OR_DIR TARGET_PG_DATA_DIR" && exit 2 - - BACKUP_FILE_OR_DIR="2ドル" + + BACKUP_FILE_OR_DIR="2ドル" if test -f "$BACKUP_FILE_OR_DIR"; then BACKUP_FILE="$BACKUP_FILE_OR_DIR" elif test -d "$BACKUP_FILE_OR_DIR"; then - BACKUP_FILE=$(find $BACKUP_FILE_OR_DIR -maxdepth 1 -type f -name "base.tar.*" -printf "%p") - test ! -f "$BACKUP_FILE" \ + BACKUP_FILE=$(find $BACKUP_FILE_OR_DIR -maxdepth 1 -type f -name "base.tar.*" -printf "%p") + test ! -f "$BACKUP_FILE" \ && echoerr "pg_backup restore: source backup archive file '$BACKUP_FILE_OR_DIR/base.tar.*' does not exist!" && exit 1 else echoerr "pg_backup restore: source backup archive file/directory '$BACKUP_FILE_OR_DIR' does not exist!" exit 1 fi - + PG_DATA_DIR="3ドル" test ! -d "$PG_DATA_DIR" && echoerr "pg_backup restore: target directory '$PG_DATA_DIR' does not exist!" && exit 1 - + # определяем архиватор по расширению файла BACKUP_FILE_EXT=$(basename "$BACKUP_FILE" | grep -oP '\.\Ktar\..*$') ARCHIVE_TYPE=$(echo "$BACKUP_FILE_EXT" | cut -d. -f2) @@ -129,28 +129,28 @@ elif test "${1:-}" = "restore"; then echoerr "pg_backup validate: no compress program found" exit 1 fi - + echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_DIR'" - pv -treb $BACKUP_FILE \ + pv -treb $BACKUP_FILE \ | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_DIR - + if test -d "$BACKUP_FILE_OR_DIR"; then FILE="$BACKUP_FILE_OR_DIR/pg_wal.$BACKUP_FILE_EXT" test ! -f "$FILE" && echoerr "Файл '$FILE' не найден" && exit 1 echo "Расшифровываем и распаковываем архив '$FILE' в папку '$PG_DATA_DIR/pg_wal'" - pv -treb $FILE \ + pv -treb $FILE \ | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_DIR/pg_wal fi - + echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" rm -f -r -v $PG_DATA_DIR/*.{signal,{backup,old}{,.*}} $PG_DATA_DIR/log/* - + TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) - echosucc "pg_backup restore: success, duration: $TIME_ELAPSED (day:hh:mm:ss)" - + echosucc "pg_backup restore: success, duration: $TIME_ELAPSED (day:hh:mm:ss)" + cd $PG_DATA_DIR read -p "Укажите роль создаваемого сервера (primary/standby): " PG_ROLE if test "$PG_ROLE" = "primary"; then @@ -161,18 +161,18 @@ elif test "${1:-}" = "restore"; then echoerr "Роль указана неверно, ожидается primary/standby" exit 1 fi - echowarn "Донастройте postgresql.conf и запустите кластер СУБД!" + echowarn "Донастройте postgresql.conf и запустите кластер СУБД!" exit 0 # проверяем корректность и восстанавливаемость PostgreSQL из резервной копии elif test "${1:-}" = "validate"; then TIME_START=$(date +%s) # время в Unixtime - + echo "Получаем название предпоследнего или последнего файла с архивом резервной копии (сортировка по дате модификации)" BACKUP_FILE=$(find $BACKUP_DIR -maxdepth 2 -type f \( -name "*.pg_backup.tar.*" -o -path "*.pg_backup/base.tar.*" \) \ -printf "%T@ %p\n" | sort -n | tail -2 | head -1 | cut -d" " -f2) test -z "$BACKUP_FILE" && echoerr "pg_backup validate: no backup archive file found in directory '$BACKUP_DIR'" && exit 1 echo "pg_backup validate: archive file '$BACKUP_FILE' selected" - + # определяем архиватор по расширению файла BACKUP_FILE_EXT=$(basename "$BACKUP_FILE" | grep -oP '\.\Ktar\..*$') ARCHIVE_TYPE=$(echo "$BACKUP_FILE_EXT" | cut -d. -f2) @@ -186,23 +186,23 @@ elif test "${1:-}" = "validate"; then fi LOG_FILE_PREFIX=$(dirname $BACKUP_FILE)/$(basename $BACKUP_FILE .$BACKUP_FILE_EXT) touch $LOG_FILE_PREFIX.validate-selected.log - + PG_DATA_TEST_DIR=$(dirname $(dirname $BACKUP_DIR))/pgdata_validate echo "Создаём тестовую папку '$PG_DATA_TEST_DIR' для данных СУБД, удаляем старые данные (защита от предыдущего неудачного запуска скрипта)" test -d "$PG_DATA_TEST_DIR" && rm -r $PG_DATA_TEST_DIR && echo "pg_backup validate: old temporary directory '$PG_DATA_TEST_DIR' deleted" mkdir $PG_DATA_TEST_DIR echo "pg_backup validate: temporary directory '$PG_DATA_TEST_DIR' created" - + echo "Проверяем, что у папки '$PG_DATA_TEST_DIR' права доступа 750 или 700, иначе PostgreSQL не запустится" chmod 750 $PG_DATA_TEST_DIR # chmod не гарантирует изменение прав доступа на SMB/CIFS stat -c "%a" $PG_DATA_TEST_DIR | grep -qP '^7[05]0$' \ || (echoerr "pg_backup validate: directory '$PG_DATA_TEST_DIR' permission must be 750 or 700" && exit 1) - + echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_TEST_DIR'" gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $BACKUP_FILE \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR - + BACKUP_BASE_DIR=$(echo "$BACKUP_FILE" | grep -qP '\.pg_backup/base\.tar\.' && dirname "$BACKUP_FILE" || true) if test -z "$BACKUP_BASE_DIR"; then echo "Проверяем существование папки '$WAL_DIR', из неё могут быть прочитаны дополнительные WAL файлы" @@ -210,65 +210,66 @@ elif test "${1:-}" = "validate"; then else echo "Копируем '$BACKUP_BASE_DIR/backup_manifest' в папку '$PG_DATA_TEST_DIR'" cp $BACKUP_BASE_DIR/backup_manifest $PG_DATA_TEST_DIR - + FILE="$BACKUP_BASE_DIR/pg_wal.$BACKUP_FILE_EXT" test ! -f "$FILE" && echoerr "Файл '$FILE' не найден" && exit 1 echo "Расшифровываем и распаковываем архив '$FILE' в папку '$PG_DATA_TEST_DIR/pg_wal'" gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $FILE \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR/pg_wal fi - + DIR_SIZE=$(du -sh "$PG_DATA_TEST_DIR" | grep -oP '^\S+') echo "pg_backup validate: archive file extracted to directory '$PG_DATA_TEST_DIR' (total size: $DIR_SIZE)" - + echo "Проверяем целостность копии кластера СУБД, сделанной программой pg_basebackup, по манифесту backup_manifest" $PG_BIN_DIR/pg_verifybackup --no-parse-wal --exit-on-error --quiet $PG_DATA_TEST_DIR &> $LOG_FILE_PREFIX.pg_verifybackup.log echo "pg_backup validate: '$PG_DATA_TEST_DIR' backup verify OK" - + echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" rm -f -r -v $PG_DATA_TEST_DIR/*.{signal,{backup,old}{,.*}} $PG_DATA_TEST_DIR/log/* - + echo "Разрешаем локальному пользователю postgres аутентифицироваться методом peer" sed -i '1i local all postgres peer' $PG_DATA_TEST_DIR/pg_hba.conf # добавляем строчку в начало файла - + echo "(Ре)стартуем сервер СУБД в роли мастер (рестарт - это защита от предыдущего неудачного запуска скрипта)" touch $PG_DATA_TEST_DIR/recovery.signal PG_PORT=55432 $PG_BIN_DIR/pg_ctl restart --pgdata=$PG_DATA_TEST_DIR --silent \ - --options="-p $PG_PORT -B 128MB --cluster_name=BACKUP_VALIDATE --log_directory=log --archive_mode=off" + --options="-p $PG_PORT -B 128MB --cluster_name=BACKUP_VALIDATE --archive_mode=off --log_directory=$PG_DATA_TEST_DIR/log" \ + --options="--hba_file=$PG_DATA_TEST_DIR/pg_hba.conf --ident-file=$PG_DATA_TEST_DIR/pg_ident.conf" echo "pg_backup validate: server started (port $PG_PORT)" - + echo "Проверяем подключение к СУБД" psql --port=$PG_PORT --user=$PG_USERNAME --no-password --dbname=postgres --no-psqlrc --command='\conninfo' echo "pg_backup validate: server connection OK" - + echo "Проверяем логическую целостность таблиц и индексов (amcheck)" $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* \ --rootdescend --on-error-stop &> $LOG_FILE_PREFIX.pg_amcheck.log echo "pg_backup validate: amcheck OK" - + echo "Останавливаем сервер СУБД" $PG_BIN_DIR/pg_ctl stop --pgdata=$PG_DATA_TEST_DIR --silent echo "pg_backup validate: server stopped" - + for LOG_FILE in $PG_DATA_TEST_DIR/log/*; do echo "Проверяем отсутствие ошибок в файле $LOG_FILE" grep -P '\b(WARNING|ERROR|FATAL|PANIC)\b' $LOG_FILE && exit 1 || true done echo "pg_backup validate: no problems found in log files" - + echo "Проверяем контрольные суммы данных в кластере СУБД" $PG_BIN_DIR/pg_checksums --check --pgdata=$PG_DATA_TEST_DIR &> $LOG_FILE_PREFIX.pg_checksums.log echo "pg_backup validate: '$PG_DATA_TEST_DIR' checksums OK" - + LOG_FILE=$LOG_FILE_PREFIX.pg_controldata.log echo "Сохраняем управляющую информацию кластера СУБД в файл '$LOG_FILE'" $PG_BIN_DIR/pg_controldata --pgdata=$PG_DATA_TEST_DIR &> $LOG_FILE - + echo "Удаляем папку '$PG_DATA_TEST_DIR', она больше не нужна" rm -r $PG_DATA_TEST_DIR echo "pg_backup validate: temporary directory '$PG_DATA_TEST_DIR' deleted" - + TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) LOG_FILE=$LOG_FILE_PREFIX.validate-success.log @@ -280,24 +281,24 @@ elif test -n "${1:-}"; then echoerr "pg_backup: unknown first parameter '${1:-}'" exit 2 fi - + # ----------------------------------------------------------------------------------------------------------------------- echoinfo "pg_backup: creating started" TIME_START=$(date +%s) # время в Unixtime BASE_NAME=${BACKUP_DIR}/$(date +%Y-%m-%d.%H%M%S).$(hostname).pg_backup ZSTD_THREADS=$(echo "$(nproc) / 2.5 + 1" | bc) mkdir -p ${BACKUP_DIR} ${WAL_DIR} # создаём директории, если их ещё нет - + # Для многопоточного режима zstd используется степень сжатия 5, которая получена опытным путём. # Это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск с учётом его нагрузки другими процессами. - + echo 'Проверяем необходимость бекапирования WAL файлов' # зависит от текущего дня, настройки параметра archive_mode и роли СУБД primary/standby IS_BACKUP_WAL=$(psql --user=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ --command="select extract(doy from now())%${BACKUP_WAL_DOY_DIVIDER}=0 or setting='off' or (pg_is_in_recovery() and setting='on') from pg_settings where name='archive_mode'") - + if test "$IS_BACKUP_WAL" = "f"; then echo 'Создаём физическую резервную копию (без WAL файлов)' ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar --pgdata=- \ @@ -312,7 +313,7 @@ else test $(echo "$LIBZSTD_VER>= 1.5" | bc -l) = 1 && OPT_COMPRESS="server-zstd:level=5,workers=${ZSTD_THREADS}" ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --compress=${OPT_COMPRESS} --checkpoint=fast --format=tar \ --pgdata=${BASE_NAME} - + FILES="${BASE_NAME}/base.tar ${BASE_NAME}/pg_wal.tar" for FILE in $FILES; do if test -f "$FILE"; then @@ -328,20 +329,20 @@ else fi done fi - + # создаём логическую резервную копию (deprecated) # ${PG_BIN_DIR}/pg_dumpall --username=${PG_USERNAME} --no-password | zstd -q -T${ZSTD_THREADS} -5 -o ${BASE_NAME}.sql.zst - + TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) - + echosucc "pg_backup: created successfully, duration: $TIME_ELAPSED (day:hh:mm:ss)" - + # ----------------------------------------------------------------------------------------------------------------------- # удаляем архивные резервные копии старше N дней (папки и файлы рекурсивно) echo "pg_backup: deleting backup files older than ${BACKUP_AGE_DAYS} days" find ${BACKUP_DIR} -mindepth 1 -mtime +${BACKUP_AGE_DAYS} -delete - + # удаляем архивные WAL файлы старше N дней (сортировка по дате модификации) echo "pg_backup: detect oldest kept WAL file for ${BACKUP_AGE_DAYS} days" WAL_OLD_FILE=$(find ${WAL_DIR} -maxdepth 1 -mtime +${BACKUP_AGE_DAYS} -type f ! -size 0 \ @@ -352,12 +353,12 @@ if test -z "${WAL_OLD_FILE}"; then else echo "pg_backup: WAL old file is ${WAL_OLD_FILE}" WAL_OLD_FILE_EXT=$(echo "${WAL_OLD_FILE}" | grep -oP '\.[a-z\d]+$') # compressed files support (.gz, .zst, .lz4) - + BEFORE_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') ${PG_BIN_DIR}/pg_archivecleanup -x "${WAL_OLD_FILE_EXT}" "${WAL_DIR}" "${WAL_OLD_FILE}" - + AFTER_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') echo "pg_backup: WAL dir size reducing: ${BEFORE_WAL_DIR_SIZE} (before cleanup) -> ${AFTER_WAL_DIR_SIZE} (after cleanup)" fi - + echosucc "pg_backup: done" From cd91a8fb5a0a1382afdda3847c15ce47bf4016fe Mon Sep 17 00:00:00 2001 From: Rinat Date: Mon, 1 Sep 2025 19:51:37 +0300 Subject: [PATCH 075/145] Update pg_backup.sh --- pg_backup/pg_backup.sh | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index 17ba9c3..a54efd7 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -222,7 +222,9 @@ elif test "${1:-}" = "validate"; then echo "pg_backup validate: archive file extracted to directory '$PG_DATA_TEST_DIR' (total size: $DIR_SIZE)" echo "Проверяем целостность копии кластера СУБД, сделанной программой pg_basebackup, по манифесту backup_manifest" - $PG_BIN_DIR/pg_verifybackup --no-parse-wal --exit-on-error --quiet $PG_DATA_TEST_DIR &> $LOG_FILE_PREFIX.pg_verifybackup.log + $PG_BIN_DIR/pg_verifybackup --no-parse-wal --exit-on-error --quiet $PG_DATA_TEST_DIR \ + 1> $LOG_FILE_PREFIX.pg_verifybackup.stdout.log \ + 2> $LOG_FILE_PREFIX.pg_verifybackup.stderr.log echo "pg_backup validate: '$PG_DATA_TEST_DIR' backup verify OK" echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" @@ -240,13 +242,23 @@ elif test "${1:-}" = "validate"; then echo "pg_backup validate: server started (port $PG_PORT)" echo "Проверяем подключение к СУБД" - psql --port=$PG_PORT --user=$PG_USERNAME --no-password --dbname=postgres --no-psqlrc --command='\conninfo' - echo "pg_backup validate: server connection OK" - - echo "Проверяем логическую целостность таблиц и индексов (amcheck)" - $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* \ - --rootdescend --on-error-stop &> $LOG_FILE_PREFIX.pg_amcheck.log - echo "pg_backup validate: amcheck OK" + if ! psql --port=$PG_PORT --user=$PG_USERNAME --no-password --dbname=postgres --no-psqlrc --echo-errors --command='\conninfo' \ + 1> $LOG_FILE_PREFIX.psql.stdout.log \ + 2> $LOG_FILE_PREFIX.psql.stderr.log ; then + echowarn "pg_backup validate: server connection ERROR" + else + echo "pg_backup validate: server connection OK" + + echo "Проверяем логическую целостность таблиц и индексов (amcheck)" + if ! $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* \ + --rootdescend --on-error-stop \ + 1> $LOG_FILE_PREFIX.pg_amcheck.stdout.log \ + 2> $LOG_FILE_PREFIX.pg_amcheck.stderr.log ; then + echowarn "pg_backup validate: amcheck ERROR" + else + echo "pg_backup validate: amcheck OK" + fi + fi echo "Останавливаем сервер СУБД" $PG_BIN_DIR/pg_ctl stop --pgdata=$PG_DATA_TEST_DIR --silent @@ -259,12 +271,15 @@ elif test "${1:-}" = "validate"; then echo "pg_backup validate: no problems found in log files" echo "Проверяем контрольные суммы данных в кластере СУБД" - $PG_BIN_DIR/pg_checksums --check --pgdata=$PG_DATA_TEST_DIR &> $LOG_FILE_PREFIX.pg_checksums.log + $PG_BIN_DIR/pg_checksums --check --pgdata=$PG_DATA_TEST_DIR \ + 1> $LOG_FILE_PREFIX.pg_checksums.stdout.log + 2> $LOG_FILE_PREFIX.pg_checksums.stderr.log echo "pg_backup validate: '$PG_DATA_TEST_DIR' checksums OK" - LOG_FILE=$LOG_FILE_PREFIX.pg_controldata.log - echo "Сохраняем управляющую информацию кластера СУБД в файл '$LOG_FILE'" - $PG_BIN_DIR/pg_controldata --pgdata=$PG_DATA_TEST_DIR &> $LOG_FILE + echo "Сохраняем управляющую информацию кластера СУБД" + $PG_BIN_DIR/pg_controldata --pgdata=$PG_DATA_TEST_DIR \ + 1> $LOG_FILE_PREFIX.pg_controldata.stdout.log \ + 2> $LOG_FILE_PREFIX.pg_controldata.stderr.log echo "Удаляем папку '$PG_DATA_TEST_DIR', она больше не нужна" rm -r $PG_DATA_TEST_DIR From 8b87ddf8a525051b79cbb8594c391ad50eb5262e Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 2 Sep 2025 12:18:04 +0300 Subject: [PATCH 076/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index a553b6f..4e3ab85 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -18,7 +18,7 @@ * Иначе только с одного сервера СУБД в каждом ЦОДе. Приоритет выбора сервера: синхронная реплика, мастер, асинхронная реплика (с отставанием не более 1000 МБ). > [!NOTE] -> Резервная копия сжимается в формат [`zstd`](https://github.com/facebook/zstd) (16–30% от исходного размера файлов СУБД) и шифруется. Это позволяет экономить место на сетевом диске, уменьшить нагрузку на ввод-вывод, увеличить безопасность. +> Резервная копия сжимается в формат [`zstd`](https://github.com/facebook/zstd) (10–30% от исходного размера файлов СУБД) и шифруется. Это позволяет экономить место на сетевом диске, уменьшить нагрузку на ввод-вывод, увеличить безопасность. > [!CAUTION] > Внимание! From ab43918550276a0de6d33fee221912ae75960b4c Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 2 Sep 2025 14:29:06 +0300 Subject: [PATCH 077/145] Update README.md --- pg_backup/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pg_backup/README.md b/pg_backup/README.md index 4e3ab85..a65d648 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -7,6 +7,11 @@ 1. Проверка необходимости запуска команд (п. 1-3) с текущего сервера кластера СУБД 1. Восстановление резервной копии СУБД +## Требования +* GNU/Linux +* PostgreSQL ≥ v15 +* Bash ≥ 4.4 + ## Как это работает? На каждом сервере СУБД по расписанию запускаются [systemd](https://en.wikipedia.org/wiki/Systemd) сервисы: From b3510c93296bf94e09a8eb6104e6064fa1ae585b Mon Sep 17 00:00:00 2001 From: Rinat Date: Wed, 3 Sep 2025 16:42:43 +0300 Subject: [PATCH 078/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index a65d648..e5aa8fc 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -23,7 +23,7 @@ * Иначе только с одного сервера СУБД в каждом ЦОДе. Приоритет выбора сервера: синхронная реплика, мастер, асинхронная реплика (с отставанием не более 1000 МБ). > [!NOTE] -> Резервная копия сжимается в формат [`zstd`](https://github.com/facebook/zstd) (10–30% от исходного размера файлов СУБД) и шифруется. Это позволяет экономить место на сетевом диске, уменьшить нагрузку на ввод-вывод, увеличить безопасность. +> Резервная копия сжимается (10–30% от исходного размера файлов СУБД) и шифруется. Это позволяет экономить место на сетевом диске, уменьшить нагрузку на ввод-вывод, увеличить безопасность. > [!CAUTION] > Внимание! From 8d947c5c728da12cf9a089a89061a029d359a705 Mon Sep 17 00:00:00 2001 From: Rinat Date: Wed, 3 Sep 2025 16:47:53 +0300 Subject: [PATCH 079/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index e5aa8fc..0c00dbb 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -9,7 +9,7 @@ ## Требования * GNU/Linux -* PostgreSQL ≥ v15 +* PostgreSQL ≥ v14 * Bash ≥ 4.4 ## Как это работает? From e1170b61c0652186c9eca76e1fa94404ab9dcf99 Mon Sep 17 00:00:00 2001 From: Rinat Date: Wed, 3 Sep 2025 21:58:23 +0300 Subject: [PATCH 080/145] new options --- pg_backup/pg_backup.conf | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pg_backup/pg_backup.conf b/pg_backup/pg_backup.conf index 1255a65..acd220e 100644 --- a/pg_backup/pg_backup.conf +++ b/pg_backup/pg_backup.conf @@ -4,6 +4,15 @@ PG_USERNAME="bkp_replicator" # путь к исполняемым файлам утилит СУБД PG_BIN_DIR="/usr/pgsql-16/bin" +# https://postgrespro.ru/docs/postgresql/16/libpq-pgpass +PG_PASS_FILE="/var/lib/pgsql/.pgpass" + +# https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-ARCHIVE-COMMAND +PG_ARCHIVE_COMMAND_FILE="/var/lib/pgsql/archive_command.sh" + +# https://postgrespro.ru/docs/postgresql/16/runtime-config-wal#GUC-RESTORE-COMMAND +PG_RESTORE_COMMAND_FILE="/var/lib/pgsql/restore_command.sh" + # папка для хранения файлов с резервными копиями # для корректной работы системы резервного копирования внутри папок /mnt/backup_db/{active_full,archive_wal}/ должна быть папка с названием БД # но в PostgreSQL можно сделать физическую резервную копию только для всего кластера, поэтому папка называется cluster From 0a47187cf8f5c54961d4015524f19fd1f657d487 Mon Sep 17 00:00:00 2001 From: Rinat Date: Wed, 3 Sep 2025 22:00:25 +0300 Subject: [PATCH 081/145] Update pg_backup.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Доработал для совместимости с PostgreSQL v14 * Ужесточил валидацию конфигурационных параметров * Контрольные суммы проверяются, только если они включены --- pg_backup/pg_backup.sh | 198 ++++++++++++++++++++++++----------------- 1 file changed, 116 insertions(+), 82 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index a54efd7..622d602 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -11,7 +11,7 @@ set -euo pipefail SCRIPT_FILE=$(readlink -f "0ドル") SCRIPT_DIR=$(dirname "$SCRIPT_FILE") -bash -n "${SCRIPT_FILE}" || exit # Check syntax this file +bash -n "$SCRIPT_FILE" || exit # Check syntax this file # Colors Red='\e[1;31m' @@ -26,10 +26,10 @@ White='\e[1;37m' Reset='\e[0m' # Colored messages -echoerr() { echo -e "${Red}$@${Reset}" 1>&2; } -echowarn() { echo -e "${Yellow}$@${Reset}" 1>&2; } -echoinfo() { echo -e "${White}$@${Reset}" ; } -echosucc() { echo -e "${Green}$@${Reset}" ; } +echoerr() { echo -e "${Red}$@${Reset}" 1>&2; } # ошибки +echowarn() { echo -e "${Yellow}$@${Reset}" 1>&2; } # предупреждения +echoinfo() { echo -e "${White}$@${Reset}" ; } # важные сообщения +echosucc() { echo -e "${Green}$@${Reset}" ; } # сообщения об успехе elapsed() { local time_start=1ドル #time_start=$(date +%s) @@ -49,16 +49,29 @@ ionice -c 2 -n 7 -p $$ renice -n 19 -p $$ source "$SCRIPT_DIR/pg_backup.conf" # include +TIME_START=$(date +%s) # время в Unixtime -PG_PASS_FILE="$SCRIPT_DIR/.pgpass" -if test $(whoami) != "postgres"; then - echoerr "pg_backup: run script as user postgres, not $(whoami)!" +# разные обязательные общие проверки при запуске скрипта +if test "$(whoami)" != "postgres"; then + echoerr "pg_backup: run script as user 'postgres', not '$(whoami)'" exit 1 elif ! grep -q -w "$PG_USERNAME" "$PG_PASS_FILE"; then echoerr "pg_backup: file '$PG_PASS_FILE' must contain record for user '$PG_USERNAME'" exit 1 elif test "$GPG_PASSPHRASE" = "*censored*"; then - echoerr "pg_backup: change value of \$GPG_PASSPHRASE in '$SCRIPT_DIR/pg_backup.conf'!" + echoerr "pg_backup: change default value of GPG_PASSPHRASE in '$SCRIPT_DIR/pg_backup.conf'" + exit 1 +elif test ! -d "$BACKUP_DIR"; then + echoerr "pg_backup: directory '$BACKUP_DIR' does not exist" + exit 1 +elif test ! -d "$WAL_DIR"; then + echoerr "pg_backup: directory '$WAL_DIR' does not exist" + exit 1 +elif test ! -x "$PG_ARCHIVE_COMMAND_FILE"; then + echoerr "pg_backup: file '$PG_ARCHIVE_COMMAND_FILE' does not exist or user has not execute access" + exit 1 +elif test ! -x "$PG_RESTORE_COMMAND_FILE"; then + echoerr "pg_backup: file '$PG_RESTORE_COMMAND_FILE' does not exist or user has not execute access" exit 1 fi @@ -96,10 +109,9 @@ if test "${1:-}" = "ExecCondition"; then echo "pg_backup: perform will be from '$MEMBER' [$HOST] ($ROLE)" test $(hostname) = "$MEMBER" exit + # восстанавливаем PostgreSQL из резервной копии elif test "${1:-}" = "restore"; then - TIME_START=$(date +%s) # время в Unixtime - # скрипт должен запускаться с тремя параметрами test "$#" -ne 3 && echoinfo "Usage: 0ドル restore SOURCE_BACKUP_FILE_OR_DIR TARGET_PG_DATA_DIR" && exit 2 @@ -109,23 +121,20 @@ elif test "${1:-}" = "restore"; then elif test -d "$BACKUP_FILE_OR_DIR"; then BACKUP_FILE=$(find $BACKUP_FILE_OR_DIR -maxdepth 1 -type f -name "base.tar.*" -printf "%p") test ! -f "$BACKUP_FILE" \ - && echoerr "pg_backup restore: source backup archive file '$BACKUP_FILE_OR_DIR/base.tar.*' does not exist!" && exit 1 + && echoerr "pg_backup restore: source backup archive file '$BACKUP_FILE_OR_DIR/base.tar.*' does not exist" && exit 1 else - echoerr "pg_backup restore: source backup archive file/directory '$BACKUP_FILE_OR_DIR' does not exist!" + echoerr "pg_backup restore: source backup archive file/directory '$BACKUP_FILE_OR_DIR' does not exist" exit 1 fi PG_DATA_DIR="3ドル" - test ! -d "$PG_DATA_DIR" && echoerr "pg_backup restore: target directory '$PG_DATA_DIR' does not exist!" && exit 1 + test ! -d "$PG_DATA_DIR" && echoerr "pg_backup restore: target directory '$PG_DATA_DIR' does not exist" && exit 1 # определяем архиватор по расширению файла - BACKUP_FILE_EXT=$(basename "$BACKUP_FILE" | grep -oP '\.\Ktar\..*$') + BACKUP_FILE_EXT=$(basename "$BACKUP_FILE" | grep -oP '\.\Ktar\..+$') ARCHIVE_TYPE=$(echo "$BACKUP_FILE_EXT" | cut -d. -f2) - if test "$ARCHIVE_TYPE" = "zst"; then - COMPRESS_PROGRAM="unzstd" - elif test "$ARCHIVE_TYPE" = "lz4"; then - COMPRESS_PROGRAM="unlz4" - else + COMPRESS_PROGRAM=$(echo "zst:unzstd,lz4:unlz4,gz:unpigz" | grep -oP "\b${ARCHIVE_TYPE}:\K[^,]+") + if test -z "$ARCHIVE_TYPE" || test -z "$COMPRESS_PROGRAM"; then echoerr "pg_backup validate: no compress program found" exit 1 fi @@ -145,7 +154,7 @@ elif test "${1:-}" = "restore"; then fi echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" - rm -f -r -v $PG_DATA_DIR/*.{signal,{backup,old}{,.*}} $PG_DATA_DIR/log/* + rm -f -r -v $PG_DATA_DIR/{*.{signal,{backup,old}{,.*}},log/*} TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) @@ -163,10 +172,9 @@ elif test "${1:-}" = "restore"; then fi echowarn "Донастройте postgresql.conf и запустите кластер СУБД!" exit 0 + # проверяем корректность и восстанавливаемость PostgreSQL из резервной копии elif test "${1:-}" = "validate"; then - TIME_START=$(date +%s) # время в Unixtime - echo "Получаем название предпоследнего или последнего файла с архивом резервной копии (сортировка по дате модификации)" BACKUP_FILE=$(find $BACKUP_DIR -maxdepth 2 -type f \( -name "*.pg_backup.tar.*" -o -path "*.pg_backup/base.tar.*" \) \ -printf "%T@ %p\n" | sort -n | tail -2 | head -1 | cut -d" " -f2) @@ -174,16 +182,14 @@ elif test "${1:-}" = "validate"; then echo "pg_backup validate: archive file '$BACKUP_FILE' selected" # определяем архиватор по расширению файла - BACKUP_FILE_EXT=$(basename "$BACKUP_FILE" | grep -oP '\.\Ktar\..*$') + BACKUP_FILE_EXT=$(basename "$BACKUP_FILE" | grep -oP '\.\Ktar\..+$') ARCHIVE_TYPE=$(echo "$BACKUP_FILE_EXT" | cut -d. -f2) - if test "$ARCHIVE_TYPE" = "zst"; then - COMPRESS_PROGRAM="unzstd" - elif test "$ARCHIVE_TYPE" = "lz4"; then - COMPRESS_PROGRAM="unlz4" - else + COMPRESS_PROGRAM=$(echo "zst:unzstd,lz4:unlz4,gz:unpigz" | grep -oP "\b${ARCHIVE_TYPE}:\K[^,]+") + if test -z "$ARCHIVE_TYPE" || test -z "$COMPRESS_PROGRAM"; then echoerr "pg_backup validate: no compress program found" exit 1 fi + LOG_FILE_PREFIX=$(dirname $BACKUP_FILE)/$(basename $BACKUP_FILE .$BACKUP_FILE_EXT) touch $LOG_FILE_PREFIX.validate-selected.log @@ -204,10 +210,7 @@ elif test "${1:-}" = "validate"; then | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR BACKUP_BASE_DIR=$(echo "$BACKUP_FILE" | grep -qP '\.pg_backup/base\.tar\.' && dirname "$BACKUP_FILE" || true) - if test -z "$BACKUP_BASE_DIR"; then - echo "Проверяем существование папки '$WAL_DIR', из неё могут быть прочитаны дополнительные WAL файлы" - test ! -d "$WAL_DIR" && echowarn "pg_backup validate: directory '$WAL_DIR' does not exist" - else + if test ! -z "$BACKUP_BASE_DIR"; then echo "Копируем '$BACKUP_BASE_DIR/backup_manifest' в папку '$PG_DATA_TEST_DIR'" cp $BACKUP_BASE_DIR/backup_manifest $PG_DATA_TEST_DIR @@ -228,7 +231,7 @@ elif test "${1:-}" = "validate"; then echo "pg_backup validate: '$PG_DATA_TEST_DIR' backup verify OK" echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" - rm -f -r -v $PG_DATA_TEST_DIR/*.{signal,{backup,old}{,.*}} $PG_DATA_TEST_DIR/log/* + rm -f -r -v $PG_DATA_TEST_DIR/{*.{signal,{backup,old}{,.*}},log/*} echo "Разрешаем локальному пользователю postgres аутентифицироваться методом peer" sed -i '1i local all postgres peer' $PG_DATA_TEST_DIR/pg_hba.conf # добавляем строчку в начало файла @@ -236,45 +239,59 @@ elif test "${1:-}" = "validate"; then echo "(Ре)стартуем сервер СУБД в роли мастер (рестарт - это защита от предыдущего неудачного запуска скрипта)" touch $PG_DATA_TEST_DIR/recovery.signal PG_PORT=55432 - $PG_BIN_DIR/pg_ctl restart --pgdata=$PG_DATA_TEST_DIR --silent \ + $PG_BIN_DIR/pg_ctl restart --pgdata=$PG_DATA_TEST_DIR \ --options="-p $PG_PORT -B 128MB --cluster_name=BACKUP_VALIDATE --archive_mode=off --log_directory=$PG_DATA_TEST_DIR/log" \ - --options="--hba_file=$PG_DATA_TEST_DIR/pg_hba.conf --ident-file=$PG_DATA_TEST_DIR/pg_ident.conf" - echo "pg_backup validate: server started (port $PG_PORT)" - - echo "Проверяем подключение к СУБД" - if ! psql --port=$PG_PORT --user=$PG_USERNAME --no-password --dbname=postgres --no-psqlrc --echo-errors --command='\conninfo' \ - 1> $LOG_FILE_PREFIX.psql.stdout.log \ - 2> $LOG_FILE_PREFIX.psql.stderr.log ; then - echowarn "pg_backup validate: server connection ERROR" + --options="--hba_file=$PG_DATA_TEST_DIR/pg_hba.conf --ident-file=$PG_DATA_TEST_DIR/pg_ident.conf" \ + --options="--restore_command='$PG_RESTORE_COMMAND_FILE %f %p'" \ + 1> $LOG_FILE_PREFIX.pg_ctl.stdout.log \ + 2> $LOG_FILE_PREFIX.pg_ctl.stderr.log + echoinfo "pg_backup validate: server started (port $PG_PORT)" + + # ВНИМАНИЕ! После старта тестовой СУБД завершать работу скрипта с ошибкой нельзя до остановки СУБД! + + echo "Проверяем количество ошибок в контрольных суммах" + CHECKSUM_FAILURES=$(psql --port=$PG_PORT --user=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc \ + --pset=null=¤ --tuples-only --no-align --command='select sum(checksum_failures) from pg_stat_database' \ + 2> $LOG_FILE_PREFIX.psql.stderr.log) || true + if test -z "$CHECKSUM_FAILURES"; then + echowarn "pg_backup validate: connection ERROR" + elif test "$CHECKSUM_FAILURES" = "¤"; then + echowarn "pg_backup validate: data checksums disabled" + elif test "$CHECKSUM_FAILURES" -gt 0; then + echowarn "pg_backup validate: data checksum failures: $CHECKSUM_FAILURES" else - echo "pg_backup validate: server connection OK" - - echo "Проверяем логическую целостность таблиц и индексов (amcheck)" - if ! $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* \ - --rootdescend --on-error-stop \ - 1> $LOG_FILE_PREFIX.pg_amcheck.stdout.log \ - 2> $LOG_FILE_PREFIX.pg_amcheck.stderr.log ; then - echowarn "pg_backup validate: amcheck ERROR" - else - echo "pg_backup validate: amcheck OK" - fi + echo "pg_backup validate: data checksum failures: 0" + fi + + echo "Проверяем логическую целостность таблиц и индексов (amcheck)" + if $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* \ + --rootdescend --on-error-stop \ + 1> $LOG_FILE_PREFIX.pg_amcheck.stdout.log \ + 2> $LOG_FILE_PREFIX.pg_amcheck.stderr.log ; then + echo "pg_backup validate: amcheck OK" + else + echowarn "pg_backup validate: amcheck ERROR" fi echo "Останавливаем сервер СУБД" - $PG_BIN_DIR/pg_ctl stop --pgdata=$PG_DATA_TEST_DIR --silent - echo "pg_backup validate: server stopped" + $PG_BIN_DIR/pg_ctl stop --pgdata=$PG_DATA_TEST_DIR \ + 1> $LOG_FILE_PREFIX.pg_ctl.stdout.log \ + 2> $LOG_FILE_PREFIX.pg_ctl.stderr.log + echoinfo "pg_backup validate: server stopped (port $PG_PORT)" for LOG_FILE in $PG_DATA_TEST_DIR/log/*; do echo "Проверяем отсутствие ошибок в файле $LOG_FILE" - grep -P '\b(WARNING|ERROR|FATAL|PANIC)\b' $LOG_FILE && exit 1 || true + grep -P '\b(WARNING|ERROR|FATAL|PANIC)\b' $LOG_FILE && echoerr "pg_backup validate: problems found" && exit 1 || true done echo "pg_backup validate: no problems found in log files" - echo "Проверяем контрольные суммы данных в кластере СУБД" - $PG_BIN_DIR/pg_checksums --check --pgdata=$PG_DATA_TEST_DIR \ - 1> $LOG_FILE_PREFIX.pg_checksums.stdout.log - 2> $LOG_FILE_PREFIX.pg_checksums.stderr.log - echo "pg_backup validate: '$PG_DATA_TEST_DIR' checksums OK" + if echo "$CHECKSUM_FAILURES" | grep -qP '^\d+$'; then + echo "Проверяем контрольные суммы данных в кластере СУБД" + $PG_BIN_DIR/pg_checksums --check --pgdata=$PG_DATA_TEST_DIR \ + 1> $LOG_FILE_PREFIX.pg_checksums.stdout.log \ + 2> $LOG_FILE_PREFIX.pg_checksums.stderr.log + echo "pg_backup validate: '$PG_DATA_TEST_DIR' checksums OK" + fi echo "Сохраняем управляющую информацию кластера СУБД" $PG_BIN_DIR/pg_controldata --pgdata=$PG_DATA_TEST_DIR \ @@ -292,6 +309,7 @@ elif test "${1:-}" = "validate"; then echo "Validate duration: $TIME_ELAPSED (day:hh:mm:ss)">> $LOG_FILE echosucc "pg_backup validate: success, duration: $TIME_ELAPSED (day:hh:mm:ss)" exit 0 + elif test -n "${1:-}"; then echoerr "pg_backup: unknown first parameter '${1:-}'" exit 2 @@ -299,10 +317,8 @@ fi # ----------------------------------------------------------------------------------------------------------------------- echoinfo "pg_backup: creating started" -TIME_START=$(date +%s) # время в Unixtime BASE_NAME=${BACKUP_DIR}/$(date +%Y-%m-%d.%H%M%S).$(hostname).pg_backup -ZSTD_THREADS=$(echo "$(nproc) / 2.5 + 1" | bc) -mkdir -p ${BACKUP_DIR} ${WAL_DIR} # создаём директории, если их ещё нет +COMPRESS_THREADS=$(echo "$(nproc) / 2.5 + 1" | bc) # Для многопоточного режима zstd используется степень сжатия 5, которая получена опытным путём. # Это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск с учётом его нагрузки другими процессами. @@ -316,16 +332,24 @@ IS_BACKUP_WAL=$(psql --user=$PG_USERNAME --no-password --dbname=postgres --quiet if test "$IS_BACKUP_WAL" = "f"; then echo 'Создаём физическую резервную копию (без WAL файлов)' + FILE="${BASE_NAME}.tar.zst.gpg" ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar --pgdata=- \ - | zstd -q -T${ZSTD_THREADS} -5 \ - | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o ${BASE_NAME}.tar.zst.gpg + | zstd -q -T${COMPRESS_THREADS} -5 \ + | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE + SIZE=$(du -sh "$FILE" | grep -oP '^\S+') + echoinfo "Создан файл '$FILE' (size: $SIZE)" else echo 'Создаём физическую резервную копию (с WAL файлами)' - # в библиотеке libzstd многопоточность поддерживается с версии 1.5.0 - LIBZSTD_VER=$(rpm -q libzstd | grep -oP '^libzstd-\K\d+\.\d+') - test -z "$LIBZSTD_VER" && echoerr "pg_backup: cannot get libzstd version, it is installed?" && exit 1 - OPT_COMPRESS="server-zstd:level=1" - test $(echo "$LIBZSTD_VER>= 1.5" | bc -l) = 1 && OPT_COMPRESS="server-zstd:level=5,workers=${ZSTD_THREADS}" + PG_MAJOR_VER=$(echo "$PG_BIN_DIR" | grep -oP '\-\K\d+(?=/)') + if test "$PG_MAJOR_VER" -ge 15; then + # в библиотеке libzstd многопоточность поддерживается с версии 1.5.0 + LIBZSTD_VER=$(rpm -q libzstd | grep -oP '^libzstd-\K\d+\.\d+') + test -z "$LIBZSTD_VER" && echoerr "pg_backup: cannot get libzstd version, it is installed?" && exit 1 + OPT_COMPRESS="server-zstd:level=1" + test $(echo "$LIBZSTD_VER>= 1.5" | bc -l) = 1 && OPT_COMPRESS="server-zstd:level=5,workers=${COMPRESS_THREADS}" + else + OPT_COMPRESS=1 # gzip support only + fi ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --compress=${OPT_COMPRESS} --checkpoint=fast --format=tar \ --pgdata=${BASE_NAME} @@ -333,20 +357,30 @@ else for FILE in $FILES; do if test -f "$FILE"; then echo "Сжимаем и шифруем '$FILE'" - zstd -c -q -T${ZSTD_THREADS} -5 $FILE | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.zst.gpg + if test "$PG_MAJOR_VER" -ge 15; then + zstd -c -q -T${COMPRESS_THREADS} -5 $FILE | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.zst.gpg + else + pigz -c -q -p ${COMPRESS_THREADS} -5 $FILE | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.gz.gpg + fi rm -f $FILE elif test -f "$FILE.zst"; then echo "Шифруем '$FILE.zst'" gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none $FILE.zst rm -f $FILE.zst + elif test -f "$FILE.gz"; then + echo "Шифруем '$FILE.gz'" + gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none $FILE.gz + rm -f $FILE.gz else - echoerr "Файл '$FILE' или '$FILE.zst' не найден" && exit 1 + echoerr "Файл '$FILE' или '$FILE.zst' или '$FILE.gz' не найден" && exit 1 fi done + SIZE=$(du -sh "$BASE_NAME" | grep -oP '^\S+') + echoinfo "Создана папка '$BASE_NAME' (total size: $SIZE)" fi # создаём логическую резервную копию (deprecated) -# ${PG_BIN_DIR}/pg_dumpall --username=${PG_USERNAME} --no-password | zstd -q -T${ZSTD_THREADS} -5 -o ${BASE_NAME}.sql.zst +# ${PG_BIN_DIR}/pg_dumpall --username=${PG_USERNAME} --no-password | zstd -q -T${COMPRESS_THREADS} -5 -o ${BASE_NAME}.sql.zst TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) @@ -364,16 +398,16 @@ WAL_OLD_FILE=$(find ${WAL_DIR} -maxdepth 1 -mtime +${BACKUP_AGE_DAYS} -type f ! ! -name "*.history" ! -name "*.history.*" -printf "%T@ %f\n" \ | sort -n | tail -1 | cut -d" " -f2) if test -z "${WAL_OLD_FILE}"; then - echo "pg_backup: WAL old file is not found" + echowarn "pg_backup: WAL old file is not found" else - echo "pg_backup: WAL old file is ${WAL_OLD_FILE}" - WAL_OLD_FILE_EXT=$(echo "${WAL_OLD_FILE}" | grep -oP '\.[a-z\d]+$') # compressed files support (.gz, .zst, .lz4) + echo "pg_backup: WAL old file is ${WAL_OLD_FILE}" + WAL_OLD_FILE_EXT=$(echo "${WAL_OLD_FILE}" | grep -oP '\.[a-z\d]+$') # compressed files support (.gz, .zst, .lz4) - BEFORE_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') - ${PG_BIN_DIR}/pg_archivecleanup -x "${WAL_OLD_FILE_EXT}" "${WAL_DIR}" "${WAL_OLD_FILE}" + BEFORE_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') + ${PG_BIN_DIR}/pg_archivecleanup -x "${WAL_OLD_FILE_EXT}" "${WAL_DIR}" "${WAL_OLD_FILE}" - AFTER_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') - echo "pg_backup: WAL dir size reducing: ${BEFORE_WAL_DIR_SIZE} (before cleanup) -> ${AFTER_WAL_DIR_SIZE} (after cleanup)" + AFTER_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') + echo "pg_backup: WAL dir size reducing: ${BEFORE_WAL_DIR_SIZE} (before cleanup) -> ${AFTER_WAL_DIR_SIZE} (after cleanup)" fi echosucc "pg_backup: done" From 6d9f6d868b380d35126c275ded43e357f93ed0dc Mon Sep 17 00:00:00 2001 From: Rinat Date: Thu, 4 Sep 2025 14:35:55 +0300 Subject: [PATCH 082/145] Update pg_backup.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Уровень сжатия теперь зависит от кол-ва CPU --- pg_backup/pg_backup.sh | 59 +++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index 622d602..93217cd 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -11,9 +11,9 @@ set -euo pipefail SCRIPT_FILE=$(readlink -f "0ドル") SCRIPT_DIR=$(dirname "$SCRIPT_FILE") -bash -n "$SCRIPT_FILE" || exit # Check syntax this file +bash -n "$SCRIPT_FILE" || exit # check syntax this file -# Colors +# colors Red='\e[1;31m' Green='\e[0;32m' Yellow='\e[38;5;220m' @@ -25,15 +25,16 @@ Gray='\e[0;37m' White='\e[1;37m' Reset='\e[0m' -# Colored messages +# colored messages echoerr() { echo -e "${Red}$@${Reset}" 1>&2; } # ошибки echowarn() { echo -e "${Yellow}$@${Reset}" 1>&2; } # предупреждения echoinfo() { echo -e "${White}$@${Reset}" ; } # важные сообщения echosucc() { echo -e "${Green}$@${Reset}" ; } # сообщения об успехе +# функция подсчитывает длительность (day:hh:mm:ss) между временными метками в Unixtime elapsed() { - local time_start=1ドル #time_start=$(date +%s) - local time_end=2ドル #time_end=$(date +%s) + local time_start=1ドル # time_start=$(date +%s) + local time_end=2ドル # time_end=$(date +%s) local dt=$(echo "$time_end - $time_start" | bc) local dd=$(echo "$dt/86400" | bc) local dt2=$(echo "$dt-86400*$dd" | bc) @@ -41,7 +42,7 @@ elapsed() { local dt3=$(echo "$dt2-3600*$dh" | bc) local dm=$(echo "$dt3/60" | bc) local ds=$(echo "$dt3-60*$dm" | bc) - printf '%dd:%02d:%02d:%02d' $dd $dh $dm $ds #day:hh:mm:ss + printf '%dd:%02d:%02d:%02d' $dd $dh $dm $ds } # меняем приоритет этого процесса ($$ - это его pid) на минимальный (дочерние процессы наследуют значение приоритета родительского процесса) @@ -79,7 +80,7 @@ fi if test "${1:-}" = "ExecCondition"; then if ! (command -v patronictl &> /dev/null && command -v jq &> /dev/null); then # test ! -f "$PGDATA/standby.signal" # deprecated - PG_ROLE=$(psql --user=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ + PG_ROLE=$(psql --username=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ --command="select case when pg_is_in_recovery() then 'standby' else 'primary' end") echo "pg_backup: candidate role is $PG_ROLE (checked by psql)" test ${2:='primary'} = "$PG_ROLE" @@ -111,6 +112,7 @@ if test "${1:-}" = "ExecCondition"; then exit # восстанавливаем PostgreSQL из резервной копии +# на экране будет отображаться прогресс работы в процентах, скорость работы в мегабайтах/секунду, текущая и оставшаяся длительность работы elif test "${1:-}" = "restore"; then # скрипт должен запускаться с тремя параметрами test "$#" -ne 3 && echoinfo "Usage: 0ドル restore SOURCE_BACKUP_FILE_OR_DIR TARGET_PG_DATA_DIR" && exit 2 @@ -140,20 +142,21 @@ elif test "${1:-}" = "restore"; then fi echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_DIR'" - pv -treb $BACKUP_FILE \ + pv -trebp $BACKUP_FILE \ | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_DIR if test -d "$BACKUP_FILE_OR_DIR"; then - FILE="$BACKUP_FILE_OR_DIR/pg_wal.$BACKUP_FILE_EXT" - test ! -f "$FILE" && echoerr "Файл '$FILE' не найден" && exit 1 - echo "Расшифровываем и распаковываем архив '$FILE' в папку '$PG_DATA_DIR/pg_wal'" - pv -treb $FILE \ + WAL_FILE="$BACKUP_FILE_OR_DIR/pg_wal.$BACKUP_FILE_EXT" + test ! -f "$WAL_FILE" && echoerr "Файл '$WAL_FILE' не найден" && exit 1 + echo "Расшифровываем и распаковываем архив '$WAL_FILE' в папку '$PG_DATA_DIR/pg_wal'" + pv -trebp $WAL_FILE \ | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_DIR/pg_wal fi echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" + # https://www.google.com/search?q=Linux+curly+brace+expansion+documentation rm -f -r -v $PG_DATA_DIR/{*.{signal,{backup,old}{,.*}},log/*} TIME_END=$(date +%s) # время в Unixtime @@ -231,6 +234,7 @@ elif test "${1:-}" = "validate"; then echo "pg_backup validate: '$PG_DATA_TEST_DIR' backup verify OK" echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" + # https://www.google.com/search?q=Linux+curly+brace+expansion+documentation rm -f -r -v $PG_DATA_TEST_DIR/{*.{signal,{backup,old}{,.*}},log/*} echo "Разрешаем локальному пользователю postgres аутентифицироваться методом peer" @@ -250,7 +254,7 @@ elif test "${1:-}" = "validate"; then # ВНИМАНИЕ! После старта тестовой СУБД завершать работу скрипта с ошибкой нельзя до остановки СУБД! echo "Проверяем количество ошибок в контрольных суммах" - CHECKSUM_FAILURES=$(psql --port=$PG_PORT --user=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc \ + CHECKSUM_FAILURES=$(psql --port=$PG_PORT --username=postgres --no-password --dbname=postgres --quiet --no-psqlrc \ --pset=null=¤ --tuples-only --no-align --command='select sum(checksum_failures) from pg_stat_database' \ 2> $LOG_FILE_PREFIX.psql.stderr.log) || true if test -z "$CHECKSUM_FAILURES"; then @@ -264,10 +268,9 @@ elif test "${1:-}" = "validate"; then fi echo "Проверяем логическую целостность таблиц и индексов (amcheck)" - if $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* \ - --rootdescend --on-error-stop \ - 1> $LOG_FILE_PREFIX.pg_amcheck.stdout.log \ - 2> $LOG_FILE_PREFIX.pg_amcheck.stderr.log ; then + if $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* --rootdescend --on-error-stop \ + 1> $LOG_FILE_PREFIX.pg_amcheck.stdout.log \ + 2> $LOG_FILE_PREFIX.pg_amcheck.stderr.log ; then echo "pg_backup validate: amcheck OK" else echowarn "pg_backup validate: amcheck ERROR" @@ -305,7 +308,7 @@ elif test "${1:-}" = "validate"; then TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) LOG_FILE=$LOG_FILE_PREFIX.validate-success.log - echo "Total size: $DIR_SIZE">> $LOG_FILE + echo "Total size: $DIR_SIZE"> $LOG_FILE echo "Validate duration: $TIME_ELAPSED (day:hh:mm:ss)">> $LOG_FILE echosucc "pg_backup validate: success, duration: $TIME_ELAPSED (day:hh:mm:ss)" exit 0 @@ -320,12 +323,14 @@ echoinfo "pg_backup: creating started" BASE_NAME=${BACKUP_DIR}/$(date +%Y-%m-%d.%H%M%S).$(hostname).pg_backup COMPRESS_THREADS=$(echo "$(nproc) / 2.5 + 1" | bc) -# Для многопоточного режима zstd используется степень сжатия 5, которая получена опытным путём. -# Это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск с учётом его нагрузки другими процессами. +# для многопоточного режима используется максимальная степень сжатия 5, которая получена опытным путём +# это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск, с учётом нагрузки другими процессами +COMPRESS_LEVEL=$COMPRESS_THREADS +test "$COMPRESS_LEVEL" -gt 5 && $COMPRESS_LEVEL=5 echo 'Проверяем необходимость бекапирования WAL файлов' # зависит от текущего дня, настройки параметра archive_mode и роли СУБД primary/standby -IS_BACKUP_WAL=$(psql --user=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ +IS_BACKUP_WAL=$(psql --username=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ --command="select extract(doy from now())%${BACKUP_WAL_DOY_DIVIDER}=0 or setting='off' or (pg_is_in_recovery() and setting='on') from pg_settings where name='archive_mode'") @@ -334,7 +339,7 @@ if test "$IS_BACKUP_WAL" = "f"; then echo 'Создаём физическую резервную копию (без WAL файлов)' FILE="${BASE_NAME}.tar.zst.gpg" ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar --pgdata=- \ - | zstd -q -T${COMPRESS_THREADS} -5 \ + | zstd -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} \ | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE SIZE=$(du -sh "$FILE" | grep -oP '^\S+') echoinfo "Создан файл '$FILE' (size: $SIZE)" @@ -346,7 +351,7 @@ else LIBZSTD_VER=$(rpm -q libzstd | grep -oP '^libzstd-\K\d+\.\d+') test -z "$LIBZSTD_VER" && echoerr "pg_backup: cannot get libzstd version, it is installed?" && exit 1 OPT_COMPRESS="server-zstd:level=1" - test $(echo "$LIBZSTD_VER>= 1.5" | bc -l) = 1 && OPT_COMPRESS="server-zstd:level=5,workers=${COMPRESS_THREADS}" + test $(echo "$LIBZSTD_VER>= 1.5" | bc -l) = 1 && OPT_COMPRESS="server-zstd:level=${COMPRESS_LEVEL},workers=${COMPRESS_THREADS}" else OPT_COMPRESS=1 # gzip support only fi @@ -358,9 +363,11 @@ else if test -f "$FILE"; then echo "Сжимаем и шифруем '$FILE'" if test "$PG_MAJOR_VER" -ge 15; then - zstd -c -q -T${COMPRESS_THREADS} -5 $FILE | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.zst.gpg + zstd -c -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE \ + | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.zst.gpg else - pigz -c -q -p ${COMPRESS_THREADS} -5 $FILE | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.gz.gpg + pigz -c -q -p ${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE \ + | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.gz.gpg fi rm -f $FILE elif test -f "$FILE.zst"; then @@ -380,7 +387,7 @@ else fi # создаём логическую резервную копию (deprecated) -# ${PG_BIN_DIR}/pg_dumpall --username=${PG_USERNAME} --no-password | zstd -q -T${COMPRESS_THREADS} -5 -o ${BASE_NAME}.sql.zst +# ${PG_BIN_DIR}/pg_dumpall --username=${PG_USERNAME} --no-password | zstd -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} -o ${BASE_NAME}.sql.zst TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) From 40486f6c6ad16e743d3e74ac77612c683ff5258b Mon Sep 17 00:00:00 2001 From: Rinat Date: Thu, 4 Sep 2025 16:55:11 +0300 Subject: [PATCH 083/145] Update pg_backup_validate.timer --- pg_backup/pg_backup_validate.timer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/pg_backup_validate.timer b/pg_backup/pg_backup_validate.timer index 1901bda..44fcf42 100644 --- a/pg_backup/pg_backup_validate.timer +++ b/pg_backup/pg_backup_validate.timer @@ -4,7 +4,7 @@ Description=PostgreSQL backup validate timer [Timer] Unit=pg_backup_validate.service OnCalendar=Wed *-*-* 03:00:00 -RandomizedDelaySec=7200 +RandomizedDelaySec=10800 Persistent=true [Install] From 0e7ef2eba02ddb250cc3dbdf2ba36aa805cb13ca Mon Sep 17 00:00:00 2001 From: Rinat Date: Thu, 4 Sep 2025 16:56:44 +0300 Subject: [PATCH 084/145] Update README.md --- pg_backup/README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index 0c00dbb..95acc09 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -68,20 +68,26 @@ cd ~postgres # создаём файлы (1) nano -c .pgpass # в файле нужно сохранить пароль для пользователя bkp_replicator -(cp --update $HOME_DIR/pg_install/pg_backup.sh . || nano -c pg_backup.sh) && \ -(cp --update $HOME_DIR/pg_install/pg_backup.conf . || nano -c pg_backup.conf) && \ -(cp --update $HOME_DIR/pg_install/archive_command.sh . || nano -c archive_command.sh) && \ -(cp --update $HOME_DIR/pg_install/restore_command.sh . || nano -c restore_command.sh) +(cp --update --backup $HOME_DIR/pg_install/pg_backup.sh . || nano -c pg_backup.sh) && \ +(cp --update --backup $HOME_DIR/pg_install/pg_backup.conf . || nano -c pg_backup.conf) && \ +(cp --update --backup $HOME_DIR/pg_install/archive_command.sh . || nano -c archive_command.sh) && \ +(cp --update --backup $HOME_DIR/pg_install/restore_command.sh . || nano -c restore_command.sh) # выставляем нужные права и владельца chmod 600 .pgpass pg_backup.conf && \ chmod 700 {pg_backup,{archive,restore}_command}.sh && \ chown postgres:postgres .pgpass {pg_backup,{archive,restore}_command}.sh pg_backup.conf +# проверяем работоспособность (отладка), выводим сообщения на экран +sudo -i -u postgres -- ./pg_backup.sh ExecCondition # будем ли создавать или проверять резервную копию с текущего сервера СУБД (см. код возврата)? +sudo -i -u postgres -- ./pg_backup.sh # создаст резервную копию текущего сервера СУБД +sudo -i -u postgres -- ./pg_backup.sh validate # проверит корректность и восстанавливаемость резервной копии СУБД +sudo -i -u postgres -- ./pg_backup.sh restore SOURCE_BACKUP_FILE_OR_DIR TARGET_PG_DATA_DIR # восстановит резервную копию СУБД + # создаём файлы (2) -(cp --update $HOME_DIR/pg_install/pg_backup.timer /etc/systemd/system || nano -c /etc/systemd/system/pg_backup.timer) && \ -(cp --update $HOME_DIR/pg_install/pg_backup.service /etc/systemd/system || nano -c /etc/systemd/system/pg_backup.service) && \ -(cp --update $HOME_DIR/pg_install/pg_backup_validate.timer /etc/systemd/system || nano -c /etc/systemd/system/pg_backup_validate.timer) && \ -(cp --update $HOME_DIR/pg_install/pg_backup_validate.service /etc/systemd/system || nano -c /etc/systemd/system/pg_backup_validate.service) && \ +(cp --update --backup $HOME_DIR/pg_install/pg_backup.timer /etc/systemd/system || nano -c /etc/systemd/system/pg_backup.timer) && \ +(cp --update --backup $HOME_DIR/pg_install/pg_backup.service /etc/systemd/system || nano -c /etc/systemd/system/pg_backup.service) && \ +(cp --update --backup $HOME_DIR/pg_install/pg_backup_validate.timer /etc/systemd/system || nano -c /etc/systemd/system/pg_backup_validate.timer) && \ +(cp --update --backup $HOME_DIR/pg_install/pg_backup_validate.service /etc/systemd/system || nano -c /etc/systemd/system/pg_backup_validate.service) && \ systemctl daemon-reload # активируем # добавляем в автозагрузку @@ -90,12 +96,6 @@ systemctl enable pg_backup.service && \ systemctl enable pg_backup_validate.timer && \ systemctl enable pg_backup_validate.service -# проверяем работоспособность (отладка), выводим сообщения на экран -sudo -i -u postgres -- ./pg_backup.sh ExecCondition # будем ли создавать или проверять резервную копию с текущего сервера СУБД (см. код возврата)? -sudo -i -u postgres -- ./pg_backup.sh # создаст резервную копию текущего сервера СУБД -sudo -i -u postgres -- ./pg_backup.sh validate # проверит корректность и восстанавливаемость резервной копии СУБД -sudo -i -u postgres -- ./pg_backup.sh restore SOURCE_BACKUP_FILE_OR_DIR TARGET_PG_DATA_DIR # восстановит резервную копию СУБД - # запускаем; сделает резервную копию СУБД, если условие ExecCondition выполнится (НЕ выведет сообщения на экран) systemctl start pg_backup.timer && \ systemctl start pg_backup.service && \ From 1e7abbd1e138a70da93dbddd919fb8a4e0e0241f Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 9 Sep 2025 00:06:55 +0300 Subject: [PATCH 085/145] PG_AMCHECK_VALIDATE, GPG_ENCRYPT added --- pg_backup/pg_backup.conf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pg_backup/pg_backup.conf b/pg_backup/pg_backup.conf index acd220e..89f8c97 100644 --- a/pg_backup/pg_backup.conf +++ b/pg_backup/pg_backup.conf @@ -25,6 +25,12 @@ WAL_DIR="/mnt/backup_db/archive_wal/cluster" # текущий день с начала года должен делиться на BACKUP_WAL_DOY_DIVIDER без остатка (1 - ежедневно, 2 - каждый второй день и т.д.) BACKUP_WAL_DOY_DIVIDER=5 +# при валидации СУБД запускать программу pg_amcheck (1 - да, 0 - нет) +PG_AMCHECK_VALIDATE=1 + +# шифровать файлы (1 - да, 0 - нет) +GPG_ENCRYPT=1 + # пароль для шифрования/дешифрования файлов резервных копий GPG_PASSPHRASE="*censored*" From 3be29eb425ed437befc95874b3fc8a155ffab4a3 Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 9 Sep 2025 00:09:51 +0300 Subject: [PATCH 086/145] Update pg_backup.sh PG_AMCHECK_VALIDATE, GPG_ENCODE support extended error log to files --- pg_backup/pg_backup.sh | 165 ++++++++++++++++++++++++++++------------- 1 file changed, 113 insertions(+), 52 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index 93217cd..acd0111 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -28,8 +28,9 @@ Reset='\e[0m' # colored messages echoerr() { echo -e "${Red}$@${Reset}" 1>&2; } # ошибки echowarn() { echo -e "${Yellow}$@${Reset}" 1>&2; } # предупреждения -echoinfo() { echo -e "${White}$@${Reset}" ; } # важные сообщения -echosucc() { echo -e "${Green}$@${Reset}" ; } # сообщения об успехе +echohead() { echo -e "${Blue}$@${Reset}" ; } # заголовок или этап +echoinfo() { echo -e "${White}$@${Reset}" ; } # важные сообщения +echosucc() { echo -e "${Green}$@${Reset}" ; } # сообщения об успехе # функция подсчитывает длительность (day:hh:mm:ss) между временными метками в Unixtime elapsed() { @@ -59,8 +60,14 @@ if test "$(whoami)" != "postgres"; then elif ! grep -q -w "$PG_USERNAME" "$PG_PASS_FILE"; then echoerr "pg_backup: file '$PG_PASS_FILE' must contain record for user '$PG_USERNAME'" exit 1 -elif test "$GPG_PASSPHRASE" = "*censored*"; then - echoerr "pg_backup: change default value of GPG_PASSPHRASE in '$SCRIPT_DIR/pg_backup.conf'" +elif ! (echo "$PG_AMCHECK_VALIDATE" | grep -qoP '^[01]$'); then + echoerr "pg_backup: '$SCRIPT_DIR/pg_backup.conf': incorrect value of PG_AMCHECK_VALIDATE, expected 0 or 1" + exit 1 +elif ! (echo "$GPG_ENCRYPT" | grep -qoP '^[01]$'); then + echoerr "pg_backup: '$SCRIPT_DIR/pg_backup.conf': incorrect value of GPG_PASSPHRASE, expected 0 or 1" + exit 1 +elif test "$GPG_ENCRYPT" = 1 && test "$GPG_PASSPHRASE" = "*censored*"; then + echoerr "pg_backup: '$SCRIPT_DIR/pg_backup.conf': change default value of GPG_PASSPHRASE" exit 1 elif test ! -d "$BACKUP_DIR"; then echoerr "pg_backup: directory '$BACKUP_DIR' does not exist" @@ -121,7 +128,7 @@ elif test "${1:-}" = "restore"; then if test -f "$BACKUP_FILE_OR_DIR"; then BACKUP_FILE="$BACKUP_FILE_OR_DIR" elif test -d "$BACKUP_FILE_OR_DIR"; then - BACKUP_FILE=$(find $BACKUP_FILE_OR_DIR -maxdepth 1 -type f -name "base.tar.*" -printf "%p") + BACKUP_FILE=$(find $BACKUP_FILE_OR_DIR -maxdepth 1 -type f -name "base.tar.*" ! -name "*.log" -printf "%p") test ! -f "$BACKUP_FILE" \ && echoerr "pg_backup restore: source backup archive file '$BACKUP_FILE_OR_DIR/base.tar.*' does not exist" && exit 1 else @@ -141,23 +148,34 @@ elif test "${1:-}" = "restore"; then exit 1 fi - echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_DIR'" + if test "$GPG_ENCRYPT" = 0; then + echo "Распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_DIR'" + GPG_COMMAND="cat" + else + echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_DIR'" + GPG_COMMAND="gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch" + fi + # посмотреть прогресс выполнения процесса pv: sudo pv -d PID pv -trebp $BACKUP_FILE \ - | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch \ + | $GPG_COMMAND \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_DIR if test -d "$BACKUP_FILE_OR_DIR"; then WAL_FILE="$BACKUP_FILE_OR_DIR/pg_wal.$BACKUP_FILE_EXT" test ! -f "$WAL_FILE" && echoerr "Файл '$WAL_FILE' не найден" && exit 1 - echo "Расшифровываем и распаковываем архив '$WAL_FILE' в папку '$PG_DATA_DIR/pg_wal'" + if test "$GPG_ENCRYPT" = 0; then + echo "Распаковываем архив '$WAL_FILE' в папку '$PG_DATA_DIR/pg_wal'" + else + echo "Расшифровываем и распаковываем архив '$WAL_FILE' в папку '$PG_DATA_DIR/pg_wal'" + fi pv -trebp $WAL_FILE \ - | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch \ + | $GPG_COMMAND \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_DIR/pg_wal fi echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" # https://www.google.com/search?q=Linux+curly+brace+expansion+documentation - rm -f -r -v $PG_DATA_DIR/{*.{signal,{backup,old}{,.*}},log/*} + rm -f -r -v $PG_DATA_DIR/{*.{signal,{backup,old}{,.*}},log/*,*~} TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) @@ -180,7 +198,7 @@ elif test "${1:-}" = "restore"; then elif test "${1:-}" = "validate"; then echo "Получаем название предпоследнего или последнего файла с архивом резервной копии (сортировка по дате модификации)" BACKUP_FILE=$(find $BACKUP_DIR -maxdepth 2 -type f \( -name "*.pg_backup.tar.*" -o -path "*.pg_backup/base.tar.*" \) \ - -printf "%T@ %p\n" | sort -n | tail -2 | head -1 | cut -d" " -f2) + ! -name "*.log" -printf "%T@ %p\n" | sort -n | tail -2 | head -1 | cut -d" " -f2) test -z "$BACKUP_FILE" && echoerr "pg_backup validate: no backup archive file found in directory '$BACKUP_DIR'" && exit 1 echo "pg_backup validate: archive file '$BACKUP_FILE' selected" @@ -208,20 +226,36 @@ elif test "${1:-}" = "validate"; then stat -c "%a" $PG_DATA_TEST_DIR | grep -qP '^7[05]0$' \ || (echoerr "pg_backup validate: directory '$PG_DATA_TEST_DIR' permission must be 750 or 700" && exit 1) - echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_TEST_DIR'" - gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $BACKUP_FILE \ - | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR + if test "$GPG_ENCRYPT" = 0; then + echo "Распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_TEST_DIR'" + tar -xf $BACKUP_FILE --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR \ + 2> $LOG_FILE_PREFIX.tar.stderr.log + else + echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_TEST_DIR'" + gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $BACKUP_FILE \ + 2> $LOG_FILE_PREFIX.gpg.stderr.log \ + | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR \ + 2> $LOG_FILE_PREFIX.tar.stderr.log + fi BACKUP_BASE_DIR=$(echo "$BACKUP_FILE" | grep -qP '\.pg_backup/base\.tar\.' && dirname "$BACKUP_FILE" || true) if test ! -z "$BACKUP_BASE_DIR"; then echo "Копируем '$BACKUP_BASE_DIR/backup_manifest' в папку '$PG_DATA_TEST_DIR'" cp $BACKUP_BASE_DIR/backup_manifest $PG_DATA_TEST_DIR - FILE="$BACKUP_BASE_DIR/pg_wal.$BACKUP_FILE_EXT" - test ! -f "$FILE" && echoerr "Файл '$FILE' не найден" && exit 1 - echo "Расшифровываем и распаковываем архив '$FILE' в папку '$PG_DATA_TEST_DIR/pg_wal'" - gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $FILE \ - | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR/pg_wal + WAL_FILE="$BACKUP_BASE_DIR/pg_wal.$BACKUP_FILE_EXT" + test ! -f "$WAL_FILE" && echoerr "Файл '$WAL_FILE' не найден" && exit 1 + if test "$GPG_ENCRYPT" = 0; then + echo "Распаковываем архив '$WAL_FILE' в папку '$PG_DATA_TEST_DIR/pg_wal'" + tar -xf $WAL_FILE --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR/pg_wal \ + 2> $LOG_FILE_PREFIX.tar.stderr.log + else + echo "Расшифровываем и распаковываем архив '$WAL_FILE' в папку '$PG_DATA_TEST_DIR/pg_wal'" + gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $WAL_FILE \ + 2> $LOG_FILE_PREFIX.gpg.stderr.log \ + | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR/pg_wal \ + 2> $LOG_FILE_PREFIX.tar.stderr.log + fi fi DIR_SIZE=$(du -sh "$PG_DATA_TEST_DIR" | grep -oP '^\S+') @@ -235,7 +269,7 @@ elif test "${1:-}" = "validate"; then echo "Удаляем старые и ненужные файлы (информация об удалённых файлах будет выведена)" # https://www.google.com/search?q=Linux+curly+brace+expansion+documentation - rm -f -r -v $PG_DATA_TEST_DIR/{*.{signal,{backup,old}{,.*}},log/*} + rm -f -r -v $PG_DATA_TEST_DIR/{*.{signal,{backup,old}{,.*}},log/*,*~} echo "Разрешаем локальному пользователю postgres аутентифицироваться методом peer" sed -i '1i local all postgres peer' $PG_DATA_TEST_DIR/pg_hba.conf # добавляем строчку в начало файла @@ -267,13 +301,17 @@ elif test "${1:-}" = "validate"; then echo "pg_backup validate: data checksum failures: 0" fi - echo "Проверяем логическую целостность таблиц и индексов (amcheck)" - if $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* --rootdescend --on-error-stop \ - 1> $LOG_FILE_PREFIX.pg_amcheck.stdout.log \ - 2> $LOG_FILE_PREFIX.pg_amcheck.stderr.log ; then - echo "pg_backup validate: amcheck OK" + if test "$PG_AMCHECK_VALIDATE" = 0; then + echowarn "Проверка логической целостности таблиц и индексов (amcheck) отключена" else - echowarn "pg_backup validate: amcheck ERROR" + echo "Проверяем логическую целостность таблиц и индексов (amcheck)" + if $PG_BIN_DIR/pg_amcheck --port=$PG_PORT --username=postgres --no-password --database=* --rootdescend --on-error-stop \ + 1> $LOG_FILE_PREFIX.pg_amcheck.stdout.log \ + 2> $LOG_FILE_PREFIX.pg_amcheck.stderr.log ; then + echo "pg_backup validate: amcheck OK" + else + echowarn "pg_backup validate: amcheck ERROR" + fi fi echo "Останавливаем сервер СУБД" @@ -313,8 +351,9 @@ elif test "${1:-}" = "validate"; then echosucc "pg_backup validate: success, duration: $TIME_ELAPSED (day:hh:mm:ss)" exit 0 -elif test -n "${1:-}"; then - echoerr "pg_backup: unknown first parameter '${1:-}'" +elif test "${1:-}" != "create"; then + echoinfo "Usage: 0ドル COMMAND" + echo "COMMAND - one of: create, validate, restore" exit 2 fi @@ -323,7 +362,7 @@ echoinfo "pg_backup: creating started" BASE_NAME=${BACKUP_DIR}/$(date +%Y-%m-%d.%H%M%S).$(hostname).pg_backup COMPRESS_THREADS=$(echo "$(nproc) / 2.5 + 1" | bc) -# для многопоточного режима используется максимальная степень сжатия 5, которая получена опытным путём +# для многопоточного режима используется максимальная степень сжатия 5, которая была получена опытным путём # это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск, с учётом нагрузки другими процессами COMPRESS_LEVEL=$COMPRESS_THREADS test "$COMPRESS_LEVEL" -gt 5 && $COMPRESS_LEVEL=5 @@ -337,47 +376,66 @@ IS_BACKUP_WAL=$(psql --username=$PG_USERNAME --no-password --dbname=postgres --q if test "$IS_BACKUP_WAL" = "f"; then echo 'Создаём физическую резервную копию (без WAL файлов)' - FILE="${BASE_NAME}.tar.zst.gpg" - ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar --pgdata=- \ - | zstd -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} \ - | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE + COMMAND="${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --wal-method=none --checkpoint=fast --format=tar --pgdata=-" + if test "$GPG_ENCRYPT" = 0; then + FILE="${BASE_NAME}.tar.zst" + ($COMMAND | zstd -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} -o $FILE) 2> $BASE_NAME.stderr.log + else + FILE="${BASE_NAME}.tar.zst.gpg" + ($COMMAND | zstd -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} \ + | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE) 2> $BASE_NAME.stderr.log + fi SIZE=$(du -sh "$FILE" | grep -oP '^\S+') echoinfo "Создан файл '$FILE' (size: $SIZE)" else echo 'Создаём физическую резервную копию (с WAL файлами)' PG_MAJOR_VER=$(echo "$PG_BIN_DIR" | grep -oP '\-\K\d+(?=/)') + OPT_COMPRESS=1 # gzip support only if test "$PG_MAJOR_VER" -ge 15; then # в библиотеке libzstd многопоточность поддерживается с версии 1.5.0 LIBZSTD_VER=$(rpm -q libzstd | grep -oP '^libzstd-\K\d+\.\d+') test -z "$LIBZSTD_VER" && echoerr "pg_backup: cannot get libzstd version, it is installed?" && exit 1 OPT_COMPRESS="server-zstd:level=1" test $(echo "$LIBZSTD_VER>= 1.5" | bc -l) = 1 && OPT_COMPRESS="server-zstd:level=${COMPRESS_LEVEL},workers=${COMPRESS_THREADS}" - else - OPT_COMPRESS=1 # gzip support only fi + mkdir -p ${BASE_NAME} ${PG_BIN_DIR}/pg_basebackup --username=${PG_USERNAME} --no-password --compress=${OPT_COMPRESS} --checkpoint=fast --format=tar \ - --pgdata=${BASE_NAME} + --pgdata=${BASE_NAME} \ + 2> $BASE_NAME.stderr.log FILES="${BASE_NAME}/base.tar ${BASE_NAME}/pg_wal.tar" for FILE in $FILES; do if test -f "$FILE"; then - echo "Сжимаем и шифруем '$FILE'" - if test "$PG_MAJOR_VER" -ge 15; then - zstd -c -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE \ - | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.zst.gpg + if test "$GPG_ENCRYPT" = 0; then + echo "Сжимаем '$FILE'" + if test "$PG_MAJOR_VER" -ge 15; then + zstd -c -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE -o $FILE.zst 2> $BASE_NAME.stderr.log + else + pigz -c -q -p ${COMPRESS_THREADS} -${COMPRESS_LEVEL} -o $FILE.gz 2> $BASE_NAME.stderr.log + fi else - pigz -c -q -p ${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE \ - | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.gz.gpg + echo "Сжимаем и шифруем '$FILE'" + if test "$PG_MAJOR_VER" -ge 15; then + (zstd -c -q -T${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE \ + | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.zst.gpg) 2> $BASE_NAME.stderr.log + else + (pigz -c -q -p ${COMPRESS_THREADS} -${COMPRESS_LEVEL} $FILE \ + | gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none -o $FILE.gz.gpg) 2> $BASE_NAME.stderr.log + fi fi rm -f $FILE elif test -f "$FILE.zst"; then - echo "Шифруем '$FILE.zst'" - gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none $FILE.zst - rm -f $FILE.zst + if test "$GPG_ENCRYPT" = 1; then + echo "Шифруем '$FILE.zst'" + gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none $FILE.zst 2> $BASE_NAME.stderr.log + rm -f $FILE.zst + fi elif test -f "$FILE.gz"; then - echo "Шифруем '$FILE.gz'" - gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none $FILE.gz - rm -f $FILE.gz + if test "$GPG_ENCRYPT" = 1; then + echo "Шифруем '$FILE.gz'" + gpg -c --passphrase=${GPG_PASSPHRASE} --batch --compress-algo=none $FILE.gz 2> $BASE_NAME.stderr.log + rm -f $FILE.gz + fi else echoerr "Файл '$FILE' или '$FILE.zst' или '$FILE.gz' не найден" && exit 1 fi @@ -392,29 +450,32 @@ fi TIME_END=$(date +%s) # время в Unixtime TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) -echosucc "pg_backup: created successfully, duration: $TIME_ELAPSED (day:hh:mm:ss)" +echosucc "pg_backup: created, duration: $TIME_ELAPSED (day:hh:mm:ss)" # ----------------------------------------------------------------------------------------------------------------------- # удаляем архивные резервные копии старше N дней (папки и файлы рекурсивно) echo "pg_backup: deleting backup files older than ${BACKUP_AGE_DAYS} days" find ${BACKUP_DIR} -mindepth 1 -mtime +${BACKUP_AGE_DAYS} -delete +echosucc "pg_backup: old backup files deleted" +# ----------------------------------------------------------------------------------------------------------------------- # удаляем архивные WAL файлы старше N дней (сортировка по дате модификации) echo "pg_backup: detect oldest kept WAL file for ${BACKUP_AGE_DAYS} days" WAL_OLD_FILE=$(find ${WAL_DIR} -maxdepth 1 -mtime +${BACKUP_AGE_DAYS} -type f ! -size 0 \ ! -name "*.history" ! -name "*.history.*" -printf "%T@ %f\n" \ | sort -n | tail -1 | cut -d" " -f2) if test -z "${WAL_OLD_FILE}"; then - echowarn "pg_backup: WAL old file is not found" + echowarn "pg_backup: old WAL file is not found" else echo "pg_backup: WAL old file is ${WAL_OLD_FILE}" WAL_OLD_FILE_EXT=$(echo "${WAL_OLD_FILE}" | grep -oP '\.[a-z\d]+$') # compressed files support (.gz, .zst, .lz4) BEFORE_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') - ${PG_BIN_DIR}/pg_archivecleanup -x "${WAL_OLD_FILE_EXT}" "${WAL_DIR}" "${WAL_OLD_FILE}" + ${PG_BIN_DIR}/pg_archivecleanup -x "${WAL_OLD_FILE_EXT}" "${WAL_DIR}" "${WAL_OLD_FILE}" 2> ${WAL_DIR}/pg_archivecleanup.stderr.log AFTER_WAL_DIR_SIZE=$(du -sh "${WAL_DIR}" | grep -oP '^\S+') echo "pg_backup: WAL dir size reducing: ${BEFORE_WAL_DIR_SIZE} (before cleanup) -> ${AFTER_WAL_DIR_SIZE} (after cleanup)" + echosucc "pg_backup: old WAL files deleted" fi -echosucc "pg_backup: done" +exit 0 From a8add372cd5cec87bc260043fc4237deaec4d177 Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 9 Sep 2025 00:10:41 +0300 Subject: [PATCH 087/145] Create pg_backup_test.sh --- pg_backup/pg_backup_test.sh | 101 ++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 pg_backup/pg_backup_test.sh diff --git a/pg_backup/pg_backup_test.sh b/pg_backup/pg_backup_test.sh new file mode 100644 index 0000000..b19de59 --- /dev/null +++ b/pg_backup/pg_backup_test.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# https://habr.com/ru/company/ruvds/blog/325522/ - Bash documentation + +# https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html +# set -e - прекращает выполнение скрипта, если команда завершилась ошибкой +# set -u - прекращает выполнение скрипта, если встретилась несуществующая переменная +# set -x - выводит выполняемые команды в stdout перед выполнением (только для отладки, а то замусоривает журнал!) +# set -o pipefail - прекращает выполнение скрипта, даже если одна из частей пайпа завершилась ошибкой +set -euo pipefail + +SCRIPT_FILE=$(readlink -f "0ドル") +SCRIPT_DIR=$(dirname "$SCRIPT_FILE") + +bash -n "$SCRIPT_FILE" || exit # check syntax this file + +# colors +Red='\e[1;31m' +Green='\e[0;32m' +Yellow='\e[38;5;220m' +Blue='\e[38;5;39m' +Orange='\e[38;5;214m' +Magenta='\e[0;35m' +Cyan='\e[0;36m' +Gray='\e[0;37m' +White='\e[1;37m' +Reset='\e[0m' + +# colored messages +echoerr() { echo -e "${Red}$@${Reset}" 1>&2; } # ошибки +echowarn() { echo -e "${Yellow}$@${Reset}" 1>&2; } # предупреждения +echohead() { echo -e "${Blue}$@${Reset}" ; } # заголовок или этап +echoinfo() { echo -e "${White}$@${Reset}" ; } # важные сообщения +echosucc() { echo -e "${Green}$@${Reset}" ; } # сообщения об успехе + +# функция подсчитывает длительность (day:hh:mm:ss) между временными метками в Unixtime +elapsed() { + local time_start=1ドル # time_start=$(date +%s) + local time_end=2ドル # time_end=$(date +%s) + local dt=$(echo "$time_end - $time_start" | bc) + local dd=$(echo "$dt/86400" | bc) + local dt2=$(echo "$dt-86400*$dd" | bc) + local dh=$(echo "$dt2/3600" | bc) + local dt3=$(echo "$dt2-3600*$dh" | bc) + local dm=$(echo "$dt3/60" | bc) + local ds=$(echo "$dt3-60*$dm" | bc) + printf '%dd:%02d:%02d:%02d' $dd $dh $dm $ds +} + +read -p "Запустить тестирование скрипта pg_backup.sh на тестовой СУБД? (yes/no): " RUN_FLAG +if test "$RUN_FLAG" != "yes"; then + echowarn "Запуск отменён" + exit 1 +fi + +echohead "Test started" +TIME_START=$(date +%s) # время в Unixtime + +BACKUP_DIR="/mnt/backup_db/active_full/cluster" +TEMP_DIR="$BACKUP_DIR/pg_backup_test" +CONF_FILE="$SCRIPT_DIR/pg_backup.conf" + +for BACKUP_WAL_DOY_DIVIDER in 1 999; do + for FLAG in 0 1; do + + echohead "Корректируем '$CONF_FILE': BACKUP_WAL_DOY_DIVIDER=$BACKUP_WAL_DOY_DIVIDER, GPG_ENCRYPT=$FLAG, PG_AMCHECK_VALIDATE=$FLAG" + sed -E -e "s/(BACKUP_WAL_DOY_DIVIDER)=[0-9]+/1円=${BACKUP_WAL_DOY_DIVIDER}/" \ + -e "s/(GPG_ENCRYPT)=[0-9]+/1円=${FLAG}/" \ + -e "s/(PG_AMCHECK_VALIDATE)=[0-9]+/1円=${FLAG}/" \ + -i $CONF_FILE + + echohead "Удаляем все файлы в папке '$BACKUP_DIR'" + find $BACKUP_DIR -mindepth 1 -delete + + echohead "Тестируем ExecCondition" + sudo -i -u postgres -- ./pg_backup.sh ExecCondition + + echohead "Тестируем create" + sudo -i -u postgres -- ./pg_backup.sh create + + echohead "Тестируем validate" + sudo -i -u postgres -- ./pg_backup.sh validate + + echohead "Получаем название папки/файла бекапа" + BACKUP_FILE_OR_DIR=$(find $BACKUP_DIR -maxdepth 1 -name "*.pg_backup*" ! -name "*.log" -printf "%p") + test -z "$BACKUP_FILE_OR_DIR" && echoerr "no backup archive file/directory found in directory '$BACKUP_DIR'" && exit 1 + echo "Название папки/файла бекапа: '$BACKUP_FILE_OR_DIR'" + + echohead "Создаём временную папку '$TEMP_DIR'" + sudo -i -u postgres -- mkdir -p $TEMP_DIR + + echohead "Тестируем restore" + printf 'primary\n' | sudo -i -u postgres -- ./pg_backup.sh restore $BACKUP_FILE_OR_DIR $TEMP_DIR + + echohead "Удаляем временную папку '$TEMP_DIR'" + sudo -i -u postgres -- rm -r $TEMP_DIR + done +done + +TIME_END=$(date +%s) # время в Unixtime +TIME_ELAPSED=$(elapsed $TIME_START $TIME_END) +echosucc "Test finished successfully, duration: $TIME_ELAPSED (day:hh:mm:ss)" From 74f648546d7d1b5df06b6c7926724a075c75bd54 Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 9 Sep 2025 00:11:41 +0300 Subject: [PATCH 088/145] Update pg_backup.service --- pg_backup/pg_backup.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/pg_backup.service b/pg_backup/pg_backup.service index 979c0b2..130f815 100644 --- a/pg_backup/pg_backup.service +++ b/pg_backup/pg_backup.service @@ -6,7 +6,7 @@ User=postgres Group=postgres ExecCondition=/bin/bash /var/lib/pgsql/pg_backup.sh ExecCondition -ExecStart=/bin/bash /var/lib/pgsql/pg_backup.sh +ExecStart=/bin/bash /var/lib/pgsql/pg_backup.sh create [Install] WantedBy=multi-user.target From 2247a908a95ab1a10b067a8afbbed42b35c8d6ed Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 9 Sep 2025 00:16:27 +0300 Subject: [PATCH 089/145] Update README.md --- pg_backup/README.md | 54 ++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index 95acc09..7305bbf 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -58,7 +58,7 @@ foreach ($db_host in $db_hosts) { } ``` -**Шаг 2. Выполнить на каждом сервере СУБД Linux (Bash)** +**Шаг 2. Выполнить на каждом сервере СУБД Linux (Bash) - создаём файлы** ```bash sudo -i @@ -79,7 +79,7 @@ chown postgres:postgres .pgpass {pg_backup,{archive,restore}_command}.sh pg_back # проверяем работоспособность (отладка), выводим сообщения на экран sudo -i -u postgres -- ./pg_backup.sh ExecCondition # будем ли создавать или проверять резервную копию с текущего сервера СУБД (см. код возврата)? -sudo -i -u postgres -- ./pg_backup.sh # создаст резервную копию текущего сервера СУБД +sudo -i -u postgres -- ./pg_backup.sh create # создаст резервную копию текущего сервера СУБД sudo -i -u postgres -- ./pg_backup.sh validate # проверит корректность и восстанавливаемость резервной копии СУБД sudo -i -u postgres -- ./pg_backup.sh restore SOURCE_BACKUP_FILE_OR_DIR TARGET_PG_DATA_DIR # восстановит резервную копию СУБД @@ -89,24 +89,37 @@ sudo -i -u postgres -- ./pg_backup.sh restore SOURCE_BACKUP_FILE_OR_DIR TARGET_P (cp --update --backup $HOME_DIR/pg_install/pg_backup_validate.timer /etc/systemd/system || nano -c /etc/systemd/system/pg_backup_validate.timer) && \ (cp --update --backup $HOME_DIR/pg_install/pg_backup_validate.service /etc/systemd/system || nano -c /etc/systemd/system/pg_backup_validate.service) && \ systemctl daemon-reload # активируем - +``` + +**Шаг 3. Выполнить на каждом сервере СУБД Linux (Bash) - запускаем pg_backup** +```bash # добавляем в автозагрузку systemctl enable pg_backup.timer && \ -systemctl enable pg_backup.service && \ -systemctl enable pg_backup_validate.timer && \ -systemctl enable pg_backup_validate.service +systemctl enable pg_backup.service -# запускаем; сделает резервную копию СУБД, если условие ExecCondition выполнится (НЕ выведет сообщения на экран) +# запускаем; сделает резервную копию СУБД, если условие ExecCondition выполнится systemctl start pg_backup.timer && \ -systemctl start pg_backup.service && \ -systemctl start pg_backup_validate.timer && \ -systemctl start pg_backup_validate.service +systemctl start pg_backup.service -# проверяем статус (1) +# проверяем статус systemctl status pg_backup.timer && \ systemctl status pg_backup.service -# проверяем статус (2) +# получаем список активных таймеров, д.б. указана дата-время следующего запуска! +systemctl list-timers | grep -P 'NEXT|pg_backup' +``` + +**Шаг 4. Выполнить на каждом сервере СУБД Linux (Bash) - запускаем pg_backup_validate** +```bash +# добавляем в автозагрузку +systemctl enable pg_backup_validate.timer && \ +systemctl enable pg_backup_validate.service + +# запускаем; проверит корректность и восстанавливаемость резервной копии СУБД, если условие ExecCondition выполнится +systemctl start pg_backup_validate.timer && \ +systemctl start pg_backup_validate.service + +# проверяем статус systemctl status pg_backup_validate.timer && \ systemctl status pg_backup_validate.service @@ -115,14 +128,15 @@ systemctl list-timers | grep -P 'NEXT|pg_backup' ``` Файлы -* [`/etc/systemd/system/pg_backup.timer`](pg_backup.timer) -* [`/etc/systemd/system/pg_backup.service`](pg_backup.service) -* [`/etc/systemd/system/pg_backup_validate.timer`](pg_backup_validate.timer) -* [`/etc/systemd/system/pg_backup_validate.service`](pg_backup_validate.service) -* [`/var/lib/pgsql/pg_backup.sh`](pg_backup.sh) -* [`/var/lib/pgsql/pg_backup.conf`](pg_backup.conf) -* [`/var/lib/pgsql/archive_command.sh`](archive_command.sh) -* [`/var/lib/pgsql/restore_command.sh`](restore_command.sh) +1. [`/etc/systemd/system/pg_backup.timer`](pg_backup.timer) +1. [`/etc/systemd/system/pg_backup.service`](pg_backup.service) +1. [`/etc/systemd/system/pg_backup_validate.timer`](pg_backup_validate.timer) +1. [`/etc/systemd/system/pg_backup_validate.service`](pg_backup_validate.service) +1. [`/var/lib/pgsql/pg_backup.sh`](pg_backup.sh) +1. [`/var/lib/pgsql/pg_backup_test.sh`](pg_backup_test.sh) +1. [`/var/lib/pgsql/pg_backup.conf`](pg_backup.conf) +1. [`/var/lib/pgsql/archive_command.sh`](archive_command.sh) +1. [`/var/lib/pgsql/restore_command.sh`](restore_command.sh) ## Ссылки по теме * [PostgreSQL: копирование WAL файлов в архив (archive_command)](archive_command.md) From 67730149765e0c51f4821a4c51ceaa0e52b4dbf6 Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 9 Sep 2025 00:27:55 +0300 Subject: [PATCH 090/145] Update pg_backup_test.sh --- pg_backup/pg_backup_test.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pg_backup/pg_backup_test.sh b/pg_backup/pg_backup_test.sh index b19de59..24854ee 100644 --- a/pg_backup/pg_backup_test.sh +++ b/pg_backup/pg_backup_test.sh @@ -59,6 +59,9 @@ BACKUP_DIR="/mnt/backup_db/active_full/cluster" TEMP_DIR="$BACKUP_DIR/pg_backup_test" CONF_FILE="$SCRIPT_DIR/pg_backup.conf" +echohead "Запускаем pg_backup.sh без параметров" +sudo -i -u postgres -- ./pg_backup.sh || true + for BACKUP_WAL_DOY_DIVIDER in 1 999; do for FLAG in 0 1; do From 7ff6436be95220d121d96915f54b931000047d79 Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 9 Sep 2025 18:09:16 +0300 Subject: [PATCH 091/145] Update README.md --- pg_backup/README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index 7305bbf..e9d54b1 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -33,6 +33,15 @@ или [`pg_receivewal`](https://postgrespro.ru/docs/postgresql/16/app-pgreceivewal). > 1. Следует учесть [ограничения создания резервной копии с реплики](https://postgrespro.ru/docs/postgresql/16/app-pgbasebackup)! +Валидация — это выполнение команд с самым низким приоритетом и только на реплике (при её наличии): +1. дешифрование и распаковка архива с бекапом во временную папку +1. проверка файлов СУБД через pg_verifybackup +1. запуск СУБД (на отдельном порту) +1. проверка СУБД через pg_amcheck (опционально) +1. остановка СУБД +1. проверка СУБД через pg_checksums +1. сохранение артефактов рядом с бекапом и удаление временной папки + ## Инсталляция **Шаг 1. Выполнить на терминальном сервере Windows (PowerShell)** @@ -91,7 +100,7 @@ sudo -i -u postgres -- ./pg_backup.sh restore SOURCE_BACKUP_FILE_OR_DIR TARGET_P systemctl daemon-reload # активируем ``` -**Шаг 3. Выполнить на каждом сервере СУБД Linux (Bash) - запускаем pg_backup** +**Шаг 3. Выполнить на каждом сервере СУБД Linux (Bash) - запускаем сервис создания бекапов** ```bash # добавляем в автозагрузку systemctl enable pg_backup.timer && \ @@ -109,7 +118,7 @@ systemctl status pg_backup.service systemctl list-timers | grep -P 'NEXT|pg_backup' ``` -**Шаг 4. Выполнить на каждом сервере СУБД Linux (Bash) - запускаем pg_backup_validate** +**Шаг 4. Выполнить на каждом сервере СУБД Linux (Bash) - запускаем сервис валидации бекапов** ```bash # добавляем в автозагрузку systemctl enable pg_backup_validate.timer && \ From 550df7960195bcdb4e76a9bd1b5603b7e5d7fdbd Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 9 Sep 2025 18:10:09 +0300 Subject: [PATCH 092/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index e9d54b1..361be39 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -34,7 +34,7 @@ > 1. Следует учесть [ограничения создания резервной копии с реплики](https://postgrespro.ru/docs/postgresql/16/app-pgbasebackup)! Валидация — это выполнение команд с самым низким приоритетом и только на реплике (при её наличии): -1. дешифрование и распаковка архива с бекапом во временную папку +1. дешифрование (опционально) и распаковка архива с бекапом во временную папку 1. проверка файлов СУБД через pg_verifybackup 1. запуск СУБД (на отдельном порту) 1. проверка СУБД через pg_amcheck (опционально) From 64dbee5eee9229621ccab72ad8e316054e39b744 Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 9 Sep 2025 18:14:59 +0300 Subject: [PATCH 093/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index 361be39..94c6afe 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -33,7 +33,7 @@ или [`pg_receivewal`](https://postgrespro.ru/docs/postgresql/16/app-pgreceivewal). > 1. Следует учесть [ограничения создания резервной копии с реплики](https://postgrespro.ru/docs/postgresql/16/app-pgbasebackup)! -Валидация — это выполнение команд с самым низким приоритетом и только на реплике (при её наличии): +Валидация — это выполнение команд с самым низким приоритетом (минимизации рисков влияния на работающую СУБД) и только на реплике (при её наличии): 1. дешифрование (опционально) и распаковка архива с бекапом во временную папку 1. проверка файлов СУБД через pg_verifybackup 1. запуск СУБД (на отдельном порту) From e81b542dd1f925e07d377623517f1a1c53d94006 Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 9 Sep 2025 18:15:38 +0300 Subject: [PATCH 094/145] Update pg_backup.sh --- pg_backup/pg_backup.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index acd0111..a0aa5cc 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -46,7 +46,8 @@ elapsed() { printf '%dd:%02d:%02d:%02d' $dd $dh $dm $ds } -# меняем приоритет этого процесса ($$ - это его pid) на минимальный (дочерние процессы наследуют значение приоритета родительского процесса) +# для минимизации рисков влияния на работающую СУБД меняем приоритет этого процесса ($$ - это его pid) на минимальный +# (дочерние процессы наследуют значение приоритета родительского процесса) ionice -c 2 -n 7 -p $$ renice -n 19 -p $$ From e1c4d94872e83465fa355581044baecd59138ab8 Mon Sep 17 00:00:00 2001 From: Rinat Date: Tue, 9 Sep 2025 18:16:32 +0300 Subject: [PATCH 095/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index 94c6afe..ae3b60e 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -33,7 +33,7 @@ или [`pg_receivewal`](https://postgrespro.ru/docs/postgresql/16/app-pgreceivewal). > 1. Следует учесть [ограничения создания резервной копии с реплики](https://postgrespro.ru/docs/postgresql/16/app-pgbasebackup)! -Валидация — это выполнение команд с самым низким приоритетом (минимизации рисков влияния на работающую СУБД) и только на реплике (при её наличии): +Валидация — это выполнение команд с самым низким приоритетом (для минимизации рисков влияния на работающую СУБД) и только на реплике (при её наличии): 1. дешифрование (опционально) и распаковка архива с бекапом во временную папку 1. проверка файлов СУБД через pg_verifybackup 1. запуск СУБД (на отдельном порту) From e45c2ad9a9c84341465bfdbc2423c633ba47b848 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月11日 11:11:26 +0300 Subject: [PATCH 096/145] pg v12 --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index ae3b60e..e427e2f 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -9,7 +9,7 @@ ## Требования * GNU/Linux -* PostgreSQL ≥ v14 +* PostgreSQL ≥ v12 * Bash ≥ 4.4 ## Как это работает? From f0b40ef01b1a80f3244b4f47a1aa3c6394405c9b Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月11日 11:13:25 +0300 Subject: [PATCH 097/145] pg v12, errors fixed in some cases --- pg_backup/pg_backup.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index a0aa5cc..6cbef20 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -91,7 +91,7 @@ if test "${1:-}" = "ExecCondition"; then PG_ROLE=$(psql --username=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ --command="select case when pg_is_in_recovery() then 'standby' else 'primary' end") echo "pg_backup: candidate role is $PG_ROLE (checked by psql)" - test ${2:='primary'} = "$PG_ROLE" + test ${2:-primary} = "$PG_ROLE" exit fi @@ -366,14 +366,14 @@ COMPRESS_THREADS=$(echo "$(nproc) / 2.5 + 1" | bc) # для многопоточного режима используется максимальная степень сжатия 5, которая была получена опытным путём # это баланс между скоростью работы, размером сжатого файла, скоростью записи на сетевой диск, с учётом нагрузки другими процессами COMPRESS_LEVEL=$COMPRESS_THREADS -test "$COMPRESS_LEVEL" -gt 5 && $COMPRESS_LEVEL=5 +test "$COMPRESS_LEVEL" -gt 5 && COMPRESS_LEVEL=5 echo 'Проверяем необходимость бекапирования WAL файлов' # зависит от текущего дня, настройки параметра archive_mode и роли СУБД primary/standby IS_BACKUP_WAL=$(psql --username=$PG_USERNAME --no-password --dbname=postgres --quiet --no-psqlrc --pset=null=¤ --tuples-only --no-align \ - --command="select extract(doy from now())%${BACKUP_WAL_DOY_DIVIDER}=0 - or setting='off' or (pg_is_in_recovery() and setting='on') - from pg_settings where name='archive_mode'") + --command="select extract(doy from now())::int % ${BACKUP_WAL_DOY_DIVIDER} = 0 --cast to int for Postgres v12 + or setting = 'off' or (pg_is_in_recovery() and setting = 'on') + from pg_settings where name = 'archive_mode'") if test "$IS_BACKUP_WAL" = "f"; then echo 'Создаём физическую резервную копию (без WAL файлов)' From d5e83d44bde42a335d7adb3ca8d33d51a3720ed1 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月16日 22:57:28 +0300 Subject: [PATCH 098/145] Update pg_dump_restore.md --- experiments/pg_dump_restore.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/experiments/pg_dump_restore.md b/experiments/pg_dump_restore.md index 03e0dbb..095b372 100644 --- a/experiments/pg_dump_restore.md +++ b/experiments/pg_dump_restore.md @@ -1,5 +1,8 @@ # Эксперимент по созданию дампов БД и воссозданию БД из дампов +> [!NOTE] +> Актуальность — ноябрь 2023. + ## Введение Рассматривается логическая, а не физическая резервная копия (бекап) БД. From 2e744dadf5dc986dd54bc83e71101003ab4c3731 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月16日 22:59:56 +0300 Subject: [PATCH 099/145] Update delta_encode.md --- experiments/delta_encode.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/experiments/delta_encode.md b/experiments/delta_encode.md index dacefaf..72825c7 100644 --- a/experiments/delta_encode.md +++ b/experiments/delta_encode.md @@ -1,5 +1,8 @@ # Эксперимент по дельта-кодированию числовых массивов в PostgreSQL +> [!NOTE] +> Актуальность — ноябрь 2023. + ```sql drop table if exists test.delta; @@ -84,4 +87,4 @@ from test.delta; | jsonb\_int\_array | 2130 | 2044 | 5490 | 4496 | Отсортированный список чисел (например список идентификаторов) с дельта + Фибоначчи кодированием -и хранением в `bytea` занимает почти 2 раза меньше места, чем в обычном массиве `int[]`. \ No newline at end of file +и хранением в `bytea` занимает почти 2 раза меньше места, чем в обычном массиве `int[]`. From 9a1571b5ab8d5af460000a2dd36609c4a5f611cf Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月16日 23:02:19 +0300 Subject: [PATCH 100/145] xz support --- pg_archive_log/pg_archive_log.service | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pg_archive_log/pg_archive_log.service b/pg_archive_log/pg_archive_log.service index 406b71a..1b9d8dd 100644 --- a/pg_archive_log/pg_archive_log.service +++ b/pg_archive_log/pg_archive_log.service @@ -12,14 +12,17 @@ ExecStartPre=mkdir -p ${LOG_DIR} ExecStartPre=chmod 750 ${LOG_DIR} # удаляем файлы старше N дней -ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +30 -delete +ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +90 -delete # удаляем файлы нулевого размера старше K дней -ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +2 -size 0 -delete +ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size 0 -delete -# архивируем несжатые файлы старше M дней (достаточно одного потока zstd, т.к. файлы относительно небольшие) -# не ставьте большой уровень компрессии, это приводит к большому потреблению CPU, а экономия на размере файла несущественная -ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size +100k ! -name "*.zst" -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; +# архивируем несжатые файлы старше M дней (достаточно одного потока, т.к. файлы относительно небольшие) +# выбирайте, что для вас важнее, скорость работы или размер файла: +# 'zstd -9' быстрее в 12 раз, но больше размер файла на 13%, чем 'xz -9' +# 'xz -9' быстрее в 3.5 раза, но больше размер файла на 14%, чем 'zstd -19' +# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +0 -size +100k ! -name "*.zst" ! -name "*.xz" -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; +ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +0 -size +100k ! -name "*.zst" ! -name "*.xz" -exec ionice -c2 -n7 -- nice -n19 -- xz -9 '{}' \; [Install] WantedBy=multi-user.target From 7a66fa1898fbabca6f8d309979fd5347fc20d1dd Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月16日 23:04:07 +0300 Subject: [PATCH 101/145] Update README.md --- pg_archive_log/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index 7cd9577..306e13a 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -5,7 +5,7 @@ [Systemd](https://en.wikipedia.org/wiki/Systemd) сервис, который запускается 1 раз в сутки. 1. удаляет файлы старше N дней 2. удаляет файлы нулевого размера старше K дней -3. архивирует несжатые файлы старше М дней в формат `zstd`, если размер файла> S килобайт +3. архивирует несжатые файлы старше М дней, если размер файла> S килобайт ## Предусловия ```ini From 4791cd0010b5456ac29eb739a8c688dbafc4595f Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月18日 10:33:47 +0300 Subject: [PATCH 102/145] test (de)compression --- pg_archive_log/README.md | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index 306e13a..a320345 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -2,12 +2,19 @@ ## Описание -[Systemd](https://en.wikipedia.org/wiki/Systemd) сервис, который запускается 1 раз в сутки. +[Systemd](https://en.wikipedia.org/wiki/Systemd) сервис, который запускается 1 раз в сутки: 1. удаляет файлы старше N дней -2. удаляет файлы нулевого размера старше K дней -3. архивирует несжатые файлы старше М дней, если размер файла> S килобайт +1. удаляет файлы нулевого размера старше K дней +1. архивирует несжатые файлы старше М дней, если размер файла> S килобайт + +Команда для архивации (сжатия) файлов выполняется в один поток с самым низким приоритетом (для минимизации рисков влияния на работающую СУБД). + +## Требования + +Место на диске обычно ограничено и не бесплатное. За длительный период хранения файлов их необходимо сжимать как можно сильнее, но не потребляя слишком много ресурсов (CPU, память). В данном случае степень сжатия важнее скорости сжатия и распаковки (можно подождать). ## Предусловия + ```ini log_destination = 'csvlog' #опционально log_directory = '/var/log/postgresql/16' #для надёжности, папку /var/log лучше сделать в отдельном разделе ФС с квотой свободного места @@ -41,3 +48,17 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' **Файлы** 1. [`/etc/systemd/system/pg_archive_log.timer`](pg_archive_log.timer) 2. [`/etc/systemd/system/pg_archive_log.service`](pg_archive_log.service) + +## Тестирование сжатия + +| Program and compression level | Compression size (bytes) | Compression size (%) | Compression duration (s) | Compression memory (KB) | Decompression duration (s) | Decompression memory (KB) | Rating place | +| :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| `gzip -9` | 3,453,449 | 144% | 1.24 | 2,416 | 0.46 | 2,408 | — | +| `zstd -9` | 2,381,965 | 100% | 1.55 | 41,580 | 0.04 | 4,220 | 3 | +| `zstd -14` | 2,449,812 | 103% | 2.61 | 117,560 | 0.04 | 6,436 | — | +| `zstd -19` | 1,760,829 | 74% | 77.40 | 216,512 | 0.08 | 10,444 | — | +| `bzip2 -9` | 2,138,384 | 90% | 37.70 | 7,944 | 3.34 | 5,032 | — | +| `bzip3 -b8` | 1,509,780 | 63% | 2.76 | 21,212 | 1.99 | 52,428 | 1 | +| `bzip3 -b16` | 1,471,514 | 62% | 2.71 | 39,424 | 1.91 | 101,636 | 2 | +| `bzip3 -b64` | 1,412,929 | 59% | 2.98 | 149,040 | 2.12 | 396,484 | — | +| `xz -9` | 2,057,736 | 86% | 18,34 | 629,832 | 0.38 | 16,510 | — | From 0cb861a8b6e4aa5265c3251d3878b9ef9dd14cc8 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月18日 10:35:15 +0300 Subject: [PATCH 103/145] xz replaced to bzip3 --- pg_archive_log/pg_archive_log.service | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pg_archive_log/pg_archive_log.service b/pg_archive_log/pg_archive_log.service index 1b9d8dd..3244fda 100644 --- a/pg_archive_log/pg_archive_log.service +++ b/pg_archive_log/pg_archive_log.service @@ -11,18 +11,19 @@ Environment="LOG_DIR=/var/log/postgresql/16" ExecStartPre=mkdir -p ${LOG_DIR} ExecStartPre=chmod 750 ${LOG_DIR} -# удаляем файлы старше N дней +# удаляем файлы старше N дней (в промышленной среде поставьте 180 или 90, в тестовой 7) ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +90 -delete # удаляем файлы нулевого размера старше K дней ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size 0 -delete # архивируем несжатые файлы старше M дней (достаточно одного потока, т.к. файлы относительно небольшие) -# выбирайте, что для вас важнее, скорость работы или размер файла: -# 'zstd -9' быстрее в 12 раз, но больше размер файла на 13%, чем 'xz -9' -# 'xz -9' быстрее в 3.5 раза, но больше размер файла на 14%, чем 'zstd -19' -# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +0 -size +100k ! -name "*.zst" ! -name "*.xz" -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; -ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +0 -size +100k ! -name "*.zst" ! -name "*.xz" -exec ionice -c2 -n7 -- nice -n19 -- xz -9 '{}' \; +# выбирайте, что для вас больше подходит (zstd или bzip3): +# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size +100k ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; +# bzip3 ≥ v1.5.0: +# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size +100k ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 --rm '{}' \; +# bzip3 < v1.5.0: +ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size +100k ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 '{}' \; -exec rm -f '{}' \; [Install] WantedBy=multi-user.target From 2a8dc35c18c93a27e7ee62d7a4e51cd301e2a7e6 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月18日 10:42:02 +0300 Subject: [PATCH 104/145] test (de)compression doc --- pg_archive_log/README.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index a320345..f39385c 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -50,15 +50,19 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' 2. [`/etc/systemd/system/pg_archive_log.service`](pg_archive_log.service) ## Тестирование сжатия - + +Исходный тестовый файл: `postgresql-2025年09月14日.csv` **186,311,281 байт**.\ +Сжатие и распаковка в один поток. Распаковка в `/dev/null`.\ +Для замеров длительности выполнения и потребления памяти использовалась команда `/usr/bin/time -v COMMAND`. + | Program and compression level | Compression size (bytes) | Compression size (%) | Compression duration (s) | Compression memory (KB) | Decompression duration (s) | Decompression memory (KB) | Rating place | | :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | | `gzip -9` | 3,453,449 | 144% | 1.24 | 2,416 | 0.46 | 2,408 | — | | `zstd -9` | 2,381,965 | 100% | 1.55 | 41,580 | 0.04 | 4,220 | 3 | -| `zstd -14` | 2,449,812 | 103% | 2.61 | 117,560 | 0.04 | 6,436 | — | -| `zstd -19` | 1,760,829 | 74% | 77.40 | 216,512 | 0.08 | 10,444 | — | -| `bzip2 -9` | 2,138,384 | 90% | 37.70 | 7,944 | 3.34 | 5,032 | — | -| `bzip3 -b8` | 1,509,780 | 63% | 2.76 | 21,212 | 1.99 | 52,428 | 1 | -| `bzip3 -b16` | 1,471,514 | 62% | 2.71 | 39,424 | 1.91 | 101,636 | 2 | -| `bzip3 -b64` | 1,412,929 | 59% | 2.98 | 149,040 | 2.12 | 396,484 | — | -| `xz -9` | 2,057,736 | 86% | 18,34 | 629,832 | 0.38 | 16,510 | — | +| `zstd -14` | 2,449,812 | 103% | 2.61 | 117,560 | 0.04 | 6,436 | — | +| `zstd -19` | 1,760,829 | 74% | 77.40 | 216,512 | 0.08 | 10,444 | — | +| `bzip2 -9` | 2,138,384 | 90% | 37.70 | 7,944 | 3.34 | 5,032 | — | +| `bzip3 -b8` | 1,509,780 | 63% | 2.76 | 21,212 | 1.99 | 52,428 | 1 | +| `bzip3 -b16` | 1,471,514 | 62% | 2.71 | 39,424 | 1.91 | 101,636 | 2 | +| `bzip3 -b64` | 1,412,929 | 59% | 2.98 | 149,040 | 2.12 | 396,484 | — | +| `xz -9` | 2,057,736 | 86% | 18,34 | 629,832 | 0.38 | 16,510 | — | From e6d06b4ce7c749d8f4f9f11f493af41889a721c1 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月18日 10:52:06 +0300 Subject: [PATCH 105/145] test (de)compression program versions --- pg_archive_log/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index f39385c..fd5c823 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -66,3 +66,12 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' | `bzip3 -b16` | 1,471,514 | 62% | 2.71 | 39,424 | 1.91 | 101,636 | 2 | | `bzip3 -b64` | 1,412,929 | 59% | 2.98 | 149,040 | 2.12 | 396,484 | — | | `xz -9` | 2,057,736 | 86% | 18,34 | 629,832 | 0.38 | 16,510 | — | + +### Версии ПО + +| Program | Version | +| ------- | ------- | +| [zstd](https://github.com/facebook/zstd) | 1.4.4 | +| bzip2 | 1.0.6 | +| [bzip3](https://github.com/iczelia/bzip3) | 1.3.1 | +| xz, liblzma | 5.2.4 | From 03257297a9f079c2e65c685b4776485edf8e3bb5 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月18日 11:24:49 +0300 Subject: [PATCH 106/145] test (de)compression table colors --- pg_archive_log/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index fd5c823..57bc1b3 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -57,15 +57,15 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' | Program and compression level | Compression size (bytes) | Compression size (%) | Compression duration (s) | Compression memory (KB) | Decompression duration (s) | Decompression memory (KB) | Rating place | | :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| `gzip -9` | 3,453,449 | 144% | 1.24 | 2,416 | 0.46 | 2,408 | — | -| `zstd -9` | 2,381,965 | 100% | 1.55 | 41,580 | 0.04 | 4,220 | 3 | -| `zstd -14` | 2,449,812 | 103% | 2.61 | 117,560 | 0.04 | 6,436 | — | -| `zstd -19` | 1,760,829 | 74% | 77.40 | 216,512 | 0.08 | 10,444 | — | -| `bzip2 -9` | 2,138,384 | 90% | 37.70 | 7,944 | 3.34 | 5,032 | — | -| `bzip3 -b8` | 1,509,780 | 63% | 2.76 | 21,212 | 1.99 | 52,428 | 1 | -| `bzip3 -b16` | 1,471,514 | 62% | 2.71 | 39,424 | 1.91 | 101,636 | 2 | -| `bzip3 -b64` | 1,412,929 | 59% | 2.98 | 149,040 | 2.12 | 396,484 | — | -| `xz -9` | 2,057,736 | 86% | 18,34 | 629,832 | 0.38 | 16,510 | — | +| `gzip -9` | $\color{#f00}{3,453,449}$ | $\color{#f00}{144\\%}$ | $\color{#090}{1.24}$ | $\color{#090}{2,416}$ | $\color{#090}{0.46}$ | $\color{#090}{2,408}$ | — | +| `zstd -9` | $\color{#f00}{2,381,965}$ | 100% | $\color{#090}{1.55}$ | $\color{#090}{41,580}$ | $\color{#090}{0.04}$ | $\color{#090}{4,220}$ | 3 | +| `zstd -14` | $\color{#f00}{2,449,812}$ | $\color{#f00}{103\\%}$ | $\color{#090}{2.61}$ | 117,560 | $\color{#090}{0.04}$ | $\color{#090}{6,436}$ | — | +| `zstd -19` | 1,760,829 | 74% | $\color{#f00}{77.40}$ | $\color{#f00}{216,512}$ | $\color{#090}{0.08}$ | $\color{#090}{10,444}$ | — | +| `bzip2 -9` | 2,138,384 | 90% | $\color{#f00}{37.70}$ | $\color{#090}{7,944}$ | $\color{#f00}{3.34}$ | $\color{#090}{5,032}$ | — | +| `bzip3 -b8` | $\color{#090}{1,509,780}$ | $\color{#090}{63\\%}$ | $\color{#090}{2.76}$ | $\color{#090}{21,212}$ | 1.99 | $\color{#090}{52,428}$ | 1 | +| `bzip3 -b16` | $\color{#090}{1,471,514}$ | $\color{#090}{62\\%}$ | $\color{#090}{2.71}$ | $\color{#090}{39,424}$ | 1.91 | 101,636 | 2 | +| `bzip3 -b64` | $\color{#090}{1,412,929}$ | $\color{#090}{59\\%}$ | $\color{#090}{2.98}$ | $\color{#f00}{149,040}$ | 2.12 | $\color{#f00}{396,484}$ | — | +| `xz -9` | 2,057,736 | 86% | $\color{#f00}{18,34}$ | $\color{#f00}{629,832}$ | $\color{#090}{0.38}$ | $\color{#090}{16,510}$ | — | ### Версии ПО From 73facb3294987cad189bcf5068ea8363c63ff1e1 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月18日 11:28:41 +0300 Subject: [PATCH 107/145] test (de)compression table spaces --- pg_archive_log/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index 57bc1b3..df0cf3e 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -57,15 +57,15 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' | Program and compression level | Compression size (bytes) | Compression size (%) | Compression duration (s) | Compression memory (KB) | Decompression duration (s) | Decompression memory (KB) | Rating place | | :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| `gzip -9` | $\color{#f00}{3,453,449}$ | $\color{#f00}{144\\%}$ | $\color{#090}{1.24}$ | $\color{#090}{2,416}$ | $\color{#090}{0.46}$ | $\color{#090}{2,408}$ | — | -| `zstd -9` | $\color{#f00}{2,381,965}$ | 100% | $\color{#090}{1.55}$ | $\color{#090}{41,580}$ | $\color{#090}{0.04}$ | $\color{#090}{4,220}$ | 3 | -| `zstd -14` | $\color{#f00}{2,449,812}$ | $\color{#f00}{103\\%}$ | $\color{#090}{2.61}$ | 117,560 | $\color{#090}{0.04}$ | $\color{#090}{6,436}$ | — | -| `zstd -19` | 1,760,829 | 74% | $\color{#f00}{77.40}$ | $\color{#f00}{216,512}$ | $\color{#090}{0.08}$ | $\color{#090}{10,444}$ | — | -| `bzip2 -9` | 2,138,384 | 90% | $\color{#f00}{37.70}$ | $\color{#090}{7,944}$ | $\color{#f00}{3.34}$ | $\color{#090}{5,032}$ | — | -| `bzip3 -b8` | $\color{#090}{1,509,780}$ | $\color{#090}{63\\%}$ | $\color{#090}{2.76}$ | $\color{#090}{21,212}$ | 1.99 | $\color{#090}{52,428}$ | 1 | -| `bzip3 -b16` | $\color{#090}{1,471,514}$ | $\color{#090}{62\\%}$ | $\color{#090}{2.71}$ | $\color{#090}{39,424}$ | 1.91 | 101,636 | 2 | -| `bzip3 -b64` | $\color{#090}{1,412,929}$ | $\color{#090}{59\\%}$ | $\color{#090}{2.98}$ | $\color{#f00}{149,040}$ | 2.12 | $\color{#f00}{396,484}$ | — | -| `xz -9` | 2,057,736 | 86% | $\color{#f00}{18,34}$ | $\color{#f00}{629,832}$ | $\color{#090}{0.38}$ | $\color{#090}{16,510}$ | — | +| `gzip -9` | $\color{#f00}{3,453,449}$ | $\color{#f00}{144\\%}$ | $\color{#090}{1.24}$ | $\color{#090}{2,416}$ | $\color{#090}{0.46}$ | $\color{#090}{2,408}$ | — | +| `zstd -9` | $\color{#f00}{2,381,965}$ | 100% | $\color{#090}{1.55}$ | $\color{#090}{41,580}$ | $\color{#090}{0.04}$ | $\color{#090}{4,220}$ | 3 | +| `zstd -14` | $\color{#f00}{2,449,812}$ | $\color{#f00}{103\\%}$ | $\color{#090}{2.61}$ | 117,560 | $\color{#090}{0.04}$ | $\color{#090}{6,436}$ | — | +| `zstd -19` | 1,760,829 | 74% | $\color{#f00}{77.40}$ | $\color{#f00}{216,512}$ | $\color{#090}{0.08}$ | $\color{#090}{10,444}$ | — | +| `bzip2 -9` | 2,138,384 | 90% | $\color{#f00}{37.70}$ | $\color{#090}{7,944}$ | $\color{#f00}{3.34}$ | $\color{#090}{5,032}$ | — | +| `bzip3 -b8` | $\color{#090}{1,509,780}$ | $\color{#090}{63\\%}$ | $\color{#090}{2.76}$ | $\color{#090}{21,212}$ | 1.99 | $\color{#090}{52,428}$ | 1 | +| `bzip3 -b16` | $\color{#090}{1,471,514}$ | $\color{#090}{62\\%}$ | $\color{#090}{2.71}$ | $\color{#090}{39,424}$ | 1.91 | 101,636 | 2 | +| `bzip3 -b64` | $\color{#090}{1,412,929}$ | $\color{#090}{59\\%}$ | $\color{#090}{2.98}$ | $\color{#f00}{149,040}$ | 2.12 | $\color{#f00}{396,484}$ | — | +| `xz -9` | 2,057,736 | 86% | $\color{#f00}{18,34}$ | $\color{#f00}{629,832}$ | $\color{#090}{0.38}$ | $\color{#090}{16,510}$ | — | ### Версии ПО From 43f3d15f2062594bbaf9d3421f748462de83d3ab Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月18日 13:32:27 +0300 Subject: [PATCH 108/145] Update archive_command.sh --- pg_backup/archive_command.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/archive_command.sh b/pg_backup/archive_command.sh index 921a2f0..4a29318 100644 --- a/pg_backup/archive_command.sh +++ b/pg_backup/archive_command.sh @@ -66,7 +66,7 @@ WAL_FILES_QUEUE=$(find "$ARCHIVE_STATUS_DIR" -maxdepth 1 -type f -name "*.ready" STEP=3 # чем больше WAL файлов в очереди, тем меньше степень сжатия (но больше скорость сжатия и размер сжатого файла) -# не ставьте большой уровень компрессии, это приводит к большому потреблению CPU, а экономия на размере файла несущественная +# не ставьте большой уровень компрессии, это приводит к большому потреблению CPU и памяти, а экономия на размере файла несущественная ZSTD_LEVEL=$(echo "(9 * ${STEP} - ${WAL_FILES_QUEUE}) / ${STEP}" | bc) test "$ZSTD_LEVEL" -lt 1 && ZSTD_LEVEL=1 From 8a9236675677fa6e3bad6fb8a35c97a59aa38478 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月18日 14:06:07 +0300 Subject: [PATCH 109/145] Update README.md --- pg_archive_log/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index df0cf3e..bea186e 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -5,7 +5,7 @@ [Systemd](https://en.wikipedia.org/wiki/Systemd) сервис, который запускается 1 раз в сутки: 1. удаляет файлы старше N дней 1. удаляет файлы нулевого размера старше K дней -1. архивирует несжатые файлы старше М дней, если размер файла> S килобайт +1. архивирует несжатые файлы старше М дней Команда для архивации (сжатия) файлов выполняется в один поток с самым низким приоритетом (для минимизации рисков влияния на работающую СУБД). From ed0329862ddf33f5da2e1f72e8b7bbdb0980d394 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月18日 14:06:53 +0300 Subject: [PATCH 110/145] ` -size +100k` removed --- pg_archive_log/pg_archive_log.service | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pg_archive_log/pg_archive_log.service b/pg_archive_log/pg_archive_log.service index 3244fda..e7879cc 100644 --- a/pg_archive_log/pg_archive_log.service +++ b/pg_archive_log/pg_archive_log.service @@ -19,11 +19,11 @@ ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size 0 -delete # архивируем несжатые файлы старше M дней (достаточно одного потока, т.к. файлы относительно небольшие) # выбирайте, что для вас больше подходит (zstd или bzip3): -# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size +100k ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; +# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; # bzip3 ≥ v1.5.0: -# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size +100k ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 --rm '{}' \; +# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 --rm '{}' \; # bzip3 < v1.5.0: -ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size +100k ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 '{}' \; -exec rm -f '{}' \; +ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 '{}' \; -exec rm -f '{}' \; [Install] WantedBy=multi-user.target From 23a450725bc7bda58f97eb8f9caa6dcf0a9c4ecf Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月18日 14:15:51 +0300 Subject: [PATCH 111/145] Update README.md --- pg_archive_log/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index bea186e..5e419b1 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -24,6 +24,9 @@ log_filename = 'postgresql-%Y-%m-%d.log' ## Инсталляция и настройка ```bash +# устанавливаем архиваторы +sudo dnf -y install zstd bzip3 + # создаём файлы sudo nano /etc/systemd/system/pg_archive_log.timer && \ sudo nano /etc/systemd/system/pg_archive_log.service From 9cb9a643d00572cb4d4fcaf6ff80b657df8a13a5 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月18日 14:33:24 +0300 Subject: [PATCH 112/145] Update pg_archive_log.service --- pg_archive_log/pg_archive_log.service | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pg_archive_log/pg_archive_log.service b/pg_archive_log/pg_archive_log.service index e7879cc..295d562 100644 --- a/pg_archive_log/pg_archive_log.service +++ b/pg_archive_log/pg_archive_log.service @@ -19,11 +19,11 @@ ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size 0 -delete # архивируем несжатые файлы старше M дней (достаточно одного потока, т.к. файлы относительно небольшие) # выбирайте, что для вас больше подходит (zstd или bzip3): -# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; +# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; # bzip3 ≥ v1.5.0: -# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 --rm '{}' \; +# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 --rm '{}' \; # bzip3 < v1.5.0: -ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 '{}' \; -exec rm -f '{}' \; +ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 '{}' \; -exec rm -f '{}' \; [Install] WantedBy=multi-user.target From b2d8d59eb7ead52e565ce594ce77e02d56275120 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月19日 11:18:06 +0300 Subject: [PATCH 113/145] `xz -4` added --- pg_archive_log/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index 5e419b1..cc174b7 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -68,6 +68,7 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' | `bzip3 -b8` | $\color{#090}{1,509,780}$ | $\color{#090}{63\\%}$ | $\color{#090}{2.76}$ | $\color{#090}{21,212}$ | 1.99 | $\color{#090}{52,428}$ | 1 | | `bzip3 -b16` | $\color{#090}{1,471,514}$ | $\color{#090}{62\\%}$ | $\color{#090}{2.71}$ | $\color{#090}{39,424}$ | 1.91 | 101,636 | 2 | | `bzip3 -b64` | $\color{#090}{1,412,929}$ | $\color{#090}{59\\%}$ | $\color{#090}{2.98}$ | $\color{#f00}{149,040}$ | 2.12 | $\color{#f00}{396,484}$ | — | +| `xz -4` | 2,233,640 | 91% | $\color{#f00}{7,20}$ | $\color{#090}{46,392}$ | $\color{#090}{0.33}$ | $\color{#090}{6,584}$ | — | | `xz -9` | 2,057,736 | 86% | $\color{#f00}{18,34}$ | $\color{#f00}{629,832}$ | $\color{#090}{0.38}$ | $\color{#090}{16,510}$ | — | ### Версии ПО From fd5d53e460aec64e99dbb22c479ae1898dfc05f4 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月19日 14:36:21 +0300 Subject: [PATCH 114/145] test (de)compression part 2 --- pg_archive_log/README.md | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index cc174b7..a4ac3b3 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -54,22 +54,39 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' ## Тестирование сжатия -Исходный тестовый файл: `postgresql-2025年09月14日.csv` **186,311,281 байт**.\ -Сжатие и распаковка в один поток. Распаковка в `/dev/null`.\ -Для замеров длительности выполнения и потребления памяти использовалась команда `/usr/bin/time -v COMMAND`. +Для замеров длительности выполнения и потребления памяти использовалась команда `/usr/bin/time -v COMMAND`.\ +Сжатие и распаковка в один поток. Распаковка в `/dev/null`. + +Хост `sc-inf-db-te12`, тестовый файл: `postgresql-2025年09月14日.csv` **186,311,281 байт**. | Program and compression level | Compression size (bytes) | Compression size (%) | Compression duration (s) | Compression memory (KB) | Decompression duration (s) | Decompression memory (KB) | Rating place | | :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | | `gzip -9` | $\color{#f00}{3,453,449}$ | $\color{#f00}{144\\%}$ | $\color{#090}{1.24}$ | $\color{#090}{2,416}$ | $\color{#090}{0.46}$ | $\color{#090}{2,408}$ | — | -| `zstd -9` | $\color{#f00}{2,381,965}$ | 100% | $\color{#090}{1.55}$ | $\color{#090}{41,580}$ | $\color{#090}{0.04}$ | $\color{#090}{4,220}$ | 3 | +| `zstd -9` | $\color{#f00}{2,381,965}$ | 100% | $\color{#090}{1.55}$ | $\color{#090}{41,580}$ | $\color{#090}{0.04}$ | $\color{#090}{4,220}$ | 5 | | `zstd -14` | $\color{#f00}{2,449,812}$ | $\color{#f00}{103\\%}$ | $\color{#090}{2.61}$ | 117,560 | $\color{#090}{0.04}$ | $\color{#090}{6,436}$ | — | | `zstd -19` | 1,760,829 | 74% | $\color{#f00}{77.40}$ | $\color{#f00}{216,512}$ | $\color{#090}{0.08}$ | $\color{#090}{10,444}$ | — | | `bzip2 -9` | 2,138,384 | 90% | $\color{#f00}{37.70}$ | $\color{#090}{7,944}$ | $\color{#f00}{3.34}$ | $\color{#090}{5,032}$ | — | | `bzip3 -b8` | $\color{#090}{1,509,780}$ | $\color{#090}{63\\%}$ | $\color{#090}{2.76}$ | $\color{#090}{21,212}$ | 1.99 | $\color{#090}{52,428}$ | 1 | | `bzip3 -b16` | $\color{#090}{1,471,514}$ | $\color{#090}{62\\%}$ | $\color{#090}{2.71}$ | $\color{#090}{39,424}$ | 1.91 | 101,636 | 2 | | `bzip3 -b64` | $\color{#090}{1,412,929}$ | $\color{#090}{59\\%}$ | $\color{#090}{2.98}$ | $\color{#f00}{149,040}$ | 2.12 | $\color{#f00}{396,484}$ | — | -| `xz -4` | 2,233,640 | 91% | $\color{#f00}{7,20}$ | $\color{#090}{46,392}$ | $\color{#090}{0.33}$ | $\color{#090}{6,584}$ | — | -| `xz -9` | 2,057,736 | 86% | $\color{#f00}{18,34}$ | $\color{#f00}{629,832}$ | $\color{#090}{0.38}$ | $\color{#090}{16,510}$ | — | +| `xz -2` | 2,085,424 | 85% | $\color{#090}{2.6}$ | $\color{#090}{17,684}$ | $\color{#090}{0.34}$ | $\color{#090}{4,720}$ | 3 | +| `xz -4` | 2,233,640 | 91% | $\color{#f00}{7.20}$ | $\color{#090}{46,392}$ | $\color{#090}{0.33}$ | $\color{#090}{6,584}$ | 4 | +| `xz -9` | 2,057,736 | 86% | $\color{#f00}{18.34}$ | $\color{#f00}{629,832}$ | $\color{#090}{0.38}$ | $\color{#090}{16,510}$ | — | + +Хост `sc-ek-db-pr33`, тестовый файл: `postgresql-2025年09月16日.csv` **12,670,480 байт**. + +| Program and compression level | Compression size (bytes) | Compression size (%) | Compression duration (s) | Compression memory (KB) | Decompression duration (s) | Decompression memory (KB) | Rating place | +| :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| `zstd -9` | 1,535,399 | 100% | $\color{#090}{0.31}$ | 22,576 | 0.01 | 4,600 | 5 | +| `zstd -14` | 1,486,106 | 98% | 1.02 | 65,632 | 0.02 | 6,656 | — | +| `zstd -19` | $\color{#090}{1,121,678}$ | 73% | $\color{#f00}{6.25}$ | 98,844 | 0.02 | 10,768 | — | +| `bzip3 -b8` | 1,421,130 | 93% | 1.00 | 35,944 | 0.94 | 52,200 | 1 | +| `bzip3 -b16` | 1,368,821 | 89% | 1.08 | 54,104 | 0.84 | 92,912 | 2 | +| `bzip3 -b32` | 1,368,821 | 89% | 1.07 | 53,340 | 0.85 | $\color{#f00}{158,436}$ | — | +| `xz -2` | 1,456,844 | 95% | 0.92 | 18,696 | 0.13 | 4,468 | 3 | +| `xz -3` | 1,438,932 | 94% | 1.66 | 34,164 | 0.11 | 6,544 | — | +| `xz -4` | $\color{#090}{1,096,736}$ | 71% | $\color{#f00}{2.75}$ | 50,624 | 0.13 | 6,556 | 4 | +| `xz -9` | $\color{#090}{924,236}$ | 60% | $\color{#f00}{4.24}$ | $\color{#f00}{163,292}$ | 0.11 | 14,776 | — | ### Версии ПО From d119ab23837903e4115996ada6c1077adeb110c2 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月19日 14:43:01 +0300 Subject: [PATCH 115/145] xz added --- pg_archive_log/pg_archive_log.service | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pg_archive_log/pg_archive_log.service b/pg_archive_log/pg_archive_log.service index 295d562..57bea10 100644 --- a/pg_archive_log/pg_archive_log.service +++ b/pg_archive_log/pg_archive_log.service @@ -18,12 +18,13 @@ ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +90 -delete ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size 0 -delete # архивируем несжатые файлы старше M дней (достаточно одного потока, т.к. файлы относительно небольшие) -# выбирайте, что для вас больше подходит (zstd или bzip3): -# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; +# выбирайте, что для вас больше подходит (zstd, xz, bzip3): +# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.xz' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; +# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +0 ! -size 0 ! -name '*.zst' ! -name '*.xz' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- xz -2 '{}' \; # bzip3 ≥ v1.5.0: -# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 --rm '{}' \; +# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.xz' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 --rm '{}' \; # bzip3 < v1.5.0: -ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 '{}' \; -exec rm -f '{}' \; +ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.xz' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 '{}' \; -exec rm -f '{}' \; [Install] WantedBy=multi-user.target From cecb2163f1fa48a1edc4bc6670d0e9d9f5c58547 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月19日 14:49:27 +0300 Subject: [PATCH 116/145] bzip3 -b8 -> -b16 --- pg_archive_log/pg_archive_log.service | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pg_archive_log/pg_archive_log.service b/pg_archive_log/pg_archive_log.service index 57bea10..ca21aa3 100644 --- a/pg_archive_log/pg_archive_log.service +++ b/pg_archive_log/pg_archive_log.service @@ -22,9 +22,9 @@ ExecStartPre=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 -size 0 -delete # ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.xz' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- zstd -9 -q --rm '{}' \; # ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +0 ! -size 0 ! -name '*.zst' ! -name '*.xz' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- xz -2 '{}' \; # bzip3 ≥ v1.5.0: -# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.xz' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 --rm '{}' \; +# ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.xz' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b16 --rm '{}' \; # bzip3 < v1.5.0: -ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.xz' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b8 '{}' \; -exec rm -f '{}' \; +ExecStart=find ${LOG_DIR} -maxdepth 1 -type f -mtime +1 ! -size 0 ! -name '*.zst' ! -name '*.xz' ! -name '*.bz3' -exec ionice -c2 -n7 -- nice -n19 -- bzip3 -b16 '{}' \; -exec rm -f '{}' \; [Install] WantedBy=multi-user.target From ef6bad7ffac872a409f23197b9e424e3e6e87de7 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月19日 18:46:46 +0300 Subject: [PATCH 117/145] Update README.md --- pg_archive_log/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index a4ac3b3..38dbf2a 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -25,7 +25,7 @@ log_filename = 'postgresql-%Y-%m-%d.log' ```bash # устанавливаем архиваторы -sudo dnf -y install zstd bzip3 +sudo dnf -y install zstd xz bzip3 # создаём файлы sudo nano /etc/systemd/system/pg_archive_log.timer && \ From c0a67c78648b06ad5acffcb3febfeb4a8240460b Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月20日 11:16:35 +0300 Subject: [PATCH 118/145] Update README.md --- pg_archive_log/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index 38dbf2a..959859f 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -90,9 +90,9 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' ### Версии ПО -| Program | Version | -| ------- | ------- | -| [zstd](https://github.com/facebook/zstd) | 1.4.4 | -| bzip2 | 1.0.6 | -| [bzip3](https://github.com/iczelia/bzip3) | 1.3.1 | -| xz, liblzma | 5.2.4 | +| Program | Version | Для каких целей лучше всего подходит для PostgreSQL | +| :--- | :--- | :--- | +| [zstd](https://github.com/facebook/zstd) | 1.4.4 | Создание и восстановление резервных копий, архивирование WAL файлов | +| bzip2 | 1.0.6 | — | +| [bzip3](https://github.com/iczelia/bzip3) | 1.3.1 | Архивирование log файлов | +| xz, liblzma | 5.2.4 | — | From f8e6dbb5c3a458719e935607f53e5069a0f6f3f8 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月20日 11:18:11 +0300 Subject: [PATCH 119/145] Update README.md --- pg_archive_log/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index 959859f..e34c210 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -92,7 +92,7 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' | Program | Version | Для каких целей лучше всего подходит для PostgreSQL | | :--- | :--- | :--- | -| [zstd](https://github.com/facebook/zstd) | 1.4.4 | Создание и восстановление резервных копий, архивирование WAL файлов | +| [zstd](https://github.com/facebook/zstd) | 1.4.4 | Сжатие и распаковка резервных копий и WAL файлов | | bzip2 | 1.0.6 | — | -| [bzip3](https://github.com/iczelia/bzip3) | 1.3.1 | Архивирование log файлов | +| [bzip3](https://github.com/iczelia/bzip3) | 1.3.1 | Сжатие и распаковка log файлов | | xz, liblzma | 5.2.4 | — | From a851e28c621c1ed70492759cbf2dcf1450dc13a2 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月20日 12:07:18 +0300 Subject: [PATCH 120/145] Update README.md --- pg_archive_log/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index e34c210..78f54a0 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -52,7 +52,7 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' 1. [`/etc/systemd/system/pg_archive_log.timer`](pg_archive_log.timer) 2. [`/etc/systemd/system/pg_archive_log.service`](pg_archive_log.service) -## Тестирование сжатия +## Тестирование сжатия и распаковки Для замеров длительности выполнения и потребления памяти использовалась команда `/usr/bin/time -v COMMAND`.\ Сжатие и распаковка в один поток. Распаковка в `/dev/null`. From ebd9f2613c01952e28787e52ae920973bd4b56c9 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月22日 14:00:45 +0300 Subject: [PATCH 121/145] Update restore_command.sh --- pg_backup/restore_command.sh | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pg_backup/restore_command.sh b/pg_backup/restore_command.sh index 97319f9..00b6236 100644 --- a/pg_backup/restore_command.sh +++ b/pg_backup/restore_command.sh @@ -1,25 +1,25 @@ #!/bin/bash - + # проверяем, скрипт должен запускаться с двумя параметрами test "$#" -ne 2 && echo "Error: 2 number of parameters expected, $# given">&2 && exit 2 - + FILE_SRC="/mnt/backup_db/archive_wal/cluster/1ドル" FILE_DST="2ドル" - -test -f "$FILE_SRC" && cp "$FILE_SRC" "$FILE_DST" && exit -test -f "$FILE_SRC.partial" && cp "$FILE_SRC.partial" "$FILE_DST.partial" && exit - -test -f "$FILE_SRC.lz4" && lz4 -dkf "$FILE_SRC.lz4" "$FILE_DST" && exit -test -f "$FILE_SRC.partial.lz4" && lz4 -dkf "$FILE_SRC.partial.lz4" "$FILE_DST.partial" && exit - -test -f "$FILE_SRC.zst" && zstd -dkf "$FILE_SRC.zst" -o "$FILE_DST" && exit -test -f "$FILE_SRC.partial.zst" && zstd -dkf "$FILE_SRC.partial.zst" -o "$FILE_DST.partial" && exit - + +test -f "$FILE_SRC" && (cp "$FILE_SRC" "$FILE_DST" ; exit) +test -f "$FILE_SRC.partial" && (cp "$FILE_SRC.partial" "$FILE_DST.partial" ; exit) + +test -f "$FILE_SRC.lz4" && (lz4 -dkf "$FILE_SRC.lz4" "$FILE_DST" ; exit) +test -f "$FILE_SRC.partial.lz4" && (lz4 -dkf "$FILE_SRC.partial.lz4" "$FILE_DST.partial" ; exit) + +test -f "$FILE_SRC.zst" && (zstd -dkf "$FILE_SRC.zst" -o "$FILE_DST" ; exit) +test -f "$FILE_SRC.partial.zst" && (zstd -dkf "$FILE_SRC.partial.zst" -o "$FILE_DST.partial" ; exit) + # gzip DEPRECATED -test -f "$FILE_SRC.gz" && gzip -dkc "$FILE_SRC.gz"> "$FILE_DST" && exit -test -f "$FILE_SRC.partial.gz" && gzip -dkc "$FILE_SRC.partial.gz"> "$FILE_DST.partial" && exit - +test -f "$FILE_SRC.gz" && (gzip -dkc "$FILE_SRC.gz"> "$FILE_DST" ; exit) +test -f "$FILE_SRC.partial.gz" && (gzip -dkc "$FILE_SRC.partial.gz"> "$FILE_DST.partial" ; exit) + # pg_receivewal support, https://www.postgresql.org/docs/current/app-pgreceivewal.html -test -f "$FILE_SRC.gz.partial" && gzip -dkc "$FILE_SRC.gz.partial"> "$FILE_DST.partial" && exit -test -f "$FILE_SRC.lz4.partial" && lz4 -dkf "$FILE_SRC.lz4.partial" "$FILE_DST.partial" && exit -test -f "$FILE_SRC.zst.partial" && zstd -dkf "$FILE_SRC.zst.partial" -o "$FILE_DST.partial" && exit +test -f "$FILE_SRC.gz.partial" && (gzip -dkc "$FILE_SRC.gz.partial"> "$FILE_DST.partial" ; exit) +test -f "$FILE_SRC.lz4.partial" && (lz4 -dkf "$FILE_SRC.lz4.partial" "$FILE_DST.partial" ; exit) +test -f "$FILE_SRC.zst.partial" && (zstd -dkf "$FILE_SRC.zst.partial" -o "$FILE_DST.partial" ; exit) From e99bae7f81904e386065d10f94eb3a1e95f85cd8 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月22日 19:14:03 +0300 Subject: [PATCH 122/145] Update README.md --- pg_backup/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index e427e2f..a396d95 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -8,9 +8,11 @@ 1. Восстановление резервной копии СУБД ## Требования -* GNU/Linux -* PostgreSQL ≥ v12 -* Bash ≥ 4.4 +* ОС: GNU/Linux (протестировано на RHEL 8.10) +* PostgreSQL ≥ v12: psql, pg_basebackup, pg_verifybackup, pg_checksums, pg_ctl +* Шифрование/дешифрование: gpg +* Сжатие: zstd, pigz; распаковка: zstd, pigz, lz4 +* Прочее: bash ≥ v4.4, pv; опционально: patronictl, jq ## Как это работает? From c37520c997e35aef40d94552d572a9507f9f4ff4 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月22日 19:15:11 +0300 Subject: [PATCH 123/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index a396d95..2c520c4 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -10,7 +10,7 @@ ## Требования * ОС: GNU/Linux (протестировано на RHEL 8.10) * PostgreSQL ≥ v12: psql, pg_basebackup, pg_verifybackup, pg_checksums, pg_ctl -* Шифрование/дешифрование: gpg +* Шифрование/дешифрование (опционально): gpg * Сжатие: zstd, pigz; распаковка: zstd, pigz, lz4 * Прочее: bash ≥ v4.4, pv; опционально: patronictl, jq From bcfbdb65dbbab0e9a3cf7c0eca19db24a4c824f9 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月22日 19:25:02 +0300 Subject: [PATCH 124/145] Validation: waiting for recovery done --- pg_backup/pg_backup.sh | 43 ++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/pg_backup/pg_backup.sh b/pg_backup/pg_backup.sh index 6cbef20..1917bd0 100644 --- a/pg_backup/pg_backup.sh +++ b/pg_backup/pg_backup.sh @@ -193,6 +193,7 @@ elif test "${1:-}" = "restore"; then exit 1 fi echowarn "Донастройте postgresql.conf и запустите кластер СУБД!" + echowarn "После старта СУБД дождитесь наката всех WAL файлов. Проверить завершение можно запросом select pg_is_in_recovery()" exit 0 # проверяем корректность и восстанавливаемость PostgreSQL из резервной копии @@ -229,14 +230,15 @@ elif test "${1:-}" = "validate"; then if test "$GPG_ENCRYPT" = 0; then echo "Распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_TEST_DIR'" - tar -xf $BACKUP_FILE --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR \ - 2> $LOG_FILE_PREFIX.tar.stderr.log + # посмотреть прогресс выполнения процесса pv: sudo pv -d PID + pv $BACKUP_FILE \ + | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR 2> $LOG_FILE_PREFIX.tar.stderr.log else echo "Расшифровываем и распаковываем архив '$BACKUP_FILE' в папку '$PG_DATA_TEST_DIR'" - gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $BACKUP_FILE \ - 2> $LOG_FILE_PREFIX.gpg.stderr.log \ - | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR \ - 2> $LOG_FILE_PREFIX.tar.stderr.log + # посмотреть прогресс выполнения процесса pv: sudo pv -d PID + pv $BACKUP_FILE \ + | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch 2> $LOG_FILE_PREFIX.gpg.stderr.log \ + | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR 2> $LOG_FILE_PREFIX.tar.stderr.log fi BACKUP_BASE_DIR=$(echo "$BACKUP_FILE" | grep -qP '\.pg_backup/base\.tar\.' && dirname "$BACKUP_FILE" || true) @@ -248,12 +250,14 @@ elif test "${1:-}" = "validate"; then test ! -f "$WAL_FILE" && echoerr "Файл '$WAL_FILE' не найден" && exit 1 if test "$GPG_ENCRYPT" = 0; then echo "Распаковываем архив '$WAL_FILE' в папку '$PG_DATA_TEST_DIR/pg_wal'" - tar -xf $WAL_FILE --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR/pg_wal \ - 2> $LOG_FILE_PREFIX.tar.stderr.log + pv $WAL_FILE \ + | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR/pg_wal \ + 2> $LOG_FILE_PREFIX.tar.stderr.log else echo "Расшифровываем и распаковываем архив '$WAL_FILE' в папку '$PG_DATA_TEST_DIR/pg_wal'" - gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch $WAL_FILE \ - 2> $LOG_FILE_PREFIX.gpg.stderr.log \ + pv $WAL_FILE \ + | gpg --decrypt --passphrase=$GPG_PASSPHRASE --batch \ + 2> $LOG_FILE_PREFIX.gpg.stderr.log \ | tar -xf - --use-compress-program="$COMPRESS_PROGRAM" --directory=$PG_DATA_TEST_DIR/pg_wal \ 2> $LOG_FILE_PREFIX.tar.stderr.log fi @@ -288,6 +292,16 @@ elif test "${1:-}" = "validate"; then # ВНИМАНИЕ! После старта тестовой СУБД завершать работу скрипта с ошибкой нельзя до остановки СУБД! + echo "Ждём, пока накатятся WAL файлы" + while true; do + PG_IS_IN_RECOVERY=$(psql --port=$PG_PORT --username=postgres --no-password --dbname=postgres --quiet --no-psqlrc \ + --pset=null=¤ --tuples-only --no-align --command="select pg_is_in_recovery()") + test -z "$PG_IS_IN_RECOVERY" && echowarn "pg_backup validate: get in recovery status error" && break + test "$PG_IS_IN_RECOVERY" = "f" && echo && break + sleep 1 + # echo -n "." # debug only + done + echo "Проверяем количество ошибок в контрольных суммах" CHECKSUM_FAILURES=$(psql --port=$PG_PORT --username=postgres --no-password --dbname=postgres --quiet --no-psqlrc \ --pset=null=¤ --tuples-only --no-align --command='select sum(checksum_failures) from pg_stat_database' \ @@ -321,9 +335,10 @@ elif test "${1:-}" = "validate"; then 2> $LOG_FILE_PREFIX.pg_ctl.stderr.log echoinfo "pg_backup validate: server stopped (port $PG_PORT)" + BAD_WORDS_RE="\b(WARNING|ERROR|FATAL|PANIC|ignored?|(fail|(? ${WAL_DIR}/pg_archivecleanup.stderr.log From 477a45fa3f6df171cc8ce4218b2aea534b040361 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月22日 20:33:03 +0300 Subject: [PATCH 125/145] Update README.md --- pg_archive_log/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index 78f54a0..a8fef56 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -90,9 +90,9 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' ### Версии ПО -| Program | Version | Для каких целей лучше всего подходит для PostgreSQL | -| :--- | :--- | :--- | -| [zstd](https://github.com/facebook/zstd) | 1.4.4 | Сжатие и распаковка резервных копий и WAL файлов | -| bzip2 | 1.0.6 | — | -| [bzip3](https://github.com/iczelia/bzip3) | 1.3.1 | Сжатие и распаковка log файлов | -| xz, liblzma | 5.2.4 | — | +| Program | Version | Для каких целей лучше всего подходит (PostgreSQL) | Почему лучше всего подходит | +| :--- | :--- | :--- | :--- | +| [zstd](https://github.com/facebook/zstd) | 1.4.4 | Сжатие и распаковка резервных копий и WAL файлов | Важна скорость сжатия, особенно на больших размерах СУБД. Ещё важнее скорость распаковки для минимизации RTO. | +| bzip2 | 1.0.6 | — | | +| [bzip3](https://github.com/iczelia/bzip3) | 1.3.1 | Сжатие и распаковка log файлов | Степень сжатия немного важнее, чем скорость сжатия и распаковки. | +| xz, liblzma | 5.2.4 | — | | From 09d361af843617dd68910ec3e9ede78c40f6116a Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月22日 20:37:59 +0300 Subject: [PATCH 126/145] Update README.md --- pg_archive_log/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index a8fef56..7297933 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -94,5 +94,5 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' | :--- | :--- | :--- | :--- | | [zstd](https://github.com/facebook/zstd) | 1.4.4 | Сжатие и распаковка резервных копий и WAL файлов | Важна скорость сжатия, особенно на больших размерах СУБД. Ещё важнее скорость распаковки для минимизации RTO. | | bzip2 | 1.0.6 | — | | -| [bzip3](https://github.com/iczelia/bzip3) | 1.3.1 | Сжатие и распаковка log файлов | Степень сжатия немного важнее, чем скорость сжатия и распаковки. | +| [bzip3](https://github.com/iczelia/bzip3) | 1.3.1 | Сжатие и распаковка log файлов | Файлы за каждые сутки относительно небольшие, примерно одинакового размера. Степень сжатия немного важнее, чем скорость сжатия и распаковки. | | xz, liblzma | 5.2.4 | — | | From bec90ce262b7a64d1c101b46b959073cce78f79c Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月23日 22:26:20 +0300 Subject: [PATCH 127/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index 2c520c4..66579bc 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -1,4 +1,4 @@ -# Инсталляция сервиса резервного копирования PostgreSQL +# 🌳 Инсталляция сервиса резервного копирования PostgreSQL ## Функциональность 1. Создание полной резервной копии СУБД From 9e6e2b069637ea6e8c412e906f908ff87d1aafd2 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月23日 22:27:04 +0300 Subject: [PATCH 128/145] Update README.md --- pg_archive_log/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index 7297933..0729f78 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -1,4 +1,4 @@ -# Инсталляция сервиса архивирования log файлов PostgreSQL +# 🍁 Инсталляция сервиса архивирования log файлов PostgreSQL ## Описание From b39c3159b1d903ea66d6d5a144b1ee7ef5b302fd Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月24日 14:16:06 +0300 Subject: [PATCH 129/145] Update README.md --- pg_backup/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/README.md b/pg_backup/README.md index 66579bc..08d02fd 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -9,7 +9,7 @@ ## Требования * ОС: GNU/Linux (протестировано на RHEL 8.10) -* PostgreSQL ≥ v12: psql, pg_basebackup, pg_verifybackup, pg_checksums, pg_ctl +* PostgreSQL ≥ v12: psql, pg_basebackup, pg_verifybackup, pg_checksums, pg_ctl, pg_amcheck (опционально) * Шифрование/дешифрование (опционально): gpg * Сжатие: zstd, pigz; распаковка: zstd, pigz, lz4 * Прочее: bash ≥ v4.4, pv; опционально: patronictl, jq From d9b5c6d8f03ded26e9b144ed1a6cc1293fa9e9d3 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月24日 15:30:57 +0300 Subject: [PATCH 130/145] Update README.md --- pg_receivewal/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_receivewal/README.md b/pg_receivewal/README.md index 855583b..d6f417a 100644 --- a/pg_receivewal/README.md +++ b/pg_receivewal/README.md @@ -1,4 +1,4 @@ -# Инсталляция сервиса архивирования WAL файлов PostgreSQL +# 🍃 Инсталляция сервиса архивирования WAL файлов PostgreSQL ## Введение From 9459293693410b7bb8c7bcd752b3d81ec362742d Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月25日 19:58:21 +0300 Subject: [PATCH 131/145] Create mount_example.md --- pg_backup/mount_example.md | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 pg_backup/mount_example.md diff --git a/pg_backup/mount_example.md b/pg_backup/mount_example.md new file mode 100644 index 0000000..6d8aaad --- /dev/null +++ b/pg_backup/mount_example.md @@ -0,0 +1,52 @@ +# Монтирование сетевой папки /mnt/backup_db (на примере) + +```bash +# хотим на сервере sp-ek-db-pr02 сделать папку, как на сервере sp-ek-db-pr03 +root@sp-ek-db-pr03 ~ $ df -H +... +//sp-bkp-bpr-pr03.cplsb.ru/Backup_DB/EK 80T 24T 56T 31% /mnt/backup_db +... + +# копируем содержимое файла в буфер обмена 1 +root@sp-ek-db-pr03 ~ $ cat ~/.smbclient +username=srv_bpk_ek +password=*censored* +domain=cplsb.ru + +# копируем строку из файла в буфер обмена 2 +root@sp-ek-db-pr03 ~ $ cat /etc/fstab +... +# backup DB +//sp-bkp-bpr-pr03.cplsb.ru/Backup_DB/EK /mnt/backup_db cifs user,rw,credentials=/root/.smbclient,dir_mode=0750,file_mode=0640,uid=postgres,gid=postgres,nofail 0 0 +... + +#---------------------------------------------------------------------------------------------------------------------------- + +# инсталлируем +root@sp-ek-db-pr02 ~ $ dnf -y install cifs-utils + +# создаём папку +root@sp-ek-db-pr02 ~ $ mkdir -p /mnt/backup_db && chmod 770 /mnt/backup_db && chown postgres:postgres /mnt/backup_db + +# создаём файл из буфера обмена 1 +root@sp-ek-db-pr02 ~ $ nano -c ~/.smbclient && chmod 600 ~/.smbclient + +# добавляем строку из буфера обмена 2 +root@sp-ek-db-pr02 ~ $ nano -c /etc/fstab + +# автоматическое монтирование +root@sp-ek-db-pr02 ~ $ mount -a && systemctl daemon-reload + +# ручное монтирование без /etc/fstab (при необходимости) +# root@sp-ek-db-pr02 ~ $ mount.cifs //sp-bkp-bpr-pr03.cplsb.ru/Backup_DB/EK /mnt/backup_db -o user,rw,credentials=/root/.smbclient,dir_mode=0750,file_mode=0640,uid=postgres,gid=postgres,nofail + +# если будет ошибка, то смотрим системный журнал +root@sp-ek-db-pr02 ~ $ tail -n20 /var/log/messages + +# если нет доступов, проверяем доступность портов +# root@sp-ek-db-pr02 ~ $ nmap sp-bkp-bpr-pr03 -p 139 # DEPRECATED +root@sp-ek-db-pr02 ~ $ nmap sp-bkp-bpr-pr03 -p 445 + +# как отмонтировать, при необходимости +root@sp-ek-db-pr02 ~ $ umount /mnt/backup_db +``` From d454265febf663b9a5e7948ab53e094b9c3c1d56 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月25日 20:00:15 +0300 Subject: [PATCH 132/145] mount_example link --- pg_backup/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/pg_backup/README.md b/pg_backup/README.md index 08d02fd..e99cad2 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -150,5 +150,6 @@ systemctl list-timers | grep -P 'NEXT|pg_backup' 1. [`/var/lib/pgsql/restore_command.sh`](restore_command.sh) ## Ссылки по теме +* [Монтирование сетевой папки /mnt/backup_db (на примере)](mount_example.md) * [PostgreSQL: копирование WAL файлов в архив (archive_command)](archive_command.md) * [PostgreSQL: восстановление WAL файлов из архива (restore_command)](restore_command.md) From de0b2eb4ba90b455c92249a8960b9b8f1b0e7638 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月25日 20:02:13 +0300 Subject: [PATCH 133/145] who mount --- pg_backup/mount_example.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pg_backup/mount_example.md b/pg_backup/mount_example.md index 6d8aaad..fb9011c 100644 --- a/pg_backup/mount_example.md +++ b/pg_backup/mount_example.md @@ -1,5 +1,8 @@ # Монтирование сетевой папки /mnt/backup_db (на примере) +> [!NOTE] +> Сетевую папку обычно монтируют системные администраторы, а не DBA. + ```bash # хотим на сервере sp-ek-db-pr02 сделать папку, как на сервере sp-ek-db-pr03 root@sp-ek-db-pr03 ~ $ df -H From 950c2a789f2ff958c0779e924f9abe5840225cbf Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月25日 23:58:05 +0300 Subject: [PATCH 134/145] Create role_model.sql --- dev/role_model.sql | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 dev/role_model.sql diff --git a/dev/role_model.sql b/dev/role_model.sql new file mode 100644 index 0000000..e71e22b --- /dev/null +++ b/dev/role_model.sql @@ -0,0 +1,89 @@ +-- группы для членства ТУЗ и ПУЗ +CREATE ROLE group_personal; COMMENT ON ROLE group_personal IS 'Группа для персональных УЗ (сотрудников)'; +CREATE ROLE group_patroni; COMMENT ON ROLE group_patroni IS 'Группа для технических УЗ Patroni'; +CREATE ROLE group_application; COMMENT ON ROLE group_application IS 'Группа для технических УЗ приложения'; + +-- группы для наделения их привилегиями +CREATE ROLE group_read; -- SELECT +CREATE ROLE group_write; -- DML (INSERT, UPDATE, DELETE, TRUNCATE, MERGE), TCL (COMMIT, ROLLBACK, SAVEPOINT) +CREATE ROLE group_read_write; -- group_read + group_write +CREATE ROLE group_deploy; -- DDL (CREATE, ALTER, DROP) + group_read_write +CREATE ROLE group_permission; -- DCL (REVOKE, GRANT) +CREATE ROLE group_admin; -- администрирование СУБД без SELECT, DML, TCL, DCL +CREATE ROLE group_audit; -- чтение всех настроек СУБД + +-- группы с наследованием привилегий +GRANT group_read, group_write TO group_read_write /*WITH SET FALSE*/; +GRANT group_read_write TO group_deploy /*WITH SET FALSE*/; + +--GRANT pg_read_all_data TO group_read; +--GRANT pg_write_all_data TO group_write; + +-- пользователи ТУЗ +CREATE USER app_read; +CREATE USER app_write; +CREATE USER app_read_write; +CREATE USER app_deploy; + +-- пользователи ПУЗ +CREATE USER dba_rhmukhtarov WITH SUPERUSER; +COMMENT ON ROLE dba_rhmukhtarov IS 'DBA'; + +CREATE USER sup_petrov; +COMMENT ON ROLE sup_petrov IS 'Прикладное сопровождение АС (support)'; + +-- объединяем ТУЗ приложения в группу +GRANT group_application TO app_read; +GRANT group_application TO app_write; +GRANT group_application TO app_read_write; +GRANT group_application TO app_deploy; + +-- объединяем ПУЗ в группу +GRANT group_personal TO dba_rhmukhtarov; +GRANT group_personal TO sup_petrov; + +-- раздаём привилегии ТУЗ +GRANT group_read TO app_read; +GRANT group_write TO app_write; +GRANT group_read_write TO app_read_write; +GRANT group_deploy TO app_deploy; +alter +--ALTER USER dba_rhmukhtarov SET statement_timeout = '6h'; +--ALTER USER dba_rhmukhtarov SET log_min_duration_statement = 0; +--ALTER USER dba_rhmukhtarov set log_duration = 1; + +CREATE DATABASE app_db WITH OWNER app_deploy; + +\connect app_db + +-- посмотреть список привилегий для текущей БД +--select * FROM information_schema.table_privileges + +-- отнимаем привилегии у роли PUBLIC для уже созданных объектов в текущей БД +REVOKE ALL /*CREATE, CONNECT, TEMPORARY*/ ON DATABASE app_db FROM PUBLIC; +REVOKE ALL /*CREATE, USAGE*/ ON SCHEMA public FROM PUBLIC; +REVOKE ALL /*CREATE, USAGE*/ ON SCHEMA pg_catalog FROM PUBLIC; +REVOKE ALL /*CREATE, USAGE*/ ON SCHEMA information_schema FROM PUBLIC; + +-- отнимаем привилегии у роли PUBLIC для будущих создаваемых объектов в текущей БД +ALTER DEFAULT PRIVILEGES REVOKE ALL ON tables FROM PUBLIC; +ALTER DEFAULT PRIVILEGES REVOKE ALL ON sequences FROM PUBLIC; +ALTER DEFAULT PRIVILEGES REVOKE ALL ON routines FROM PUBLIC; +ALTER DEFAULT PRIVILEGES REVOKE ALL ON types FROM PUBLIC; +ALTER DEFAULT PRIVILEGES REVOKE ALL ON schemas FROM PUBLIC; + +GRANT CONNECT ON DATABASE app_db TO group_application; --не работает для группы + +/* + Для смены пароля нельзя использовать команду ALTER USER user_name WITH PASSWORD 'new_password'; + Т.к. пароль сохранится в журнале на сервере СУБД. Правильные клиенты передают хеш пароля. + Поменять пароль можно в любом GUI клиенте, например в DBeaver. + Для psql подключитесь к СУБД, затем введите команду \password +*/ +\password app_read; +\password app_write; +\password app_read_write; +\password app_deploy; +\password dba_rhmukhtarov; +\password sup_petrov; + From 4af81bb178bdb4eb36a8baf2962f5639d5228ce7 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月26日 00:00:32 +0300 Subject: [PATCH 135/145] Create README.md --- dev/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 dev/README.md diff --git a/dev/README.md b/dev/README.md new file mode 100644 index 0000000..348df10 --- /dev/null +++ b/dev/README.md @@ -0,0 +1,3 @@ +# In development + +В этой папке находятся файлы в разработке, идеи, черновые наброски и т.д. From 72a8082d65cb7104d42972df6beb84073be33853 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月26日 00:01:04 +0300 Subject: [PATCH 136/145] Rename TODO.md to dev/TODO.md --- TODO.md => dev/TODO.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename TODO.md => dev/TODO.md (100%) diff --git a/TODO.md b/dev/TODO.md similarity index 100% rename from TODO.md rename to dev/TODO.md From f8e51ecbccdf3c8d819a7049d9695920cbd3604f Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月26日 15:47:34 +0300 Subject: [PATCH 137/145] Update TODO.md --- psqlrc/TODO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/psqlrc/TODO.md b/psqlrc/TODO.md index dc97169..82da75c 100644 --- a/psqlrc/TODO.md +++ b/psqlrc/TODO.md @@ -1,5 +1,7 @@ # TODO list +Ошмётки в консоли https://bolknote.ru/all/oshmyotki-v-konsoli/ + Get some ideas from * https://github.com/zalando/pg_view * https://github.com/lesovsky/pgcenter/tree/master/internal/query ; https://habr.com/ru/articles/494162/ From 96f82170be3fed0065acc660c70f4d460ef3870a Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月26日 18:05:08 +0300 Subject: [PATCH 138/145] Update mount_example.md --- pg_backup/mount_example.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pg_backup/mount_example.md b/pg_backup/mount_example.md index fb9011c..93a8aaa 100644 --- a/pg_backup/mount_example.md +++ b/pg_backup/mount_example.md @@ -4,52 +4,52 @@ > Сетевую папку обычно монтируют системные администраторы, а не DBA. ```bash -# хотим на сервере sp-ek-db-pr02 сделать папку, как на сервере sp-ek-db-pr03 -root@sp-ek-db-pr03 ~ $ df -H +# хотим на сервере srv2 сделать папку, как на сервере srv3 +root@srv3 ~ $ df -H ... -//sp-bkp-bpr-pr03.cplsb.ru/Backup_DB/EK 80T 24T 56T 31% /mnt/backup_db +//srv-bkp/Backup_DB/EK 80T 24T 56T 31% /mnt/backup_db ... # копируем содержимое файла в буфер обмена 1 -root@sp-ek-db-pr03 ~ $ cat ~/.smbclient +root@srv3 ~ $ cat ~/.smbclient username=srv_bpk_ek password=*censored* domain=cplsb.ru # копируем строку из файла в буфер обмена 2 -root@sp-ek-db-pr03 ~ $ cat /etc/fstab +root@srv3 ~ $ cat /etc/fstab ... # backup DB -//sp-bkp-bpr-pr03.cplsb.ru/Backup_DB/EK /mnt/backup_db cifs user,rw,credentials=/root/.smbclient,dir_mode=0750,file_mode=0640,uid=postgres,gid=postgres,nofail 0 0 +//srv-bkp/Backup_DB/EK /mnt/backup_db cifs user,rw,credentials=/root/.smbclient,dir_mode=0750,file_mode=0640,uid=postgres,gid=postgres,nofail 0 0 ... #---------------------------------------------------------------------------------------------------------------------------- # инсталлируем -root@sp-ek-db-pr02 ~ $ dnf -y install cifs-utils +root@srv2 ~ $ dnf -y install cifs-utils # создаём папку -root@sp-ek-db-pr02 ~ $ mkdir -p /mnt/backup_db && chmod 770 /mnt/backup_db && chown postgres:postgres /mnt/backup_db +root@srv2 ~ $ mkdir -p /mnt/backup_db && chmod 770 /mnt/backup_db && chown postgres:postgres /mnt/backup_db # создаём файл из буфера обмена 1 -root@sp-ek-db-pr02 ~ $ nano -c ~/.smbclient && chmod 600 ~/.smbclient +root@srv2 ~ $ nano -c ~/.smbclient && chmod 600 ~/.smbclient # добавляем строку из буфера обмена 2 -root@sp-ek-db-pr02 ~ $ nano -c /etc/fstab +root@srv2 ~ $ nano -c /etc/fstab # автоматическое монтирование -root@sp-ek-db-pr02 ~ $ mount -a && systemctl daemon-reload +root@srv2 ~ $ mount -a && systemctl daemon-reload # ручное монтирование без /etc/fstab (при необходимости) -# root@sp-ek-db-pr02 ~ $ mount.cifs //sp-bkp-bpr-pr03.cplsb.ru/Backup_DB/EK /mnt/backup_db -o user,rw,credentials=/root/.smbclient,dir_mode=0750,file_mode=0640,uid=postgres,gid=postgres,nofail +# root@srv2 ~ $ mount.cifs //srv-bkp/Backup_DB/EK /mnt/backup_db -o user,rw,credentials=/root/.smbclient,dir_mode=0750,file_mode=0640,uid=postgres,gid=postgres,nofail # если будет ошибка, то смотрим системный журнал -root@sp-ek-db-pr02 ~ $ tail -n20 /var/log/messages +root@srv2 ~ $ tail -n20 /var/log/messages # если нет доступов, проверяем доступность портов -# root@sp-ek-db-pr02 ~ $ nmap sp-bkp-bpr-pr03 -p 139 # DEPRECATED -root@sp-ek-db-pr02 ~ $ nmap sp-bkp-bpr-pr03 -p 445 +# root@srv2 ~ $ nmap sp-bkp-bpr-pr03 -p 139 # DEPRECATED +root@srv2 ~ $ nmap sp-bkp-bpr-pr03 -p 445 # как отмонтировать, при необходимости -root@sp-ek-db-pr02 ~ $ umount /mnt/backup_db +root@srv2 ~ $ umount /mnt/backup_db ``` From b1fdaa3db230aba5155a950fa0e4693d1329e84a Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月26日 18:12:21 +0300 Subject: [PATCH 139/145] Update mount_example.md --- pg_backup/mount_example.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg_backup/mount_example.md b/pg_backup/mount_example.md index 93a8aaa..5f0fd50 100644 --- a/pg_backup/mount_example.md +++ b/pg_backup/mount_example.md @@ -14,7 +14,7 @@ root@srv3 ~ $ df -H root@srv3 ~ $ cat ~/.smbclient username=srv_bpk_ek password=*censored* -domain=cplsb.ru +domain=some.com # копируем строку из файла в буфер обмена 2 root@srv3 ~ $ cat /etc/fstab From d5e139a38e880baed56dc942ad263d434484db23 Mon Sep 17 00:00:00 2001 From: Rinat Mukhtarov Date: 2025年9月27日 14:23:23 +0300 Subject: [PATCH 140/145] host name reduced --- pg_archive_log/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index 0729f78..c256c3e 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -57,7 +57,7 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' Для замеров длительности выполнения и потребления памяти использовалась команда `/usr/bin/time -v COMMAND`.\ Сжатие и распаковка в один поток. Распаковка в `/dev/null`. -Хост `sc-inf-db-te12`, тестовый файл: `postgresql-2025年09月14日.csv` **186,311,281 байт**. +Хост `db-te12`, тестовый файл: `postgresql-2025年09月14日.csv` **186,311,281 байт**. | Program and compression level | Compression size (bytes) | Compression size (%) | Compression duration (s) | Compression memory (KB) | Decompression duration (s) | Decompression memory (KB) | Rating place | | :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | @@ -73,7 +73,7 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' | `xz -4` | 2,233,640 | 91% | $\color{#f00}{7.20}$ | $\color{#090}{46,392}$ | $\color{#090}{0.33}$ | $\color{#090}{6,584}$ | 4 | | `xz -9` | 2,057,736 | 86% | $\color{#f00}{18.34}$ | $\color{#f00}{629,832}$ | $\color{#090}{0.38}$ | $\color{#090}{16,510}$ | — | -Хост `sc-ek-db-pr33`, тестовый файл: `postgresql-2025年09月16日.csv` **12,670,480 байт**. +Хост `db-pr33`, тестовый файл: `postgresql-2025年09月16日.csv` **12,670,480 байт**. | Program and compression level | Compression size (bytes) | Compression size (%) | Compression duration (s) | Compression memory (KB) | Decompression duration (s) | Decompression memory (KB) | Rating place | | :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | From 41aea748693cd74a4d90197b81a62f024079fc23 Mon Sep 17 00:00:00 2001 From: Rinat Mukhtarov Date: 2025年9月27日 14:24:33 +0300 Subject: [PATCH 141/145] tabs --- pg_archive_log/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pg_archive_log/README.md b/pg_archive_log/README.md index c256c3e..51ad5e4 100644 --- a/pg_archive_log/README.md +++ b/pg_archive_log/README.md @@ -77,16 +77,16 @@ systemctl list-timers | grep -P 'NEXT|pg_archive_log' | Program and compression level | Compression size (bytes) | Compression size (%) | Compression duration (s) | Compression memory (KB) | Decompression duration (s) | Decompression memory (KB) | Rating place | | :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| `zstd -9` | 1,535,399 | 100% | $\color{#090}{0.31}$ | 22,576 | 0.01 | 4,600 | 5 | -| `zstd -14` | 1,486,106 | 98% | 1.02 | 65,632 | 0.02 | 6,656 | — | -| `zstd -19` | $\color{#090}{1,121,678}$ | 73% | $\color{#f00}{6.25}$ | 98,844 | 0.02 | 10,768 | — | -| `bzip3 -b8` | 1,421,130 | 93% | 1.00 | 35,944 | 0.94 | 52,200 | 1 | +| `zstd -9` | 1,535,399 | 100% | $\color{#090}{0.31}$ | 22,576 | 0.01 | 4,600 | 5 | +| `zstd -14` | 1,486,106 | 98% | 1.02 | 65,632 | 0.02 | 6,656 | — | +| `zstd -19` | $\color{#090}{1,121,678}$ | 73% | $\color{#f00}{6.25}$ | 98,844 | 0.02 | 10,768 | — | +| `bzip3 -b8` | 1,421,130 | 93% | 1.00 | 35,944 | 0.94 | 52,200 | 1 | | `bzip3 -b16` | 1,368,821 | 89% | 1.08 | 54,104 | 0.84 | 92,912 | 2 | | `bzip3 -b32` | 1,368,821 | 89% | 1.07 | 53,340 | 0.85 | $\color{#f00}{158,436}$ | — | -| `xz -2` | 1,456,844 | 95% | 0.92 | 18,696 | 0.13 | 4,468 | 3 | -| `xz -3` | 1,438,932 | 94% | 1.66 | 34,164 | 0.11 | 6,544 | — | -| `xz -4` | $\color{#090}{1,096,736}$ | 71% | $\color{#f00}{2.75}$ | 50,624 | 0.13 | 6,556 | 4 | -| `xz -9` | $\color{#090}{924,236}$ | 60% | $\color{#f00}{4.24}$ | $\color{#f00}{163,292}$ | 0.11 | 14,776 | — | +| `xz -2` | 1,456,844 | 95% | 0.92 | 18,696 | 0.13 | 4,468 | 3 | +| `xz -3` | 1,438,932 | 94% | 1.66 | 34,164 | 0.11 | 6,544 | — | +| `xz -4` | $\color{#090}{1,096,736}$ | 71% | $\color{#f00}{2.75}$ | 50,624 | 0.13 | 6,556 | 4 | +| `xz -9` | $\color{#090}{924,236}$ | 60% | $\color{#f00}{4.24}$ | $\color{#f00}{163,292}$ | 0.11 | 14,776 | — | ### Версии ПО From 374a2fd445339c43bff35327ce05217062c0ac26 Mon Sep 17 00:00:00 2001 From: Rinat Date: 2025年9月30日 20:36:49 +0300 Subject: [PATCH 142/145] Create TODO.md --- pg_backup/TODO.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 pg_backup/TODO.md diff --git a/pg_backup/TODO.md b/pg_backup/TODO.md new file mode 100644 index 0000000..d7ce7be --- /dev/null +++ b/pg_backup/TODO.md @@ -0,0 +1,7 @@ +# TODO + +Подумать над приоритетом выбора сервера, с которого делать бекап из-за этого комментария Димы Бородина: + +https://habr.com/ru/articles/506610/#comment_21736832 \ +Вот есть у нас кластер, типа patroni. В нём есть primary, и какие-то реплики. +Сейчас у нас в питонячем скрипте реплики координируются через ЗК чтобы выбрать какой из узлов снимает бекап: в последнюю очередь с primary, но лучше с реплики. Из реплик лучше выбирать не ту что в syncronous_standby_names. Среди прочих нужно выбрать реплику с максимальным LSN. Нетривиально, да? From 7022b959bb6a9bf02c222e6a64aea65cf5eace5d Mon Sep 17 00:00:00 2001 From: Rinat Date: Wed, 1 Oct 2025 13:00:25 +0300 Subject: [PATCH 143/145] Update TODO.md --- pg_backup/TODO.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pg_backup/TODO.md b/pg_backup/TODO.md index d7ce7be..659b386 100644 --- a/pg_backup/TODO.md +++ b/pg_backup/TODO.md @@ -1,7 +1,11 @@ # TODO -Подумать над приоритетом выбора сервера, с которого делать бекап из-за этого комментария Димы Бородина: - -https://habr.com/ru/articles/506610/#comment_21736832 \ -Вот есть у нас кластер, типа patroni. В нём есть primary, и какие-то реплики. -Сейчас у нас в питонячем скрипте реплики координируются через ЗК чтобы выбрать какой из узлов снимает бекап: в последнюю очередь с primary, но лучше с реплики. Из реплик лучше выбирать не ту что в syncronous_standby_names. Среди прочих нужно выбрать реплику с максимальным LSN. Нетривиально, да? +1. Подумать над приоритетом выбора сервера, с которого делать бекап из-за этого комментария Димы Бородина: \ + https://habr.com/ru/articles/506610/#comment_21736832 \ + Вот есть у нас кластер, типа patroni. В нём есть primary, и какие-то реплики. + Сейчас у нас в питонячем скрипте реплики координируются через ЗК чтобы выбрать какой из узлов снимает бекап: в последнюю очередь с primary, но лучше с реплики. Из реплик лучше выбирать не ту что в syncronous_standby_names. Среди прочих нужно выбрать реплику с максимальным LSN. Нетривиально, да? +2. В функции валидации корректности восстанавливаемости СУБД из рез. копии после выполнения pg_controldata добавить проверку, что СУБД корректно завершила работу + ``` + pg_controldata | grep state + Database cluster state: shut down + ``` From 15ef2adb7696b70b820411e159379e8a9ab7af48 Mon Sep 17 00:00:00 2001 From: Rinat Date: Wed, 1 Oct 2025 13:05:13 +0300 Subject: [PATCH 144/145] Update TODO.md --- pg_backup/TODO.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pg_backup/TODO.md b/pg_backup/TODO.md index 659b386..344507d 100644 --- a/pg_backup/TODO.md +++ b/pg_backup/TODO.md @@ -1,11 +1,17 @@ # TODO -1. Подумать над приоритетом выбора сервера, с которого делать бекап из-за этого комментария Димы Бородина: \ - https://habr.com/ru/articles/506610/#comment_21736832 \ - Вот есть у нас кластер, типа patroni. В нём есть primary, и какие-то реплики. - Сейчас у нас в питонячем скрипте реплики координируются через ЗК чтобы выбрать какой из узлов снимает бекап: в последнюю очередь с primary, но лучше с реплики. Из реплик лучше выбирать не ту что в syncronous_standby_names. Среди прочих нужно выбрать реплику с максимальным LSN. Нетривиально, да? -2. В функции валидации корректности восстанавливаемости СУБД из рез. копии после выполнения pg_controldata добавить проверку, что СУБД корректно завершила работу +1. Сделать проверку pg_checksums опциональной (добавить настройку в конфигурационный файл?), потому что pg_basebackup при создании рез. копий уже проверяет контрольные суммы, если они включены. + В gpg в командной строке вместо пароля (--passphrase) использовать чтение из файла (--passphrase_file) pg_gpg_passphrase. + Вместо команды `ionice -c2 -n7 -- nice -n19 --` использовать соответствующие настройки Systemd? \ + https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html \ + https://unix.stackexchange.com/questions/788554/set-highest-cpu-and-io-priority-for-a-systemd-service (пример) +1. В функции валидации корректности восстанавливаемости СУБД из рез. копии после выполнения pg_controldata добавить проверку, что СУБД корректно завершила работу ``` pg_controldata | grep state Database cluster state: shut down ``` +1. Подумать над приоритетом выбора сервера, с которого делать бекап из-за этого комментария Димы Бородина: \ + https://habr.com/ru/articles/506610/#comment_21736832 \ + Вот есть у нас кластер, типа patroni. В нём есть primary, и какие-то реплики. + Сейчас у нас в питонячем скрипте реплики координируются через ЗК чтобы выбрать какой из узлов снимает бекап: в последнюю очередь с primary, но лучше с реплики. + Из реплик лучше выбирать не ту что в syncronous_standby_names. Среди прочих нужно выбрать реплику с максимальным LSN. Нетривиально, да? From bc61dd1d8dc7ff3b72c979392dcea5950f441983 Mon Sep 17 00:00:00 2001 From: Rinat Date: Wed, 1 Oct 2025 13:21:39 +0300 Subject: [PATCH 145/145] links added --- pg_backup/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pg_backup/README.md b/pg_backup/README.md index e99cad2..549ed3d 100644 --- a/pg_backup/README.md +++ b/pg_backup/README.md @@ -153,3 +153,9 @@ systemctl list-timers | grep -P 'NEXT|pg_backup' * [Монтирование сетевой папки /mnt/backup_db (на примере)](mount_example.md) * [PostgreSQL: копирование WAL файлов в архив (archive_command)](archive_command.md) * [PostgreSQL: восстановление WAL файлов из архива (restore_command)](restore_command.md) +* Systemd + * https://systemd-by-example.com/ + * Google: [crontab+vs+systemd+timer](https://www.google.com/search?q=crontab+vs+systemd+timer) + * [Stop using cron! Systemd Timers Explained](https://coady.tech/systemd-timer-vs-cron/) +* https://www.dmosk.ru/miniinstruktions.php?mini=linux-cifs +* https://chmod-calculator.com/

AltStyle によって変換されたページ (->オリジナル) /