Встроенная потоковая репликация (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 /usr/local/pgsql/wal_archive
chown postgres /usr/local/pgsql/wal_archive
2. Добавим скрипты необходимые для репликации postgresql:
/opt/scripts/master_save_wal.pl:
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:
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 = <code>/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:
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 добавим следующий строчки:
4. Перезагрузим postgresql и убедимся что wal-logs создаются в директории /usr/local/pgsql/wal_archive:
Для возможности создания снимка данных postgresql посылается запрос к серверу:
,где ‘label’ может быть любым словом.
Этот запрос переводит мастер в режим создание резервной копии, при котором можно копировать директорию data на slave (например rsync). Полученный таким образом «snapshot» будет не противоречивый и будет содержать файл, указывающий ведомому серверу когда он был сделан для дальнейшего приведения данных в БД к актуальному состоянию. До посылки на мастере запроса «select pg_stop_backup();» никакие изменения в базу не записываются, а заносятся в транзакционные логи, что стоит учесть и не забывать выйти с данного режима.
5. Переводим мастер в режим бекапа:
6. Останавливаем postgresql на slave:
7. Копируем postgresql instance на slave:
После окончания не забудем выключить режим бекапов на мастере:
8. Для настройки репликации postgresql на slave-сервере. Создадим файл следующего содержания:
/usr/local/pgsql/data/recovery.conf:
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:
Создадим директорию в которую будут попадать wal-logs с мастера:
chown -R postgres /usr/local/pgsql/wal_archive_in
10. Запускаем postgresql на slave-сервере:
11.Добавим в cron пользователя postgres копировать wal-logs с мастер сервера:
12. Для настройки потоковой репликации postgresql достаточно на мастер сервере:
/usr/local/pgsql/data/postgresql.conf:
13. Добавить в файл /usr/local/pgsql/data/pg_hba.conf :
# for streaming replication
14. На slave расскоментировать строку:
15. Мониторинг репликации postgresql:
При настроенной репликации на мастере и slave запускаются дополнительные процессы: postgres: wal sender process и postgres: wal receiver process, соответственно.
Для анализа от полученных результатов нужно взять только шестнадцатиричное (HEX) значение, которое после слеша ‘/’ и перевести в десятичный (DEC) формат. Разница этих чисел даст некоторое значение от которого можно отталкиваться при подсчете отставания слейва от мастера. Для перевода HEX to DEC, т.е. шестнадцатиричного в десятичное число можно воспользоваться, к примеру, такими утилитами:
255
printf "%dn" 0xff
255
Если у вас есть некоторая таблица в которую данные поступают регулярну то можно просто написать запрос который будет сравнивать время последнего изменения в данной таблицы с текущим - таким образом можно будет определить на сколько отстает slave от мастера.
Ссылки:
Мониторинг репликации postgresql
Настройка репликации postgresql