WAL-G: бэкапы і аднаўленне СКБД PostgreSQL

Ужо даўно вядома, што рабіць бэкапы ў SQL-дампы (выкарыстоўваючы pg_dump або pg_dumpall) - не самая добрая ідэя. Для рэзервовага капіявання СКБД PostgreSQL лепш выкарыстоўваць каманду pg_basebackup, якая робіць бінарную копію WAL-часопісаў. Але калі вы пачняце вывучаць увесь працэс стварэння копіі і аднаўлення, то зразумееце што трэба напісаць як мінімум пару трохколавых ровараў, каб усё гэта працавала і не выклікала ў вас боль як зверху, так і знізу. Каб аблегчыць пакуты быў распрацаваны WAL-G.

WAL-G – гэта інструмент, напісаны на Go для рэзервовага капіявання і аднаўленні PostgreSQL баз дадзеных (а з нядаўняга часу і MySQL/MariaDB, MongoDB і FoundationDB). Ён падтрымлівае працу са сховішчамі Amazon S3 (і аналагамі, напрыклад, Yandex Object Storage), а таксама Google Cloud Storage, Azure Storage, Swift Object Storage і проста з файлавай сістэмай. Уся настройка зводзіцца да простых крокаў, але з-за таго што артыкулы пра яго разрозненыя па інтэрнэце – няма поўнага how-to мануала, які б уключаў усе крокі ад і да (на Хабры ёсць некалькі пастоў, але многія моманты там страчаны).

WAL-G: бэкапы і аднаўленне СКБД PostgreSQL

Гэты артыкул напісаны ў першую чаргу каб сістэматызаваць свае веды. Я не з'яўляюся DBA і недзе магу выражацца абывацельска-распрацоўчай мовай, таму вітаюцца любыя выпраўленні!

Асобна адзначу што ўсё ніжэйпрыведзенае актуальна і праверана для PostgreSQL 12.3/18.04 на Ubuntu XNUMX/XNUMX, усе каманды павінны выконвацца ад прывілеяванага карыстальніка.

Ўстаноўка

У момант напісання дадзенага артыкула стабільная версія WAL-G - v0.2.15 (сакавік 2020). Яе мы і будзем выкарыстоўваць (але калі вы захочаце самастойна сабраць яго з master-галінкі, то ў рэпазітары на github ёсць усе інструкцыі для гэтага). Для спампоўкі і ўстаноўкі трэба выканаць:

#!/bin/bash

curl -L "https://github.com/wal-g/wal-g/releases/download/v0.2.15/wal-g.linux-amd64.tar.gz" -o "wal-g.linux-amd64.tar.gz"
tar -xzf wal-g.linux-amd64.tar.gz
mv wal-g /usr/local/bin/

Пасля гэтага трэба сканфігураваць спачатку WAL-G, а потым сам PostgreSQL.

Настройка WAL-G

Для прыкладу захоўвання бэкапаў будзе выкарыстоўвацца Amazon S3 (таму што ён бліжэй да маіх сервераў і яго выкарыстанне абыходзіцца вельмі танна.). Для працы з ім патрэбен "s3-бакет" і ключы доступу.

Ва ўсіх папярэдніх артыкулах аб WAL-G выкарыстоўвалася канфігураванне з дапамогай зменных асяроддзі, але з гэтага рэлізу налады можна размясціць у .walg.json файле у хатняй дырэкторыі карыстальніка postgres. Для яго стварэння выканаем наступны bash-скрыпт:

#!/bin/bash

cat > /var/lib/postgresql/.walg.json << EOF
{
    "WALG_S3_PREFIX": "s3://your_bucket/path",
    "AWS_ACCESS_KEY_ID": "key_id",
    "AWS_SECRET_ACCESS_KEY": "secret_key",
    "WALG_COMPRESSION_METHOD": "brotli",
    "WALG_DELTA_MAX_STEPS": "5",
    "PGDATA": "/var/lib/postgresql/12/main",
    "PGHOST": "/var/run/postgresql/.s.PGSQL.5432"
}
EOF
# обязательно меняем владельца файла:
chown postgres: /var/lib/postgresql/.walg.json

Трохі растлумачу па ўсіх параметрах:

  • WALG_S3_PREFIX - шлях да вашага S3-бакета куды будуць залівацца бэкапы (можна як у корань, так і ў тэчку);
  • AWS_ACCESS_KEY_ID – ключ доступу ў S3 (у выпадку аднаўлення на тэставым серверы - дадзеныя ключы павінны мець ReadOnly Policy! Пра гэта больш падрабязна напісана ў раздзеле пра аднаўленне);
  • AWS_SECRET_ACCESS_KEY - сакрэтны ключ у сховішча S3;
  • WALG_COMPRESSION_METHOD – метад кампрэсіі, лепш выкарыстоўваць Brotli (бо гэта залатая сярэдзіна паміж выніковым памерам і хуткасцю сціску/разцісканні);
  • WALG_DELTA_MAX_STEPS – колькасць «дэльт» да стварэння поўнага бэкапу (дазваляюць эканоміць час і памер загружаных дадзеных, але могуць крыху запаволіць працэс аднаўлення, таму не пажадана выкарыстоўваць вялікія значэнні);
  • PGDATA - шлях да дырэкторыі з дадзенымі вашай базы (можна даведацца, выканаўшы каманду pg_lsclusters);
  • PGHOST - падлучэнне да базы, пры лакальным бэкапу лепш рабіць праз unix-socket як у гэтым прыкладзе.

Астатнія параметры можна паглядзець у дакументацыі: https://github.com/wal-g/wal-g/blob/v0.2.15/PostgreSQL.md#configuration.

Настройка PostgreSQL

Каб архіватар усярэдзіне баз сам заліваў WAL-часопісы ў воблака і аднаўляўся з іх (у выпадку неабходнасці) – трэба задаць некалькі параметраў у канфігурацыйным файле /etc/postgresql/12/main/postgresql.conf. Толькі для пачатку вам трэба пераканацца, Што ніякія з ніжэйпрыведзеных налад не зададзены ў нейкія іншыя значэнні, каб пры перазагрузцы канфігурацыі - СКБД не звалілася. Дадаць гэтыя параметры можна з дапамогай:

#!/bin/bash

echo "wal_level=replica" >> /etc/postgresql/12/main/postgresql.conf
echo "archive_mode=on" >> /etc/postgresql/12/main/postgresql.conf
echo "archive_command='/usr/local/bin/wal-g wal-push "%p" >> /var/log/postgresql/archive_command.log 2>&1' " >> /etc/postgresql/12/main/postgresql.conf
echo “archive_timeout=60” >> /etc/postgresql/12/main/postgresql.conf
echo "restore_command='/usr/local/bin/wal-g wal-fetch "%f" "%p" >> /var/log/postgresql/restore_command.log 2>&1' " >> /etc/postgresql/12/main/postgresql.conf

# перезагружаем конфиг через отправку SIGHUP сигнала всем процессам БД
killall -s HUP postgres

Апісанне усталёўваных параметраў:

  • wal_level - колькі інфармацыі пісаць у WAL часопісы, "replica" - пісаць усё;
  • archive_mode – уключэнне загрузкі WAL-часопісаў выкарыстоўваючы каманду з параметру archive_command;
  • archive_command – каманда, для архівацыі завершанага WAL-часопіса;
  • archive_timeout - архіваванне часопісаў вырабляецца толькі калі ён завершаны, але калі ваш сервер мала змяняе/дадае дадзеных у БД, тое мае сэнс выставіць тут ліміт у секундах, па заканчэнні якога каманда архівацыі будзе выклікана прымусова (у мяне інтэнсіўны запіс у базу кожную секунду, таму я адмовіўся ад усталёўкі гэтага параметру ў прадакшэне);
  • restore_command - каманда аднаўлення WAL-часопіса з бэкапу, будзе выкарыстоўвацца ў выпадку калі ў "поўным бэкапе" (base backup) будзе не хапаць апошніх змен у БД.

Падрабязней пра ўсе гэтыя параметры можна прачытаць у перакладзе афіцыйнай дакументацыі: https://postgrespro.ru/docs/postgresql/12/runtime-config-wal.

Настройка раскладу рэзервовага капіявання

Як ні круці, але самым зручным спосабам для запуску з'яўляецца cron. Менавіта яго мы і настроім для стварэння рэзервовых копій. Пачнём з каманды стварэння поўнага бэкапу: у wal-g гэта аргумент запуску backup-push. Але для пачатку лепш выканаць гэтую каманду ўручную ад карыстальніка postgres, каб пераканацца, што ўсё добра (і няма нейкіх памылак доступу):

#!/bin/bash

su - postgres -c '/usr/local/bin/wal-g backup-push /var/lib/postgresql/12/main'

У аргументах запуску паказаны шлях да дырэкторыі з дадзенымі - нагадваю што яго можна пазнаць, выканаўшы pg_lsclusters.

Калі ўсё прайшло без памылак і дадзеныя загрузіліся ў сховішча S3, то далей можна наладзіць перыядычны запуск у crontab:

#!/bin/bash

echo "15 4 * * *    /usr/local/bin/wal-g backup-push /var/lib/postgresql/12/main >> /var/log/postgresql/walg_backup.log 2>&1" >> /var/spool/cron/crontabs/postgres
# задаем владельца и выставляем правильные права файлу
chown postgres: /var/spool/cron/crontabs/postgres
chmod 600 /var/spool/cron/crontabs/postgres

У дадзеным прыкладзе працэс бэкапу запускаецца кожны дзень у 4:15 раніцы.

Выдаленне старых рэзервовых копій

Хутчэй за ўсё вам не трэба захоўваць абсалютна ўсе бэкапы з мезазойскай эры, таму будзе карысна перыядычна "падчышчаць" ваша сховішча (як "поўныя бэкапы", так і WAL-часопісы). Мы зробім гэта ўсё таксама праз cron задачу:

#!/bin/bash

echo "30 6 * * *    /usr/local/bin/wal-g delete before FIND_FULL $(date -d '-10 days' '+%FT%TZ') --confirm >> /var/log/postgresql/walg_delete.log 2>&1" >> /var/spool/cron/crontabs/postgres
# ещё раз задаем владельца и выставляем правильные права файлу (хоть это обычно это и не нужно повторно делать)
chown postgres: /var/spool/cron/crontabs/postgres
chmod 600 /var/spool/cron/crontabs/postgres

Cron будзе выконваць гэтую задачу кожны дзень у 6:30 раніцы, выдаляючы ўсё (поўныя бэкапы, дэльты і WAL'ы) акрамя дзід за апошнія 10 дзён, але пакіне прынамсі адзін бэкап да названай даты, каб любая кропка пасля даты пападала ў PITR.

Аднаўленне з рэзервовай копіі

Ні для каго не сакрэт што заклад здаровай базы - у перыядычным аднаўленні і праверцы цэласнасці дадзеных усярэдзіне. Як аднавіцца з дапамогай WAL-G - раскажу ў гэтым раздзеле, а аб праверках пагаворым пасля.

Асобна варта адзначыць што для аднаўлення на тэставым асяроддзі (усё тое, што не production) - трэба выкарыстоўваць Read Only акаўнт у S3, каб выпадкова не перазапісаць бэкапы. У выпадку з WAL-G трэба задаць карыстачу S3 наступныя правы ў Group Policy (Effect: Allow): s3:GetObject, s3:ListBucket, s3:GetBucketLocation. І, вядома, папярэдне не забыць выставіць archive_mode=off у файле налад postgresql.conf, Каб ваша тэставая база не захацела збэкапіцца па-ціхаму.

Аднаўленне вырабляецца лёгкім рухам рукі з выдаленнем усіх дадзеных PostgreSQL (у тым ліку карыстальнікаў), таму, калі ласка, будзьце лімітава акуратныя калі будзеце запускаць наступныя каманды.

#!/bin/bash

# если есть балансировщик подключений (например, pgbouncer), то вначале отключаем его, чтобы он не нарыгал ошибок в лог
service pgbouncer stop
# если есть демон, который перезапускает упавшие процессы (например, monit), то останавливаем в нём процесс мониторинга базы (у меня это pgsql12)
monit stop pgsql12
# или останавливаем мониторинг полностью
service monit stop
# останавливаем саму базу данных
service postgresql stop
# удаляем все данные из текущей базы (!!!); лучше предварительно сделать их копию, если есть свободное место на диске
rm -rf /var/lib/postgresql/12/main
# скачиваем резервную копию и разархивируем её
su - postgres -c '/usr/local/bin/wal-g backup-fetch /var/lib/postgresql/12/main LATEST'
# помещаем рядом с базой специальный файл-сигнал для восстановления (см. https://postgrespro.ru/docs/postgresql/12/runtime-config-wal#RUNTIME-CONFIG-WAL-ARCHIVE-RECOVERY ), он обязательно должен быть создан от пользователя postgres
su - postgres -c 'touch /var/lib/postgresql/12/main/recovery.signal'
# запускаем базу данных, чтобы она инициировала процесс восстановления
service postgresql start

Для тых, хто жадае правяраць працэс аднаўлення - ніжэй падрыхтаваны невялікі кавалак bash-магіі, каб у выпадку праблем у аднаўленні - скрыпт зваліўся з ненулявым exit code. У дадзеным прыкладзе робіцца 120 праверак з таймаўтам у 5 секунд (усяго 10 хвілін на аднаўленне), каб даведацца ці выдаліўся сігнальны файл (гэта будзе азначаць што аднаўленне прайшло паспяхова):

#!/bin/bash

CHECK_RECOVERY_SIGNAL_ITER=0
while [ ${CHECK_RECOVERY_SIGNAL_ITER} -le 120 ]
do
    if [ ! -f "/var/lib/postgresql/12/main/recovery.signal" ]
    then
        echo "recovery.signal removed"
        break
    fi
    sleep 5
    ((CHECK_RECOVERY_SIGNAL_ITER+1))
done

# если после всех проверок файл всё равно существует, то падаем с ошибкой
if [ -f "/var/lib/postgresql/12/main/recovery.signal" ]
then
    echo "recovery.signal still exists!"
    exit 17
fi

Пасля паспяховага аднаўлення не забудзьцеся запусціць зваротна ўсе працэсы (pgbouncer/monit і тд).

Праверка дадзеных пасля аднаўлення

Абавязкова трэба праверыць цэласнасць базы пасля аднаўлення, каб не ўзнікла сітуацыі з пабітай/крывой рэзервовай копіяй. І лепш рабіць гэта з кожным створаным архівам, але дзе і як - залежыць толькі ад вашай фантазіі (можна паднімаць асобныя сервера на пагадзіннай аплаце ці запускаць праверку ў CI). Але як мінімум - неабходна правяраць дадзеныя і індэксы ў базе.

Для праверкі дадзеных дастаткова прагнаць іх праз дамп, але лепш каб пры стварэнні базы ў вас былі ўключаны кантрольныя сумы (data checksums):

#!/bin/bash

if ! su - postgres -c 'pg_dumpall > /dev/null'
then
    echo 'pg_dumpall failed'
    exit 125
fi

Для праверкі індэксаў - існуе модуль amcheck, sql-запыт да яго возьмем з тэстаў WAL-G і вакол выбудуем невялікую логіку:

#!/bin/bash

# добавляем sql-запрос для проверки в файл во временной директории
cat > /tmp/amcheck.sql << EOF
CREATE EXTENSION IF NOT EXISTS amcheck;
SELECT bt_index_check(c.oid), c.relname, c.relpages
FROM pg_index i
JOIN pg_opclass op ON i.indclass[0] = op.oid
JOIN pg_am am ON op.opcmethod = am.oid
JOIN pg_class c ON i.indexrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE am.amname = 'btree'
AND c.relpersistence != 't'
AND i.indisready AND i.indisvalid;
EOF
chown postgres: /tmp/amcheck.sql

# добавляем скрипт для запуска проверок всех доступных баз в кластере
# (обратите внимание что переменные и запуск команд – экранированы)
cat > /tmp/run_amcheck.sh << EOF
for DBNAME in $(su - postgres -c 'psql -q -A -t -c "SELECT datname FROM pg_database WHERE datistemplate = false;" ')
do
    echo "Database: ${DBNAME}"
    su - postgres -c "psql -f /tmp/amcheck.sql -v 'ON_ERROR_STOP=1' ${DBNAME}" && EXIT_STATUS=$? || EXIT_STATUS=$?
    if [ "${EXIT_STATUS}" -ne 0 ]
    then
        echo "amcheck failed on DB: ${DBNAME}"
        exit 125
    fi
done
EOF
chmod +x /tmp/run_amcheck.sh

# запускаем скрипт
/tmp/run_amcheck.sh > /tmp/amcheck.log

# для проверки что всё прошло успешно можно проверить exit code или grep’нуть ошибку
if grep 'amcheck failed' "/tmp/amcheck.log"
then
    echo 'amcheck failed: '
    cat /tmp/amcheck.log
    exit 125
fi

Рэзюмуючы

Выказваю падзяку Андрэю Барадзіну за дапамогу ў падрыхтоўцы публікацыі і асобны дзякуй за яго ўклад у распрацоўку WAL-G!

На гэтым дадзеная нататка падышла да канца. Спадзяюся што змог данесці лёгкасць налады і велізарны патэнцыял для ўжывання гэтай прылады ў вас у кампаніі. Я вельмі шмат чуў пра WAL-G, але ніяк не хапала часу сесці і разабрацца. А пасля таго як укараніў яго ў сябе - з мяне выйшаў гэты артыкул.

Асобна варта заўважыць, што WAL-G таксама можа працаваць з наступнымі СКБД:

Крыніца: habr.com

Дадаць каментар