Резервное копирование и восстановление БД#

Резервное копирование базы данных выполняется штатными средствами PostgreSQL / Postgres Pro и не требует остановки работы СУБД. Для логического резервного копирования используется утилита pg_dump, для восстановления — pg_restore. Дамп снимается локально на сервере СУБД, архивируется и переносится во внешнее хранилище. Такой подход снижает нагрузку на сеть и позволяет сохранить резервную копию независимо от состояния основного сервера БД.

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

Global ERP совместима со сценариями онлайн-бэкапа Postgres Pro, WAL-архивации и восстановления на момент времени (PITR). Эти механизмы реализуются средствами СУБД и позволяют выполнять резервное копирование без остановки работы базы данных. Базовая физическая копия используется совместно с непрерывной архивацией WAL: при восстановлении СУБД применяет сохранённые WAL-сегменты и возвращает базу к выбранному согласованному состоянию.

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

Для БД большого размера, ориентировочно свыше 1,5 ТБ, регулярное снятие полного логического дампа с продуктивного экземпляра не используется как основной механизм защиты. В таких контурах применяются реплики БД в рамках одного или нескольких ЦОД, потоковая репликация, WAL-архивирование и физические резервные копии. Резервные операции выполняются с учётом нагрузки на продуктивный контур и не требуют остановки работы ИС.

Резервная копия используется для восстановления работоспособности ИС при логическом разрушении данных, ошибочном изменении объектов схемы, повреждении данных приложения, а также при физическом разрушении исходного экземпляра PostgreSQL. В случае физического разрушения БД восстановление выполняется на новом или восстановленном экземпляре СУБД из ранее сохранённой резервной копии.

Возможно применение сторонних средств резервного копирования, таких как Кибер Бэкап для PostgreSQL, если они обеспечивают согласованное состояние файлов БД, корректную обработку WAL-журналов и совместимы с выбранной схемой эксплуатации PostgreSQL / Postgres Pro.

Снятие дампа#

pgdump

Снятие дампа выполняется штатной утилитой pg_dump.

Для бекапа снимаем только схему public, ее достаточно для сохранения всех данных системы. Отдельно снимаем структуру всех объектов схемы aud. Это позволяет при восстановлении не пересоздавать структуры хранения аудита из системы Global.

Если требуется сохранить аудит, то схему aud рекомендуется снимать отдельно.

Совет

При включенном аудите в системе Global и высокой интенсивности работы схема aud может содержать большие объемы данных (Несколько терабайт). Создание резервной копии схемы aud может занимать продолжительное время.

Обычно все схемы снимаются для переустановки PostgreSQL или обновления версии PostgreSQL, с последующим нагоном полного дампа.

Для снятия дампа используйте формат «Директория». Это позволяет снимать дамп в многопоточном режиме и существенно ускоряет процесс.

Важно

В случае использования пуллеров соединений, таких как Pgbouncer, при снятии и восстановлении дампа к БД необходимо подключаться напрямую, минуя пуллер.

Общий пример запуска утилиты снятия дампа:

${sDumpUtl} -F d  --dbname=postgresql://${sUser}:${sPass}@${sHost}:5432/${sDbName} --jobs=$nColDbJobs --schema=public  --blobs --verbose

где

  • ${sDumpUtl} - путь к утилите pg_dump;

  • ${sUser} - логин;

  • ${sPass} - пароль;

  • ${sHost} - хост;

  • ${sDbName} - имя бд;

  • $nColDbJobs - количество потоков (выставляется по количеству ядер на сервере PostgreSQL).

Общий пример запуска утилиты для снятия схемы aud без данных:

${sDumpUtl} -F d  --dbname=postgresql://${sUser}:${sPass}@${sHost}:5432/${sDbName} --jobs=$nColDbJobs --schema=aud --schema-only --blobs --verbose --file=$sOutputDumpDir

где

  • ${sDumpUtl} - путь к утилите pg_dump;

  • ${sUser} - логин;

  • ${sPass} - пароль;

  • ${sHost} - хост;

  • ${sDbName} - имя бд;

  • $nColDbJobs - количество потоков (выставляется по количеству ядер на сервере PostgreSQL).

Совет

Параметр --schema-only позволяет выгружать только определения объектов (схемы), без данных.

После снятия дампа запаковываем директорию архиватором tar:

tar -cvf ${sArchiveDir}/${sTarFileName} $sOutputDumpDir

где

  • ${sArchiveDir} - директория для архива;

  • ${sTarFileName} - имя архивного файла;

  • $sOutputDumpDir - директория с дампом.

Развертывание дампа#

Внимание

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

Эти настройки нужно обязательно сбрасывать при установке!

Развертывание дампа выполняет штатная утилита pg_restore.

План установки дампа#

  • Обязательно останавливаем сервер Global.

  • Сохраняем схему global_system.

Пример снятия схемы global_system:

${sDumpUtl} -F d  --dbname=postgresql://${sUser}:${sPass}@${sHost}:5432/${sDbName} --jobs=$nColDbJobs --schema=global_system --blobs --verbose --file=$sOutputDumpDir
  • Обязательно удаляем все объекты схемы public:

$psqlUtl postgresql://${sUser}:${sPass}@${sHost}:5432/${sDbName} -f $SCRIPTPATH/drop.sql

где

  • ${psqlUtl} - путь к утилите psql;

  • ${sUser} - логин;

  • ${sPass} - пароль;

  • ${sHost} - хост;

  • ${sDbName} - имя бд;

  • $SCRIPTPATH - путь до файла drop.sql.

Файл drop.sql:

SET search_path TO public;
--
DO $$ DECLARE
    r RECORD;
BEGIN
    FOR r IN (SELECT p.oid::regprocedure as sFunctionName 
FROM pg_proc p 
INNER JOIN pg_namespace ns ON (p.pronamespace = ns.oid)
inner join pg_roles a on p.proowner =a.oid
WHERE ns.nspname = current_schema
  and a.rolname =current_user 
  and p.probin is null) LOOP
        EXECUTE 'DROP FUNCTION IF EXISTS ' || r.sFunctionName || ' CASCADE';
        commit;
    END LOOP;
END $$;
--
DO $$ DECLARE
    r RECORD;
BEGIN
    FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()) LOOP
        EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE';
        commit;
    END LOOP;
END $$;
--
DO $$ DECLARE
    r RECORD;
BEGIN
    FOR r IN (SELECT  c.relname   
FROM pg_class c 
inner join pg_catalog.pg_namespace n on c.relnamespace =n.oid
inner join pg_roles a on c.relowner =a.oid
WHERE (c.relkind = 'S')
  and n.nspname =current_schema 
  and a.rolname =current_user) LOOP
        EXECUTE 'drop sequence IF EXISTS ' || quote_ident(r.relname) || ' CASCADE';
        commit;
    END LOOP;
END $$;
--
DO $$ DECLARE
    r RECORD;
BEGIN
    FOR r IN (SELECT  c.relname
FROM pg_class c 
inner join pg_catalog.pg_namespace n on c.relnamespace =n.oid
inner join pg_roles a on c.relowner =a.oid
where c.relkind ='v'
  and n.nspname =current_schema 
  and a.rolname =current_user) LOOP
        EXECUTE 'drop view IF EXISTS ' || quote_ident(r.relname) || ' CASCADE';
        commit;
    END LOOP;
END $$;
--
SELECT lo_unlink(l.oid) 
FROM pg_largeobject_metadata l
inner join pg_roles a on l.lomowner =a.oid
WHERE a.rolname =current_user;
  • Перед развертыванием дампа его нужно распаковать:

tar -xvf ${dumpArchiveFile} -C $sTempPath
  • Запустите утилиту развертывания дампа.

Первый вариант:

$sRestoreUtl --dbname=postgresql://${sUser}:${sPass}@${sHost}:5432/${sDbName} -O -x -v --no-tablespaces  --jobs=$nColDbJobs $sRealDumpDir

Второй вариант:

export PGPASSWORD='${sPass}'
$sRestoreUtl -h ${sHost} -U ${sUser} -d ${sDbName} -p 5432 --no-owner --no-privileges -j 20 -v $sRealDumpDir > restore_30_10.log 2>&1

где:

  • $sRestoreUtl - путь к утилите pg_restore;

  • ${sUser} - логин;

  • ${sPass} - пароль;

  • ${sHost} - хост;

  • ${sDbName} - имя бд;

  • $nColDbJobs - количество потоков (выставляется по количеству ядер на сервере PostgreSQL);

  • $sRealDumpDir - путь к каталогу с файлами дампа;

  • если при установке удалили схему global_system, тогда восстанавливаем её, по аналогии с основной установкой дампа;

  • запускаем сервер Global.

Отключение задач в шедуллере#

После поднятия дампа необходимо отключить активные джобы (исключая системные btk), чтобы они не выполняли внешние вызовы (например, интеграции).
Скрипт для отключения джоб:

update Btk_Job
set benabled = 0
where (ssystemname is null) or (not starts_with(lower(ssystemname), 'btk_'))

JEXL-скрипт для сброса настроек шедулера#

var JObject = function (value) { new ("ru.bitec.app.gtk.lang.json.JObject", value); }

//-----------------------------------------
// Параметры шедулера
var soapHost = "localhost"; // Soap хост
var soapPort = 8080B;       // Soap порт (не забудьте 'B' в конце)
var soapReqStr = "/app/sys/soap/sys-service-1.0.0"; // Строка soap запроса
var database = "globalDb";     // База данных
var login = "admin";        // Логин
var pass = "admin";         // Пароль
var publicKey = "";         // Публичный ключ
var isHttpsEnabled = false; // HTTPS включён


//-----------------------------------------
// Скрипт
var idvBtkModule = Btk_SettingApi.getModuleIdByMnemoCode("btk");

var jQuartzProps = null;
if (Btk_SettingApi.isExistsSetting(idvBtkModule, "cQuartzProps")) {
  jQuartzProps = JObject(Btk_SettingApi.getSetting(idvBtkModule, "cQuartzProps").asJObject());
} else {
  jQuartzProps = JObject({:});
}

jQuartzProps.set("soapHost", soapHost);
jQuartzProps.set("soapPort", soapPort);
jQuartzProps.set("soapReqStr", soapReqStr);
jQuartzProps.set("database", database);
jQuartzProps.set("login", login);
jQuartzProps.set("pass", pass);
jQuartzProps.set("publicKey", publicKey);
jQuartzProps.set("isHttpsEnabled", isHttpsEnabled);

Btk_SettingApi.registerJObject(idvBtkModule, "cQuartzProps", jQuartzProps.underlying(), "");
commit();

JEXL-скрипт для отключения всех интеграций#

// Скрипт отключения всех джобов контуров интеграций
for(row: sql(`
  select distinct t.idJob from Rpl_CircuitAgent t
`).asList()) {
  var rvCircuitJob = Btk_JobApi.load(row.idjob);
  Btk_JobApi.setbEnabled(rvCircuitJob, 0B);
}
commit();

BASH-скрипт выполнения JEXL-кода через API системы#

Bash-скрипт позволяет:

  • загружать скрипты из внешних файлов;

  • авторизоваться в системе;

  • выполнять код на сервере;

  • получать результаты выполнения.

Скрипт:

#!/bin/bash

# Конфигурация
USERNAME="<username>"
PASSWORD="<password>"
DATABASE="<database>"
URL="<url>"
JEXL_SCRIPT_FILE="<path_to_script>"  # Укажите путь к файлу с JEXL-скриптом

# Проверка существования файла со скриптом
if [ ! -f "$JEXL_SCRIPT_FILE" ]; then
    echo "Ошибка: файл со скриптом '$JEXL_SCRIPT_FILE' не найден"
    exit 1
fi

# Чтение содержимого файла со скриптом
JEXL_SCRIPT=$(<"$JEXL_SCRIPT_FILE")

# Кодирование логина и пароля в base64
BASIC_AUTH=$(echo -n "$USERNAME:$PASSWORD" | base64)

# Формирование тела запроса с помощью jq
JSON_BODY=$(jq -n --arg jexl "$JEXL_SCRIPT" '{jexl: $jexl}')

# Отправка POST-запроса
curl -X POST "$URL/app/sys/rest/ss/pkg/Btk_JexlGatePkg/execute" \
  -H "Authorization: Basic $BASIC_AUTH" \
  -H "Database: $DATABASE" \
  -H "Content-Type: application/json" \
  -d "$JSON_BODY"

Требования#

  • ПО:

    • curl 7.68 или новее;

    • jq 1.6 или новее;

    • bash 5.0 или новее;

    • доступ к REST API системы;

    • разрешение на чтение файлов скриптов.

Конфигурация#

Переменная

Описание

Пример значения

USERNAME

Логин администратора

admin

PASSWORD

Пароль

admin

DATABASE

Целевая БД

globalDb

URL

Базовый URL

http://globalERP.local/globalDb/

JEXL_SCRIPT_FILE

Петь к JEXL скрипту

/scripts/update_settings.jexl

Использование#

Исправить переменные в скрипте на свои и запустить его.

bash run_jexl_api.sh

Подставляйте URL со страницы выбора приложений.

Например: на странице выбора приложений такой URL http://globalERP.local/globalDb/?redirect-id=8, следовательно в переменную подставляем http://globalERP.local/globalDb/