Настройка репликации postgresql

Июнь 1st, 2013 | от | gnu/linux, opensuse

Июн
01

Встроенная потоковая репликация (Streaming Replication), есть в PostgreSQL начиная с версии 9.0, , усовершенствованна и реализована с помощью передачи WAL (write-ahead logs, журналов (логов) опережающей записи) с главного (Master/Primary) сервера на подчиненный (Slave/Secondary/Replica).
Это асинхронная репликация Master/Slave, когда на ведущий/главный сервер идут запросы, приводящие к изменениям в БД, а на подчиненном сервере эти изменения будут выполнены только через некоторое время. В данном случае реализация состоит в том, что создается сетевое подключение между Standby и Master СУБД вместо копирования журналов по 16 мегабайт, как при Warm Standby и Hot Standby. Как известно, механизм используемый в Warm Standby и Hot Standby основан на пересылке от мастера к слейву.
Мы сначала настроим Hot Standby репликацию, а затем добавим возможность потоковой репликации.
Можно совмещать Hot Standby и Streaming Replication. Это позволяет увидеть на слейве почти мгновенно изменения, выполненные на мастере. Хотя и при работе только потоковой репликации записи с WAL-логов востанавливаются сразу же при процессе их получения. При потоковой репликации на серверах создаются специальных два дополнительных процесса walsender и walreceiver на каждую пару серверов управления базами данных, взаимодействующих между собой через сеть. Потоковая репликация позволяет легко реализовать работу нескольких ведомых.

Исходные данные.
Мастер-сервер ip-адрес: 192.168.200.62
Slave-сервер ip-адрес: 192.168.51.52
postgresql версия: 9.0.8. Важно! Для настройки репликации postgresql: Версия postgresql и операционной системы должна быть одинаковой для мастера и slave серверов
OS: OpenSuSE 11.4

1. Внесем некоторые изменения на мастер сервере:

mkdir /opt/scripts
mkdir /usr/local/pgsql/wal_archive
chown postgres /usr/local/pgsql/wal_archive

2. Добавим скрипты необходимые для репликации postgresql:
/opt/scripts/master_save_wal.pl:

показать файл »

#!/usr/bin/perl -w
use strict;
use warnings;

use POSIX qw();

#
# Saves WAL files on master.
# Called by postgresql via archive_command.
#
# Install as /opt/scripts/master_save_wal.pl
# Adjustments to postgresql.conf:
#
# wal_level=hot_standby
# archive_mode = on
# archive_command = '/opt/scripts/master_save_wal.pl %p %f /usr/local/pgsql/wal_archive'

my ($wal_ffn, $wal_fn, $archive_dir, $url_to) = @ARGV;

sub report_error {
my ($errmsg) = @_;
print STDERR "ERROR: $errmsgn";

eval {
open my $mailer, '|/usr/sbin/sendmail -t' or die "ERROR: could not send error report: $!n";
print $mailer "To: root@localhostrnSubject: $0 errorrnrnERROR: $0 @ARGVrnrn$errmsgrn.rn";
close $mailer;
};

if ( $@ ) {
print STDERR "Failed to mail error message: $@n";
}
}

/opt/scripts/slave_get_wal.pl:

показать файл »

#!/usr/bin/perl -w
use strict;
use warnings;
#
# On slave, fetches WAL file from local archive and gives it to postgresql.
# Called by restore_command from recovery.conf on slave. Does "exit 0" if file is missing.
#
# Install as /opt/scripts/slave_get_wal.pl
# Adjustments to recovery.conf:
#
# restore_command = '/opt/scripts/slave_get_wal.pl %f %p /usr/local/pgsql/wal_archive_in'
# trigger_file = '/usr/local/pgsql/data/slave_trigger_file'

my ($wal_want, $wal_to, $archive_in_dir) = @ARGV;

my $touchFile = $archive_in_dir . '/last_wal_applied_timestamp';

sub report_error {
my ($errmsg) = @_;
print STDERR "ERROR: $errmsgn";

eval {
open my $mailer, '|/usr/sbin/sendmail -t' or die "ERROR: could not send error report: $!n";
print $mailer "To: root@localhostrnSubject: $0 errorrnrnERROR: $0 @ARGVrnrn$errmsgrn.rn";
close $mailer;
};

if ( $@ ) {
print STDERR "Failed to mail error message: $@n";
}
}

unless ( defined $wal_want and defined $wal_to and defined $archive_in_dir ) {
print "nUsage: $0 wal_want wal_to archive_in_dirnn",
" wal_want: %f per recovery.conf's 'restore_command'n",
" wal_to: %p per recovery.conf's 'restore_command'n",
" archive_in_dir: directory with incoming WAL filesnn";
exit(-1);
}

my $ok_to_proceed = eval {
my $o = `/usr/local/pgsql/bin/psql -d postgres -U postgres -t -c "select 1 from pg_catalog.pg_stat_activity where xact_start is not NULL and procpid != pg_backend_pid() and not waiting limit 1"`;
return $o !~ /b1b/;
};
if ( $@ ) {
print STDERR $@;
$ok_to_proceed = 1;
}
unless ( $ok_to_proceed ) {
print STDERR "has active transaction, not proceeding with log update this timen";
exit 0;
}

sub do_stuff {
-d $archive_in_dir or die "archive_in_dir at $archive_in_dir do not existn";
my $tmp_dir = $archive_in_dir . '/tmp';
-d $tmp_dir or mkdir($tmp_dir) or die "Failed to create $tmp_dir: $!n";

my $src_fn = "${archive_in_dir}/${wal_want}.xz";
-f $src_fn or do {
print STDERR "NOTICE: Requested file $wal_want ($src_fn) not found in archiven";
exit 0;
};
my $tmp_fn = "${tmp_dir}/${wal_want}";
my $cmd = "xz -cd ${src_fn} > ${tmp_fn} && mv ${tmp_fn} ${wal_to}";
system($cmd) and die "ERROR: "$cmd": $!n";

{
open my $ofh, '>', $touchFile or die "could not touch file $touchFilen";
close $ofh;
}
}

eval { do_stuff() };
report_error($@) if $@;
exit 0; # always this

/opt/scripts/slave_wal_cleanup.pl:

показать файл »

#!/usr/bin/perl -w
use strict;
use warnings;

#
# On slave, removes praocessed WAL files from local archive.
# Called by archive_cleanup_command from recovery.conf.
#
# Install as /opt/scripts/slave_wal_cleanup.pl
# Adjustments to recovery.conf:
#
# archive_cleanup_command = '/opt/scripts/slave_wal_cleanup.pl %r /usr/local/pgsql/wal_archive_in'

my ($wal_keep_min, $archive_in_dir) = @ARGV;

sub report_error {
my ($errmsg) = @_;
print STDERR "ERROR: $errmsgn";

eval {
open my $mailer, '|/usr/sbin/sendmail -t' or die "ERROR: could not send error report: $!n";
print $mailer "To: root@localhostrnSubject: $0 errorrnrnERROR: $0 @ARGVrnrn$errmsgrn.rn";
close $mailer;
};

if ( $@ ) {
print STDERR "Failed to mail error message: $@n";
}
}

sub do_stuff {
-d $archive_in_dir or die "archive_in_dir at $archive_in_dir do not existn";

print STDERR "called as: $0 @ARGVn";
foreach my $fn ( glob("${archive_in_dir}/*.xz") ) {
next unless -f $fn;

my ($wal_name) = $fn =~ m|/([a-z0-9]+).[^/]+$|ig;
unless ( defined $wal_name ) {
print STDERR "Could not figure out wal name out of $fnn";
next;
}

if ( $wal_name lt $wal_keep_min ) {
print STDERR "$0: removing $fnn";
unlink $fn;
} else {
print STDERR "$0: keeping $fnn";
}
}

# remove old .* files - old rsync stuff
my $cmd = "find ${archive_in_dir} -maxdepth 1 -type f -name .\* -mtime +15 -exec rm {} \;";
#print "removing old files with: $cmdn";
system($cmd) and die "ERROR: failed to remove old files with "$cmd": $!n";
}

eval { do_stuff() };
report_error($@) if $@;
exit 0; # always this

3. На мастер сервере в конфигурационном файле /usr/local/pgsql/postgresql.conf добавим следующий строчки:

wal_level = hot_standby
archive_mode = on
archive_command = '/opt/scripts/master_save_wal.pl %p %f /usr/local/pgsql/wal_archive'
archive_timeout = 3600

4. Перезагрузим postgresql и убедимся что wal-logs создаются в директории /usr/local/pgsql/wal_archive:

/etc/init.d/postgres restart
ls -al /usr/local/pgsql/wal_archive/
psql -c "select pg_switch_xlog();"
ls -al /usr/local/pgsql/wal_archive/

Для возможности создания снимка данных postgresql посылается запрос к серверу:

select pg_start_backup(‘label’,true);

,где ‘label’ может быть любым словом.
Этот запрос переводит мастер в режим создание резервной копии, при котором можно копировать директорию data на slave (например rsync). Полученный таким образом «snapshot» будет не противоречивый и будет содержать файл, указывающий ведомому серверу когда он был сделан для дальнейшего приведения данных в БД к актуальному состоянию. До посылки на мастере запроса «select pg_stop_backup();» никакие изменения в базу не записываются, а заносятся в транзакционные логи, что стоит учесть и не забывать выйти с данного режима.

5. Переводим мастер в режим бекапа:

psql -c "SELECT pg_start_backup('label', true)"

6. Останавливаем postgresql на slave:

/etc/init.d/postgres stop

7. Копируем postgresql instance на slave:

rsync --rsh=ssh -avrz --progress --delete --exclude=pg_xlog --exclude=postmaster.pid root@192.168.200.62:/usr/local/pgsql/data/ /usr/local/pgsql/data/

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

psql -c "SELECT pg_stop_backup()"

8. Для настройки репликации postgresql на slave-сервере. Создадим файл следующего содержания:
/usr/local/pgsql/data/recovery.conf:

standby_mode = 'on'
restore_command = '/opt/scripts/slave_get_wal.pl %f %p /usr/local/pgsql/wal_archive_in'
trigger_file = '/usr/local/pgsql/data/trigger_file'
archive_cleanup_command = '/opt/scripts/slave_wal_cleanup.pl %r /usr/local/pgsql/wal_archive_in'
#primary_conninfo = 'host=192.168.200.62 port=5432 user=postgres' //для потоковой репликации

trigger_file нужен для того, чтобы в случае падения мастера, можно было бы сделать слейв-мастером. Т.е. доступным на запись.

9. На slave-сервере внесем одно изменение в файл /usr/local/pgsql/postgresql.conf:

hot_standby=on

Создадим директорию в которую будут попадать wal-logs с мастера:

mkdir /usr/local/pgsql/wal_archive_in
chown -R postgres /usr/local/pgsql/wal_archive_in

10. Запускаем postgresql на slave-сервере:

/etc/init.dpostgres start

11.Добавим в cron пользователя postgres копировать wal-logs с мастер сервера:

* * * * * /opt/scripts/wal_copy.pl postgres@192.168.200.62:/usr/local/pgsql/wal_archive/ /usr/local/pgsql/wal_archive_in 2>&1 | /bin/logger -t pgsql.error

12. Для настройки потоковой репликации postgresql достаточно на мастер сервере:
/usr/local/pgsql/data/postgresql.conf:

max_wal_senders = 2

13. Добавить в файл /usr/local/pgsql/data/pg_hba.conf :
# for streaming replication

host replication postgres 192.168.51.52/32 trust

14. На slave расскоментировать строку:

primary_conninfo = 'host=192.168.200.62 port=5432 user=postgres'

15. Мониторинг репликации postgresql:
При настроенной репликации на мастере и slave запускаются дополнительные процессы: postgres: wal sender process и postgres: wal receiver process, соответственно.

postgres  3510 11745  0 Apr15 ?        00:21:11 postgres: wal sender process postgres 192.168.51.52(47161) streaming 19/E493D4F8
postgres: wal receiver process   streaming 19/E496A050

Для анализа от полученных результатов нужно взять только шестнадцатиричное (HEX) значение, которое после слеша ‘/’ и перевести в десятичный (DEC) формат. Разница этих чисел даст некоторое значение от которого можно отталкиваться при подсчете отставания слейва от мастера. Для перевода HEX to DEC, т.е. шестнадцатиричного в десятичное число можно воспользоваться, к примеру, такими утилитами:

echo "ibase=16; FF"|bc
255
printf "%dn" 0xff
255

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

Ссылки:
Мониторинг репликации postgresql
Настройка репликации postgresql

Об авторе

Оставить комментарий