eugenek, JID:gnudist@jabber.ru, 2005 (обновлено в 2009)
Последнюю версию этого документа можно попробовать получить по адресу:
http://www.kuzin.net/work/sloniki-privet.html
Этот документ описывает настройку репликации базы данных
PostgreSQL с главного сервера на один ведомый сервер. Будет
рассмотрено также добавление ещё одного ведомого узла в имеющуюся
систему репликации.
Содержание
Рекомендуется прочитать документацию на Slony, которая
располагается по адресу: http://linuxfinances.info/info/slony.html,
README из дистрибутива, также полный список команд консоли slonik
находится в файле slonik-commands.html, в поставке.
Home page проекта: http://gborg.postgresql.org/project/slony1/projdisplay.php
Далее подразумевается что читатель знаком с терминами "кластер",
"узел" (node), "набор" (replication set). Если нет, некоторые моменты
могут быть не ясны. Не помешает также некоторое знание SQL ,)
0. Установка
Вопрос установки системы Slony не рассматривается в данном
документе. В документации, поставляемой с системой рассмотрена базовая
установка.
У меня используется Slony на Linux с версиями PostgreSQL от 7.4.7
до 8.3.5. Slony, как и СУБД, установлены из дистрибутивных пакетов.
1. Введение
Slony это система репликации реального времени, позволяющая
организовать синхронизацию нескольких серверов PostgreSQL по
сети. Slony использует триггеры Postgre для привязки к событиям
INSERT/DELETE/UPDATE и хранимые процедуры для выполнения действий.
Система Slony с точки зрения администратора состоит из двух
главных компонент, репликационного демона slony и административной
консоли slonik. Администрирование системы сводится к общению со
slonik-ом, демон slon только следит за собственно процессом
репликации. А админ следит за тем, чтобы slon висел там, где ему
положено.
1.1. О slonik-e
Все команды slonik принимает на свой stdin. До начала выполнения
скрипт slonik-a проверяется на соответствие синтаксису, если
обнаруживаются ошибки, скрипт не выполняется, так что можно не
волноваться если slonik сообщает о syntax error, ничего страшного не
произошло. И он ещё ничего не сделал. Скорее всего.
2. Установка Slony
Рассмотрим теперь установку на гипотетическую базу данных
customers (названия узлов, кластеров и таблиц являются вымышленными).
Наши данные
БД: customers
master_host: customers_master.com
slave_host_1: customers_slave.com
cluster name (нужно придумать): customers_rep
2.0 Подготовка master-сервера
Важно: не рекомендую использовать идущий в комплекте скрипт
slony_setup.pl на момент написания этого документа (версия Slony
1.0.5) этот скрипт не делал ряд важных проверок при генерации
репликационных скриптов, из-за чего могли возникать труднообъяснимые и
трудноуловимые глюки.
Для начала нам нужно создать пользователя Postgres, под которым
будет действовать Slony. По умолчанию, и отдавая должное системе, этого
пользователя обычно называют slony.
pgsql@customers_master$ createuser -a -d slony
pgsql@customers_master$ psql -d template1 -c "alter user slony with \
password 'slony_user_password';"
Также на каждом из узлов лучше завести системного пользователя slony,
чтобы запускать от его имени репликационного демона slon. В дальнейшем
подразумевается, что он (и пользователь и slon) есть на каждом из
узлов кластера.
2.1 Подготовка одного slave-сервера
Здесь я рассматриваю, что серверы кластера соединены посредством
сети Internet (как в моём случае), необходимо чтобы с каждого из
ведомых серверов можно было установить соединение с PostgreSQL на
мастер-хосте, и наоборот. То есть, команда:
anyuser@customers_slave$ psql -d customers -h customers_master.com -U slony
должна подключать нас к мастер-серверу (после ввода пароля,
желательно). Если что-то не так, возможно требуется поковыряться в
настройках firewall-a, или файле pg_hba.conf, который лежит в $PGDATA.
Теперь устанавливаем на slave-хост сервер PostgreSQL. Следующего
обычно не требуется, сразу после установки Postgres "up and ready", но
в случае каких-то ошибок можно начать "с чистого листа", выполнив
следующие команды (предварительно сохранив конфигурационные файлы и
остановив postmaster):
pgsql@customers_slave$ rm -rf $PGDATA
pgsql@customers_slave$ mkdir $PGDATA
pgsql@customers_slave$ initdb -E UTF8 -D $PGDATA
pgsql@customers_slave$ createuser -a -d slony
pgsql@customers_slave$ psql -d template1 -c "alter user slony with password \
'slony_user_password';"
Запускаем postmaster.
Внимание! Обычно требуется определённый владелец для реплицируемой
БД. В этом случае необходимо завести его тоже!
pgsql@customers_slave$ createuser -a -d customers_owner
pgsql@customers_slave$ psql -d template1 -c "alter user customers_owner with password \
'customers_owner_password';"
Эти две команды можно запускать с customers_master, к командной строке
в этом случае нужно добавить "-h customers_slave", чтобы все операции
выполнялись на slave.
На slave, как и на master, также нужно установить Slony.
2.2 Инициализация БД и plpgsql на slave
Следующие команды выполняются от пользователя slony. Скорее всего для
выполнения каждой из них потребуется ввести пароль
(slony_user_password). Итак:
slony@customers_master$ createdb -O customers_owner -h customers_slave.com customers
slony@customers_master$ createlang -d customers -h customers_slave.com plpgsql
Внимание! Все таблицы, которые будут добавлены в replication set
должны иметь primary key. Если какая-то из таблиц не удовлетворяет
этому условию, задержитесь на этом шаге и дайте каждой таблице primary
key командой ALTER TABLE ADD PRIMARY KEY.
Если столбца который мог бы стать primary key не находится, добавьте
новый столбец типа serial (ALTER TABLE ADD COLUMN), и заполните его
значениями. Настоятельно НЕ рекомендую использовать "table add key"
slonik-a.
Продолжаем.
Создаём таблицы и всё остальное на slave:
slony@customers_master$ pg_dump -s customers | psql -U slony -h customers_slave.com customers
pg_dump -s сдампит только структуру нашей БД.
pg_dump -s customers должен пускать без пароля, а вот для psql -U
slony -h customers_slave.com customers придётся набрать пароль
(slony_user_pass). Важно: я подразумеваю что сейчас на мастер-хосте
ещё не установлен Slony (речь не про make install), то есть в БД нет
таблиц sl_*, триггеров и прочего. Если есть, то возможно два варианта:
- добавляется узел в уже функционирующую систему репликации (читайте
раздел 5)
- это ошибка Тогда до переноса структуры на slave выполните
следующее:
slonik <<EOF
cluster name = customers_slave;
node Y admin conninfo = 'dbname=customers host=customers_master.com port=5432 user=slony password=slony_user_pass';
uninstall node (id = Y);
echo 'okay';
EOF
Y - число. Любое. Важно: если это действительно ошибка, cluster
name может иметь какой-то другое значение, например T1
(default). Нужно его выяснить и сделать uninstall.
Если структура уже перенесена (и это действительно ошибка),
сделайте uninstall с обоих узлов (с master и slave).
2.3 Инициализация кластера
Если Сейчас мы имеем два сервера PgSQL которые свободно "видят" друг друга по сети,
на одном из них находится мастер-база с данными, на другом - только структура.
На мастер-хосте запускаем такой скрипт:
#!/bin/sh
CLUSTER=customers_rep
DBNAME1=customers
DBNAME2=customers
HOST1=customers_master.com
HOST2=customers_slave.com
SLONY_USER=slony
slonik <<EOF
cluster name = $CLUSTER;
node 1 admin conninfo = 'dbname=$DBNAME1 host=$HOST1 port=5432 user=slony password=slony_user_password';
node 2 admin conninfo = 'dbname=$DBNAME2 host=$HOST2 port=5432 user=slony password=slony_user_password';
init cluster ( id = 1, comment = 'Customers DB replication cluster' );
echo 'Create set';
create set ( id = 1, origin = 1, comment = 'Customers DB replication set' );
echo 'Adding tables to the subscription set';
echo ' Adding table public.customers_sales...';
set add table ( set id = 1, origin = 1, id = 4, full qualified name = 'public.customers_sales', comment = 'Table public.customers_sales' );
echo ' done';
echo ' Adding table public.customers_something...';
set add table ( set id = 1, origin = 1, id = 5, full qualified name = 'public.customers_something, comment = 'Table public.customers_something );
echo ' done';
echo 'done adding';
store node ( id = 2, comment = 'Node 2, $HOST2' );
echo 'stored node';
store path ( server = 1, client = 2, conninfo = 'dbname=$DBNAME1 host=$HOST1 port=5432 user=slony password=slony_user_password' );
echo 'stored path';
store path ( server = 2, client = 1, conninfo = 'dbname=$DBNAME2 host=$HOST2 port=5432 user=slony password=slony_user_password' );
store listen ( origin = 1, provider = 1, receiver = 2 );
store listen ( origin = 2, provider = 2, receiver = 1 );
EOF
Здесь мы инициализируем кластер, создаём репликационный набор,
включаем в него две таблицы. And something else. Важно: нужно
перечислить все таблицы, которые нужно реплицировать, id таблицы в
наборе должен быть уникальным, таблицы должны иметь primary key.
Важно: replication set запоминается раз и навсегда. Чтобы добавить узел в
схему репликации не нужно заново инициализировать set.
Важно: если в набор добавляется или удаляется таблица нужно
переподписать все узлы. То есть сделать unsubscribe и subscribe заново
(см slonik-commands.html).
2.4 Подписываем slave-узел на replication set
Скрипт:
#!/bin/sh
CLUSTER=customers_rep
DBNAME1=customers
DBNAME2=customers
HOST1=customers_master.com
HOST2=customers_slave.com
SLONY_USER=slony
slonik <<EOF
cluster name = $CLUSTER;
node 1 admin conninfo = 'dbname=$DBNAME1 host=$HOST1 port=5432 user=slony password=slony_user_password';
node 2 admin conninfo = 'dbname=$DBNAME2 host=$HOST2 port=5432 user=slony password=slony_user_password';
echo'subscribing';
subscribe set ( id = 1, provider = 1, receiver = 2, forward = no);
EOF
2.5 Старт репликации
Теперь, на обоих узлах необходимо запустить демона репликации.
slony@customers_master$ slon customers_rep "dbname=customers user=slony"
и
slony@customers_slave$ slon customers_rep "dbname=customers user=slony"
Сейчас слоны обменяются сообщениями и начнут передачу
данных. Начальное наполнение происходит с помощью COPY, slave DB на
это время полностью блокируется.
В среднем время актуализации данных на slave-системе составляет до
10-ти секунд. slon успешно обходит проблемы со связью и подключением к
БД, и вообще требует к себе достаточно мало внимания.
3. Добавление ещё одного узла в работающую схему репликации
по пунктам:
выполнить 2.1
выполнить 2.2
Новый узел имеет id = 3. Находится на хосте customers_slave3.com,
"видит" мастер-сервер по сети и мастер может подключиться к его PgSQL.
после дублирования структуры (п 2.2) делаем следующее:
slonik <<EOF
cluster name = customers_slave;
node 3 admin conninfo = 'dbname=customers host=customers_slave3.com port=5432 user=slony password=slony_user_pass';
uninstall node (id = 3);
echo 'okay';
EOF
Это нужно чтобы удалить схему, триггеры и процедуры, которые были
сдублированы вместе с таблицами и структурой БД.
Инициализировать кластер не надо. Вместо этого записываем информацию о
новом узле в сети:
#!/bin/sh
CLUSTER=customers_rep
DBNAME1=customers
DBNAME3=customers
HOST1=customers_master.com
HOST3=customers_slave3.com
SLONY_USER=slony
slonik <<EOF
cluster name = $CLUSTER;
node 1 admin conninfo = 'dbname=$DBNAME1 host=$HOST1 port=5432 user=slony password=slony_user_pass';
node 3 admin conninfo = 'dbname=$DBNAME3 host=$HOST3 port=5432 user=slony password=slony_user_pass';
echo 'done adding';
store node ( id = 3, comment = 'Node 3, $HOST3' );
echo 'sored node';
store path ( server = 1, client = 3, conninfo = 'dbname=$DBNAME1 host=$HOST1 port=5432 user=slony password=slony_user_pass' );
echo 'stored path';
store path ( server = 3, client = 1, conninfo = 'dbname=$DBNAME3 host=$HOST3 port=5432 user=slony password=slony_user_pass' );
echo 'again';
store listen ( origin = 1, provider = 1, receiver = 3 );
store listen ( origin = 3, provider = 3, receiver = 1 );
EOF
Новый узел имеет id 3, потому что 2 уже есть и работает. Подписываем
новый узел 3 на replication set:
#!/bin/sh
CLUSTER=customers_rep
DBNAME1=customers
DBNAME3=customers
HOST1=customers_master.com
HOST3=customers_slave3.com
SLONY_USER=slony
slonik <<EOF
cluster name = $CLUSTER;
node 1 admin conninfo = 'dbname=$DBNAME1 host=$HOST1 port=5432 user=slony password=slony_user_pass';
node 3 admin conninfo = 'dbname=$DBNAME3 host=$HOST3 port=5432 user=slony password=slony_user_pass';
echo'subscribing';
subscribe set ( id = 1, provider = 1, receiver = 3, forward = no);
EOF
Теперь запускаем slon на новом узле, так же как и на
остальных. Перезапускать slon на мастере не надо.
slony@customers_slave3$ slon customers_rep "dbname=customers user=slony"
Репликация должна начаться как обычно.
3.1 Ошибка при добавлении узла в систему репликации
Периодически, при добавлении новой машины в кластер возникает
следующая ошибка: на новой ноде всё начинает жужжать и работать,
имеющиеся же отваливаются с примерно следующей диагностикой:
%slon customers_rep "dbname=customers user=slony_user"
CONFIG main: slon version 1.0.5 starting up
CONFIG main: local node id = 3
CONFIG main: loading current cluster configuration
CONFIG storeNode: no_id=1 no_comment='CustomersDB replication cluster'
CONFIG storeNode: no_id=2 no_comment='Node 2, node2.example.com'
CONFIG storeNode: no_id=4 no_comment='Node 4, node4.example.com'
CONFIG storePath: pa_server=1 pa_client=3 pa_conninfo="dbname=customers host=mainhost.com port=5432 user=slony_user password=slony_user_pass" pa_connretry=10
CONFIG storeListen: li_origin=1 li_receiver=3 li_provider=1
CONFIG storeSet: set_id=1 set_origin=1 set_comment='CustomersDB replication set'
WARN remoteWorker_wakeup: node 1 - no worker thread
CONFIG storeSubscribe: sub_set=1 sub_provider=1 sub_forward='f'
WARN remoteWorker_wakeup: node 1 - no worker thread
CONFIG enableSubscription: sub_set=1
WARN remoteWorker_wakeup: node 1 - no worker thread
CONFIG main: configuration complete - starting threads
CONFIG enableNode: no_id=1
CONFIG enableNode: no_id=2
CONFIG enableNode: no_id=4
ERROR remoteWorkerThread_1: "begin transaction; set transaction isolation level serializable; lock table "_customers_rep".sl_config_lock; select "_customers_rep".enableSubscription(1, 1, 4); notify "_customers_rep_Event"; notify "_customers_rep_Confirm"; insert into "_customers_rep".sl_event (ev_origin, ev_seqno, ev_timestamp, ev_minxid, ev_maxxid, ev_xip, ev_type , ev_data1, ev_data2, ev_data3, ev_data4 ) values ('1', '219440', '2005-05-05 18:52:42.708351', '52501283', '52501292', '''52501283''', 'ENABLE_SUBSCRIPTION', '1', '1', '4', 'f'); insert into "_customers_rep".sl_confirm (con_origin, con_received, con_seqno, con_timestamp) values (1, 3, '219440', CURRENT_TIMESTAMP); commit transaction;" PGRES_FATAL_ERROR ERROR: insert or update on table "sl_subscribe" violates foreign key constraint "sl_subscribe-sl_path-ref"
DETAIL: Key (sub_provider,sub_receiver)=(1,4) is not present in table "sl_path".
INFO remoteListenThread_1: disconnecting from 'dbname=customers host=mainhost.com port=5432 user=slony_user password=slony_user_pass'
%
Это означает что в служебной таблице _<имя
кластера>.sl_path;, например _customers_rep.sl_path
на уже имеющихся узлах отсутствует информация о новом узле. В данном
случае, id нового узла 4, пара (1,4) в sl_path отсутствует.
Видимо, это баг Slony. Как избежать этого и последующих ручных
вмешательств пока не ясно.
Чтобы это устранить, нужно выполнить на каждом из имеющихся узлов
приблизительно следующий запрос (добавить путь, в данном случае (1,4)):
slony_user@masterhost$ psql -d customers -h _every_one_of_slaves -U slony
customers=# insert into _customers_rep.sl_path values ('1','4','dbname=customers host=mainhost.com port=5432 user=slony_user password=slony_user_password,'10');
Если возникают затруднения, да и вообще для расширения кругозора
можно посмотреть на служебные таблицы и их содержимое. Они не видны
обычно и находятся в рамках пространства имён _<имя
кластера>, например _customers_rep.
4. Что делать если репликация со временем начинает тормозить
В процессе эксплуатации наблюдаю как со временем растёт нагрузка на
master-сервере, в списке активных бекендов - постоянные SELECT-ы со
слейвов. В pg_stat_activity видим примерно такие запросы:
select ev_origin, ev_seqno, ev_timestamp, ev_minxid, ev_maxxid, ev_xip, ev_type, ev_data1, ev_data2, ev_data3, ev_data4, ev_data5, ev_data6, ev_data7, ev_data8 from "_customers_rep".sl_event e where (e.ev_origin = '2' and e.ev_seqno > '336996') or (e.ev_origin = '3' and e.ev_seqno > '1712871') or (e.ev_origin = '4' and e.ev_seqno > '721285') or (e.ev_origin = '5' and e.ev_seqno > '807715') or (e.ev_origin = '1' and e.ev_seqno > '3544763') or (e.ev_origin = '6' and e.ev_seqno > '2529445') or (e.ev_origin = '7' and e.ev_seqno > '2512532') or (e.ev_origin = '8' and e.ev_seqno > '2500418') or (e.ev_origin = '10' and e.ev_seqno > '1692318') order by e.ev_origin, e.ev_seqno;
Не забываем что _customers_rep - имя схемы из примера, у вас будет
другое имя.
Таблица sl_event почему-то разрастается со временем, замедляя выполнение этих запросов до неприемлемого времени. Удаляем ненужные записи:
delete from _customers_rep.sl_event where ev_timestamp<NOW()-'1 DAY'::interval;
Производительность должна вернуться к изначальным
значениям. Возможно имеет смысл почистить таблицы
_customers_rep.sl_log_* где вместо звёздочки подставляются натуральные
числа, по-видимому по количеству репликационных сетов, так что
_customers_rep.sl_log_1 точно должна существовать.
Комментарии
не происходит синхронизация slave-сервера с мастером
Все сделано по этой инструкции, с поправкой на версию slony. Все прошло, все запустилось, но после запуска самого демона на обоих серверах, сами демоны работают, а синхронизации не происходит. В режиме отладки на консоль бесконечно пишется:
2012-09-17 01:51:57 JST DEBUG2 remoteWorkerThread_1: SYNC 5000002857 processing
2012-09-17 01:51:57 JST DEBUG1 remoteWorkerThread_1: no sets need syncing for this event
2012-09-17 01:51:59 JST DEBUG2 remoteListenThread_1: queue event 1,5000002858 SYNC
2012-09-17 01:51:59 JST DEBUG2 remoteWorkerThread_1: Received event #1 from 5000002858 type:SYNC
2012-09-17 01:51:59 JST DEBUG1 calc sync size - last time: 1 last length: 2002 ideal: 29 proposed size: 3
2012-09-17 01:51:59 JST DEBUG2 remoteWorkerThread_1: SYNC 5000002858 processing
2012-09-17 01:51:59 JST DEBUG1 remoteWorkerThread_1: no sets need syncing for this event
2012-09-17 01:52:00 JST CONFIG slon: child terminated signal: 9; pid: 27853, current worker pid: 27853
И за эти ничего больше не происходит. Может кто-то подскажет как это лечить и заставить слона работать?