forked from le0pard/postgresql_book
-
Notifications
You must be signed in to change notification settings - Fork 1
/
postgresql_replication_slony.tex
466 lines (377 loc) · 22.4 KB
/
postgresql_replication_slony.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
\section{Slony-I}
\subsection{Введение}
Slony это система репликации реального времени, позволяющая организовать синхронизацию нескольких серверов
PostgreSQL по сети. Slony использует триггеры Postgre для привязки к событиям INSERT/ DELETE/UPDATE и
хранимые процедуры для выполнения действий.
Система Slony с точки зрения администратора состоит из двух главных компонент, репликационного демона slony и
административной консоли slonik. Администрирование системы сводится к общению со slonik-ом, демон slon только
следит за собственно процессом репликации. А админ следит за тем, чтобы slon висел там, где ему положено.
\subsubsection{О slonik-e}
Все команды slonik принимает на свой stdin. До начала выполнения скрипт slonik-a проверяется на соответствие синтаксису,
если обнаруживаются ошибки, скрипт не выполняется, так что можно не волноваться если slonik сообщает о syntax error,
ничего страшного не произошло. И он ещё ничего не сделал. Скорее всего.
\subsection{Установка}
Установка на Ubuntu производится простой командой:
\begin{verbatim}
sudo aptitude install slony1-bin
\end{verbatim}
\subsection{Настройка}
\label{sec:slonyI}
Рассмотрим теперь установку на гипотетическую базу данных customers
(названия узлов, кластеров и таблиц являются вымышленными).
Наши данные
\begin{itemize}
\item БД: customers
\item master\_host: customers\_master.com
\item slave\_host\_1: customers\_slave.com
\item cluster name (нужно придумать): customers\_rep
\end{itemize}
\subsubsection{Подготовка master-сервера}
Для начала нам нужно создать пользователя Postgres, под которым будет действовать Slony.
По умолчанию, и отдавая должное системе, этого пользователя обычно называют slony.
\begin{verbatim}
pgsql@customers_master$ createuser -a -d slony
pgsql@customers_master$ psql -d template1 -c "alter \
user slony with password 'slony_user_password';"
\end{verbatim}
Также на каждом из узлов лучше завести системного пользователя slony, чтобы запускать от его имени
репликационного демона slon. В дальнейшем подразумевается, что он (и пользователь и slon) есть на
каждом из узлов кластера.
\subsubsection{Подготовка одного slave-сервера}
Здесь я рассматриваю, что серверы кластера соединены посредством сети Internet (как в моём случае), необходимо
чтобы с каждого из ведомых серверов можно было установить соединение с PostgreSQL на мастер-хосте, и наоборот.
То есть, команда:
\begin{verbatim}
anyuser@customers_slave$ psql -d customers \
-h customers_master.com -U slony
\end{verbatim}
должна подключать нас к мастер-серверу (после ввода пароля, желательно). Если что-то не так, возможно требуется
поковыряться в настройках firewall-a, или файле pg\_hba.conf, который лежит в \$PGDATA.
Теперь устанавливаем на slave-хост сервер PostgreSQL. Следующего обычно не требуется, сразу после установки Postgres
<<up and ready>>, но в случае каких-то ошибок можно начать <<с чистого листа>>, выполнив следующие команды
(предварительно сохранив конфигурационные файлы и остановив postmaster):
\begin{verbatim}
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';"
\end{verbatim}
Запускаем postmaster.
Внимание! Обычно требуется определённый владелец для реплицируемой БД. В этом случае необходимо завести его тоже!
\begin{verbatim}
pgsql@customers_slave$ createuser -a -d customers_owner
pgsql@customers_slave$ psql -d template1 -c "alter \
user customers_owner with password 'customers_owner_password';"
\end{verbatim}
Эти две команды можно запускать с customers\_master, к командной строке в этом случае нужно добавить
<<-h customers\_slave>>, чтобы все операции выполнялись на slave.
На slave, как и на master, также нужно установить Slony.
\subsubsection{Инициализация БД и plpgsql на slave}
Следующие команды выполняются от пользователя slony. Скорее всего для выполнения каждой из них потребуется
ввести пароль (slony\_user\_password). Итак:
\begin{verbatim}
slony@customers_master$ createdb -O customers_owner \
-h customers_slave.com customers
slony@customers_master$ createlang -d customers \
-h customers_slave.com plpgsql
\end{verbatim}
Внимание! Все таблицы, которые будут добавлены в replication set должны иметь primary key.
Если какая-то из таблиц не удовлетворяет этому условию, задержитесь на этом шаге и дайте каждой таблице primary key
командой ALTER TABLE ADD PRIMARY KEY.
Если столбца который мог бы стать primary key не находится, добавьте новый столбец типа serial (ALTER TABLE ADD COLUMN),
и заполните его значениями. Настоятельно НЕ рекомендую использовать <<table add key>> slonik-a.
Продолжаем.
Создаём таблицы и всё остальное на slave:
\begin{verbatim}
slony@customers_master$ pg_dump -s customers | \
psql -U slony -h customers_slave.com customers
\end{verbatim}
pg\_dump -s сдампит только структуру нашей БД.
pg\_dump -s customers должен пускать без пароля, а вот для psql -U slony -h customers\_slave.com
customers придётся набрать пароль (slony\_user\_pass). Важно: я подразумеваю что сейчас на мастер-хосте
ещё не установлен Slony (речь не про make install), то есть в БД нет таблиц sl\_*, триггеров и прочего.
Если есть, то возможно два варианта:
\begin{itemize}
\item добавляется узел в уже функционирующую систему репликации (читайте раздел 5)
\item это ошибка :-) Тогда до переноса структуры на slave выполните следующее:
\begin{verbatim}
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
\end{verbatim}
Y~--- число. Любое. Важно: если это действительно ошибка, cluster name может иметь какой-то другое значение, например T1
(default). Нужно его выяснить и сделать uninstall.
Если структура уже перенесена (и это действительно ошибка), сделайте uninstall с обоих узлов (с master и slave).
\end{itemize}
\subsubsection{Инициализация кластера}
Если Сейчас мы имеем два сервера PgSQL которые свободно <<видят>> друг друга по сети,
на одном из них находится мастер-база с данными, на другом~--- только структура.
На мастер-хосте запускаем такой скрипт:
\begin{verbatim}
#!/bin/sh
CLUSTER=customers_rep
DBNAME1=customers
DBNAME2=customers
HOST1=customers_master.com
HOST2=customers_slave.com
PORT1=5432
PORT2=5432
SLONY_USER=slony
slonik <<EOF
cluster name = $CLUSTER;
node 1 admin conninfo = 'dbname=$DBNAME1 host=$HOST1 port=$PORT1
user=slony password=slony_user_password';
node 2 admin conninfo = 'dbname=$DBNAME2 host=$HOST2
port=$PORT2 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=$PORT1 user=slony password=slony_user_password' );
echo 'stored path';
store path ( server = 2, client = 1, conninfo = 'dbname=$DBNAME2 host=$HOST2
port=$PORT2 user=slony password=slony_user_password' );
store listen ( origin = 1, provider = 1, receiver = 2 );
store listen ( origin = 2, provider = 2, receiver = 1 );
EOF
\end{verbatim}
Здесь мы инициализируем кластер, создаём репликационный набор, включаем в него две таблицы.
Важно: нужно перечислить все таблицы, которые нужно реплицировать, id таблицы в наборе должен быть уникальным,
таблицы должны иметь primary key.
Важно: replication set запоминается раз и навсегда. Чтобы добавить узел в схему репликации не нужно заново инициализировать set.
Важно: если в набор добавляется или удаляется таблица нужно переподписать все узлы.
То есть сделать unsubscribe и subscribe заново.
\subsubsection{Подписываем slave-узел на replication set}
Скрипт:
\begin{verbatim}
#!/bin/sh
CLUSTER=customers_rep
DBNAME1=customers
DBNAME2=customers
HOST1=customers_master.com
HOST2=customers_slave.com
PORT1=5432
PORT2=5432
SLONY_USER=slony
slonik <<EOF
cluster name = $CLUSTER;
node 1 admin conninfo = 'dbname=$DBNAME1 host=$HOST1
port=$PORT1 user=slony password=slony_user_password';
node 2 admin conninfo = 'dbname=$DBNAME2 host=$HOST2
port=$PORT2 user=slony password=slony_user_password';
echo'subscribing';
subscribe set ( id = 1, provider = 1, receiver = 2, forward = no);
EOF
\end{verbatim}
\subsubsection{Старт репликации}
Теперь, на обоих узлах необходимо запустить демона репликации.
\begin{verbatim}
slony@customers_master$ slon customers_rep \
"dbname=customers user=slony"
\end{verbatim}
и
\begin{verbatim}
slony@customers_slave$ slon customers_rep \
"dbname=customers user=slony"
\end{verbatim}
Сейчас слоны обменяются сообщениями и начнут передачу данных. Начальное наполнение происходит с помощью COPY,
slave DB на это время полностью блокируется.
В среднем время актуализации данных на slave-системе составляет до 10-ти секунд.
slon успешно обходит проблемы со связью и подключением к БД, и вообще требует к
себе достаточно мало внимания.
\subsection{Общие задачи}
\subsubsection{Добавление ещё одного узла в работающую схему репликации}
Выполнить~\ref{sec:slonyI}.1 и выполнить~\ref{sec:slonyI}.2.
Новый узел имеет id = 3. Находится на хосте customers\_slave3.com, <<видит>> мастер-сервер по сети и
мастер может подключиться к его PgSQL.
после дублирования структуры (п~\ref{sec:slonyI}.2) делаем следующее:
\begin{verbatim}
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
\end{verbatim}
Это нужно чтобы удалить схему, триггеры и процедуры, которые были сдублированы вместе с таблицами и структурой БД.
Инициализировать кластер не надо. Вместо этого записываем информацию о новом узле в сети:
\begin{verbatim}
#!/bin/sh
CLUSTER=customers_rep
DBNAME1=customers
DBNAME3=customers
HOST1=customers_master.com
HOST3=customers_slave3.com
PORT1=5432
PORT2=5432
SLONY_USER=slony
slonik <<EOF
cluster name = $CLUSTER;
node 1 admin conninfo = 'dbname=$DBNAME1 host=$HOST1
port=$PORT1 user=slony password=slony_user_pass';
node 3 admin conninfo = 'dbname=$DBNAME3
host=$HOST3 port=$PORT2 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=$PORT1 user=slony password=slony_user_pass' );
echo 'stored path';
store path ( server = 3, client = 1, conninfo = 'dbname=$DBNAME3
host=$HOST3 port=$PORT2 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
\end{verbatim}
Новый узел имеет id 3, потому что 2 уже есть и работает. Подписываем новый узел 3 на replication set:
\begin{verbatim}
#!/bin/sh
CLUSTER=customers_rep
DBNAME1=customers
DBNAME3=customers
HOST1=customers_master.com
HOST3=customers_slave3.com
PORT1=5432
PORT2=5432
SLONY_USER=slony
slonik <<EOF
cluster name = $CLUSTER;
node 1 admin conninfo = 'dbname=$DBNAME1 host=$HOST1
port=$PORT1 user=slony password=slony_user_pass';
node 3 admin conninfo = 'dbname=$DBNAME3 host=$HOST3
port=$PORT2 user=slony password=slony_user_pass';
echo'subscribing';
subscribe set ( id = 1, provider = 1, receiver = 3, forward = no);
EOF
\end{verbatim}
Теперь запускаем slon на новом узле, так же как и на остальных. Перезапускать slon на мастере не надо.
\begin{verbatim}
slony@customers_slave3$ slon customers_rep \
"dbname=customers user=slony"
\end{verbatim}
Репликация должна начаться как обычно.
\subsection{Устранение неисправностей}
\subsubsection{Ошибка при добавлении узла в систему репликации}
Периодически, при добавлении новой машины в кластер возникает следующая ошибка: на новой ноде всё начинает
жужжать и работать, имеющиеся же отваливаются с примерно следующей диагностикой:
\begin{verbatim}
%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'
%
\end{verbatim}
Это означает что в служебной таблице \_<имя кластера>.sl\_path;, например
\_customers\_rep.sl\_path на уже имеющихся узлах отсутствует информация о новом узле. В данном случае,
id нового узла 4, пара (1,4) в sl\_path отсутствует.
Видимо, это баг Slony. Как избежать этого и последующих ручных вмешательств пока не ясно.
Чтобы это устранить, нужно выполнить на каждом из имеющихся узлов приблизительно следующий запрос
(добавить путь, в данном случае (1,4)):
\begin{verbatim}
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');
\end{verbatim}
Если возникают затруднения, да и вообще для расширения кругозора можно посмотреть на служебные таблицы
и их содержимое. Они не видны обычно и находятся в рамках пространства имён \_<имя кластера>,
например \_customers\_rep.
\subsubsection{Что делать если репликация со временем начинает тормозить}
В процессе эксплуатации наблюдаю как со временем растёт нагрузка на master-сервере, в списке активных бекендов~---
постоянные SELECT-ы со слейвов. В pg\_stat\_activity видим примерно такие запросы:
\begin{verbatim}
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;
\end{verbatim}
Не забываем что \_customers\_rep~--- имя схемы из примера, у вас будет другое имя.
Таблица sl\_event почему-то разрастается со временем, замедляя выполнение этих
запросов до неприемлемого времени. Удаляем ненужные записи:
\begin{verbatim}
delete from _customers_rep.sl_event where
ev_timestamp<NOW()-'1 DAY'::interval;
\end{verbatim}
Производительность должна вернуться к изначальным значениям.
Возможно имеет смысл почистить таблицы \_customers\_rep.sl\_log\_* где
вместо звёздочки подставляются натуральные числа, по-видимому по
количеству репликационных сетов, так что \_customers\_rep.sl\_log\_1
точно должна существовать.