Slony-I FAQ

The Slony Global Development Group

Christopher Browne


厳密に言うと、これら全てが"よく質問される"訳ではありません。いくつかはドキュメント化しておいた方がよいと思われる発見された問題も載せてあります。

1. Slony-I 1.1 を構築しようとしましたが、以下のようなエラーになりました。

configure: error: Headers for libpqserver are not found in the includeserverdir.
   This is the path to postgres.h. Please specify the includeserverdir with
   --with-pgincludeserverdir=<dir>

2. _clustername名前空間を探したのですが、見つかりませんでした。
3. 一部のイベントは通信できていますが、リプリケーションが行われません。
4. "-"を含む名前のクラスタを作成しようとしましたが、うまく動きませんでした。
5. slon クラッシュの後、再起動しません。
6. psコマンドを実行すると、コマンドラインにパスワードが表示されてしまいます。
7. Slonikが、PostgreSQLライブラリをロードできずに、PGRES_FATAL_ERROR load '$libdir/xxid';と失敗してしまいます。
8. FQ名前空間の名前をもつテーブル・インデックス

set add table (set id = 1, origin = 1, id = 27, 
               full qualified name = 'nspace.some_table', 
               key = 'key_on_whatever', 
               comment = 'Table some_table in namespace nspace with a candidate primary key');

9. スレーブを予約させるよう試みていますが、ログには以下のメッセージが記録されます。

DEBUG1 copy_set 1
DEBUG1 remoteWorkerThread_1: connected to provider DB
WARN	remoteWorkerThread_1: transactions earlier than XID 127314958 are still in progress
WARN	remoteWorkerThread_1: data copy for set 1 failed - sleep 60 seconds

10. ERROR: duplicate key violates unique constraint "sl_table-pkey"
11. リプリケーションセットからテーブルを削除しなければなりません。
12. リプリケーションセットからシーケンスを削除しなければなりません。
13. Slony-I: 現在の定期購読セット1にテーブルを追加できません。
14. 一部のノードの一貫性が壊れはじめました。
15. pg_dumpを使用してバックアップを始めたところ、Slonyが突然止まりました。
16. slonのコミットに時間がかかります。また、同期させるのにも長時間かかります。
17. 定期購読中のノードを他のプロバイダに変更しましたが、複製中に止まってしまいます。
18. ノードを削除した後、sl_log_1が消去されなくなりました。
19. リプリケーションの失敗。一意性制約違反
20. 以下のようなslonikスクリプトを考えていますが、ハングアップし、完了しません。 なぜなら、tryブロック内ではwait for eventを持つことができない、および、待機しているイベントはそのトランザクションに届かないからです。
21. セットにおけるテーブルの順序は重要ですか?
22. Slony-Iで複製されたテーブルにおけるルールとトリガはどう動作するのですか?
23. のノードにおける定期購読通知の後、リプリケーションが以下のようなエラーメッセージにより失敗します。
24. MOVE SETを使ってオリジナルを新しいノードに移動しました。 残念なことに一部の定期購読ノードが以前のオリジナルノードを参照しています。 このため、更新の受付を止めない限り、そのノードを保守作業のためにサービスから取り出すことができません。 どうすればいいでしょうか?
25. リプリケーションが低速になりました。 FETCH 100 FROM LOG という問い合わせが長時間動いていることが分かっています。 sl_log_1が大きくなり、性能はだんだんと悪化しています。
26. 動作:すべての定期購読ノードがオリジナルの背後に落ち始めます。 そして、定期購読ノードのログには以下のようなエラーメッセージが繰り返し記録されます。 (この現象に遭遇した時、かなり長めのSQL文が各項目に記録されていました。)
27. ノードにおける定期購読通知の後、ある定期購読ノードにおいてリプリケーションが以下のエラーで失敗します。
28. SUBSCRIBE SETの実行よりかなり高速にpg_dumpを行い、データをロードすることができます。 なぜ?
29. ネットワーク"障害"のため、FAILOVERが動き、他のノードへフェイルオーバーしました。 データベースを破壊するようなディスク障害ではありません。 なぜ一から障害発生ノードを再構築しなければならないのですか?
30. リプリケーションさせるためにslonyという"スーパーユーザ"アカウントを作成しました。 推奨されたとおり、以下の問い合わせを使ってスーパーユーザの設定を行いました。 update pg_shadow set usesuper = 't' where usename in ('slony', 'molly', 'dumpy'); (また、このコマンドで他にも、バキューム用およびバックアップ用のユーザも扱いました。)
31. マスタとスレーブからslonyリプリケーションクラスタを完全にアンインストールしても、予期できない何かが刺さっています。
32. ノード1をDROP NODEを使用して削除したところ、他のノードのslonが以下のようなエラーメッセージで失敗し続けるようになりました。
33. FFPM (Frotznik Freenix Package Manager)パッケージ管理システムで管理された Frotznik Freenix 4.5を使用しています。 FFPMにはPostgreSQL 7.4.7が付随しており、これをデータベースとして使用していますが、このパッケージにはSlony-Iが含まれていません。 どのようにSlony-Iを追加するのでしょうか?
34. Bug #1226 では、 単なるシーケンスから構成されるリプリケーションセットの場合に、発生する可能性があるエラー条件を示しています。
35. 以下の問い合わせを試しましたが、動作しません。
36. 7.2ベースのPostgreSQLシステムがあります。 Slony-I本当に使用したいのですが、8.0にアップグレードしたいと考えています。 Slony-Iを動作させるためには何を行えばいいのでしょうか?

1. Slony-I 1.1 を構築しようとしましたが、以下のようなエラーになりました。

configure: error: Headers for libpqserver are not found in the includeserverdir.
   This is the path to postgres.h. Please specify the includeserverdir with
   --with-pgincludeserverdir=<dir>

おそらくPostgreSQL 7.4以前のバージョンを使用していると思います。 この場合、単にPostgreSQLmake installを行うだけでは、サーバ用のヘッダファイルはデフォルトでインストールされません。

PostgreSQLのインストール時にmake install-all-headersコマンドを実行して、サーバ用のヘッダファイルをインストールしなければなりません。

2. _clustername名前空間を探したのですが、見つかりませんでした。

もしDSNが誤っていれば、slonインスタンスは、ノードに接続することができません。

通常この場合、ノードを変更させることが完全にできません。

接続構成を再度検査してください。 また、slonはlibpqとリンクしていますので、 $HOME/.pgpassに保存されたパスワード情報が使われます。 このファイルの認証情報が正しいかどうかも確認してください。

3. 一部のイベントは通信できていますが、リプリケーションが行われません。

以下のようなSlonyのログが残っていませんか。

DEBUG1 remoteListenThread_1: connected to 'host=host004 dbname=pgbenchrep user=postgres port=5432'
ERROR  remoteListenThread_1: "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 "_pgbenchtest".sl_event e where 
	  (e.ev_origin = '1' and e.ev_seqno > '1') 
	order by e.ev_origin, e.ev_seqno" 
	- could not receive data from server: Operation now in progress

AIXおよびSolaris(他にもあるかもしれませんが)では、Slony-IおよびPostgreSQLの両方を--enable-thread-safetyオプションをつけてコンパイルしなければなりません。 PostgreSQLがそのようにコンパイルされてないときに上記の結果となります。

この障害は、(スレッドセーフの)libcと(非スレッドセーフの)libpqが異なるメモリ空間でerrnoを扱うことが原因です。 このため、要求が失敗してしまいます。

このような問題は、AIXとSolarisでしばしば起こります。 すべての必要なコンポーネントが--enable-thread-safetyオプション付きでコンパイル・リンクしていることを確認するのに、ちょっとした"オブジェクト・コード監査"を要するかもしれません。

以下に私が体験した問題を例として示します。 Solarisで古いPostgreSQLを使ってコンパイルしたライブラリを指し示すようにLD_LIBRARY_PATHを設定していました。 この結果、データベースを--enable-thread-safety付きでコンパイルし、slonも同様にコンパイルしたとしても、slonは、"間違ってコンパイルされた非スレッドセーフの古いバージョン"と動的にリンクしてしまい、正常に動作しません。 slonに対してlddを実行するまで、何が原因なのかが分かりませんでした。

Solarisでは、バージョン7.4.2のlibpqに更にスレッド・パッチが必要ですので注意してください。 PostgreSQL バージョン 8.0でも同様に必要です。

4. "-"を含む名前のクラスタを作成しようとしましたが、うまく動きませんでした。

Slony-Iは、 引用符のない識別子に関して、PostgreSQLのメインパーサと同じ規則を使用します。 そのため、識別子の名前には"-"を含めてはいけません。

識別子の名前を"引用符"で括ることで、この問題を解決できるかもしれません。 しかし、まだ問題が発生する可能性が潜在しています。 ですので、この回避方法を取る価値はおそらくありません。

5. slon クラッシュの後、再起動しません。

postgresqlを即座に停止(システムクラッシュを模擬)すると、pg_catalog.pg_listenerにrelname='_${cluster_name}_Restart'となるタプルが残ります。 このノードで他のプロセスがクラスタをサービスしているとみなされますので、slonを起動できません。 どうすればいいのでしょうか? このリレーションから問題のタプルを削除することはできません。

ログには、

別のslonデーモンがすでにこのノードを供給している

と記録されています。

問題は、pg_catalog.pg_listenerシステムテーブルに存在しないバックエンドを参照する項目が残っていることです。 PostgreSQLはこのテーブルを使用してイベント通知を管理します。 新しいslonインスタンスはデータベースと接続しますが、こうした項目があることより、古いslonがすでにこの Slony-Iノードをサービスしていると認識します。

このテーブルの"屑"を破棄しなければなりません。

このような場合、slonikスクリプトに以下のようなコマンドを入れておくと便利です。

twcsds004[/opt/twcsds004/OXRS/slony-scripts]$ cat restart_org.slonik 
cluster name = oxrsorg ;
node 1 admin conninfo = 'host=32.85.68.220 dbname=oxrsorg user=postgres port=5532';
node 2 admin conninfo = 'host=32.85.68.216 dbname=oxrsorg user=postgres port=5532';
node 3 admin conninfo = 'host=32.85.68.244 dbname=oxrsorg user=postgres port=5532';
node 4 admin conninfo = 'host=10.28.103.132 dbname=oxrsorg user=postgres port=5532';
restart node 1;
restart node 2;
restart node 3;
restart node 4;

RESTART NODEは不要な通知を消去しますので、このノードを再起動させることができます。

バージョン1.0.5のslonの起動処理では、このような状態を探し、自動的に消去します。

6. psコマンドを実行すると、コマンドラインにパスワードが表示されてしまいます。

psコマンドを実行すると、全員がコマンドラインでパスワードを見ることができます。

パスワードをSlony構成から取り除き、$(HOME)/.pgpass.内に格納してください。

7. Slonikが、PostgreSQLライブラリをロードできずに、PGRES_FATAL_ERROR load '$libdir/xxid';と失敗してしまいます。

セットアップ・スクリプトのサンプルを実行すると、以下のようなエラー・メッセージが表示されます。

stdin:64: PGRES_FATAL_ERROR load '$libdir/xxid';  - ERROR:  LOAD:
could not open file '$libdir/xxid': No such file or directory

明白に、PostgreSQLインスタンスが使用する$libdirディレクトリ内にxxid.soライブラリがありません。 Slony-Iコンポーネントが、オリジナルノードだけではなく、すべてのノードでPostgreSQL ソフトウェアのインストールを必要としていることに注意してください。

また、これは、PostgreSQLバイナリインスタンスとSlony-Iインスタンスとの間の、他の不整合を示しているかもしれません。 複数のPostgreSQLのバイナリが"存在する"可能性があるマシンでSlony-Iを自身でコンパイルした場合、slonやslonikバイナリが、使用中のPostgreSQLデータベースクラスタ用のライブラリディレクトリに存在しない、他の何かをロードしようとしているかもしれません。

要点は、そのマシンに存在するPostgreSQLSlony-Iがどのようにインストールされているかを"監査"しなければならないということです。 残念なことに、何らかの不整合だけで物事が正しく動きません。 Solarisのスレッドに関する問題についてはスレッドセーフを参照してください。

8. FQ名前空間の名前をもつテーブル・インデックス

set add table (set id = 1, origin = 1, id = 27, 
               full qualified name = 'nspace.some_table', 
               key = 'key_on_whatever', 
               comment = 'Table some_table in namespace nspace with a candidate primary key');

key ='nspace.key_on_whatever'があると、この要求は失敗するでしょう。

9. スレーブを予約させるよう試みていますが、ログには以下のメッセージが記録されます。

DEBUG1 copy_set 1
DEBUG1 remoteWorkerThread_1: connected to provider DB
WARN	remoteWorkerThread_1: transactions earlier than XID 127314958 are still in progress
WARN	remoteWorkerThread_1: data copy for set 1 failed - sleep 60 seconds

説明し忘れていましたが、同時に2つの定期購読ノードを加えようとしていました。

それはうまくいきません。 Slony-Iは、COPYコマンドを同時に取り扱いません。 src/slon/remote_worker.ccopy_set()関数を見てください。

これは(残念ながらおそらく)シングルマスタから2つのスレーブに同時にデータを投入できないことを意味しています。 まず1つをセットに予約し、その定期購読の設定(テーブルの内容などのコピー)が完了した後にのみ、2つ目の定期購読設定を開始することができます。

また、Slony-I の同期処理をブロックするトランザクションがすでに存在している可能性があります。 何が起きているかを確認するためにpg_locksを参照してください。

sampledb=# select * from pg_locks where transaction is not null order by transaction;
 relation | database | transaction |  pid    |     mode      | granted 
----------+----------+-------------+---------+---------------+---------
          |          |   127314921 | 2605100 | ExclusiveLock | t
          |          |   127326504 | 5660904 | ExclusiveLock | t
(2 rows)

分かりますか? 実際、127314958より古い127314921がまだ動いています。

$ ps -aef | egrep '[2]605100'
postgres 2605100  205018	0 18:53:43  pts/3  3:13 postgres: postgres sampledb localhost COPY 

ノードの1つに対する定期購読の設定のためのCOPYトランザクションが動いていることを示しています。 つまり、システムは最初の定期購読ノードの設定のためにビジー状態となり、この最初の定期購読が終わるまで、2番目の定期購読を始めることができません。

ところで、PostgreSQLクラスタに複数のデータベースがあり、他のデータベースにおける活動が行われていたとすると、進行中となっているもの"のXIDより古いトランザクション"が存在することになります。 クラスタ内の別のデータベースであることは関係ありません。 Slony-Iは、こうした古いトランザクションが完了するまで待機します。

10. ERROR: duplicate key violates unique constraint "sl_table-pkey"

2番目のリプリケーションセットを設定しようとしたが、以下のようなエラーになってしまいました。

stdin:9: Could not create subscription set 2 for oxrslive!
stdin:11: PGRES_FATAL_ERROR select "_oxrslive".setAddTable(2, 1, 'public.replic_test', 'replic_test__Slony-I_oxrslive_rowID_key', 'Table public.replic_test without primary key');  - ERROR:  duplicate key violates unique constraint "sl_table-pkey"
CONTEXT:  PL/pgSQL function "setaddtable_int" line 71 at SQL statement

SET ADD TABLEで使用しているテーブルIDは、すべてのセットに渡って一意でなければなりません。 したがって、2番目のセットで番号付けを1から振りなおすことはできません。 連続的に番号付けしているならば、その後のセットは前のセットが設定した値の後ろの番号からIDを始めなければなりません。

11. リプリケーションセットからテーブルを削除しなければなりません。

いくつか方法があります(完全に希望するものではないけれどね;-))。

  • リプリケーションセット全体を削除し、必要なテーブルのみで再作成することができます。 残念ながら、これは大量のデータ全体の再コピーが発生しますし、実行している間セットの残りのクラスタを使用することができません。

  • 1.0.5以降を使用しているならば、"これを行う"SET DROP TABLEコマンドがあります。

  • 1.0.1や1.0.2を使用しているのであれば、SET DROP TABLEの基本機能にはdroptable_int()の機能が含まれています。 削除したいテーブルのテーブルIDをsl_tableから見つけ出し、以下の3個の問い合わせを各ホストで実行することでこれを手作業で誤魔化すことができます。

      select _slonyschema.alterTableRestore(40);
      select _slonyschema.tableDropKey(40);
      delete from _slonyschema.sl_table where tab_id = 40;

    上の図式は、どのようにSlony-Iクラスタを定義したかに明らかに依存します。 テーブルID(上では40です)は、削除したいテーブルのIDに変更しなければなりません。

    この3つの問い合わせをすべてのノードで実行しなければなりませんが、まず、この削除が適切に伝播するようにオリジナルのノードで実行することを勧めます。 slonik文経由で行うことで、新しいSlony-Iイベントが発生し、これを行います。 EXECUTE SCRIPTを使用してこの3つの問い合わせを提出しても、これを行うことができます。 また、各データベースに接続して問い合わせを手で実行しても、これを行うことができます。

12. リプリケーションセットからシーケンスを削除しなければなりません。

1.0.5以降を使用しているのであれば、SET DROP TABLEに似たSlonikのSET DROP SEQUENCEコマンドで行うことができます。

1.0.5以前を使用しているのであれば、この処理には多少の手作業が必要です。

whois_cachemgmt_seqepp_whoi_cach_seq_という2つのシーケンスを削除するものとします。 まずseq_idの値が必要になります。

oxrsorg=# select * from _oxrsorg.sl_sequence  where seq_id in (93,59);
 seq_id | seq_reloid | seq_set |       seq_comment				 
--------+------------+---------+-------------------------------------
     93 |  107451516 |       1 | Sequence public.whois_cachemgmt_seq
     59 |  107451860 |       1 | Sequence public.epp_whoi_cach_seq_
(2 rows)

削除しなければならないデータに対して、以下のようにしてSlonyが複製処理を行わないようにします。

delete from _oxrsorg.sl_seqlog where seql_seqid in (93, 59);
delete from _oxrsorg.sl_sequence where seq_id in (93,59);

この2つの問い合わせは、schemadocddlscript( integer, text, integer ) / EXECUTE SCRIPTを使ってすべてのノードに提出することができます。 これにより、すべてのノードで"一回"この処理を行う必要がなくなります。 さもなければ、各ノードで手作業で実行しなければなりません。

SET DROP TABLE同様、Slony-I バージョン1.0.5でSET DROP SEQUENCEとして実装されました。

13. Slony-I: 現在の定期購読セット1にテーブルを追加できません。

テーブルをセットに追加しようとしたところ、以下のようなメッセージが出力されました。

	Slony-I: cannot add table to currently subscribed set 1

定期購読者が存在するセットにテーブルを追加することはできません。

この回避方法は、別のセットを作成し、作成したセットに新しいテーブルを追加します。 そして、"set 1"を定期購読するノードすべてを新しいセットに定期購読させ、その後にセットをまとめます。

14. 一部のノードの一貫性が壊れはじめました。

あるノードでSlony-Iを実行していましたが、システムの性能が落ちていることが分かりました。

以下のような長期間動いている問い合わせがありました。

	fetch 100 from LOG;

多くのデッドタプルを含むpg_listener(NOTIFYデータを含むテーブル)の特徴だと思われます。 NOTIFY イベントの処理時間が長くなり、その結果、影響するノードが段々と失敗状態になり、そしてビハインド状態になります。

活力を取り戻すようにpg_listenerに対してVACUUM FULLを実行しなければなりません。 また、定期的に pg_listenerをバキュームしなければなりません。 たいていの場合5分毎で申し分ありません。

slonデーモンはテーブル群のバキュームを行います。 cleanup_thread.cに定期的にバキュームを自動実行するテーブルの一覧が含まれています。 Slony-I 1.0.2では、pg_listenerは含まれていません。 1.0.5以降では通常バキュームされますので、問題の発生する頻度は減少しているはずです。

しかし、まだ"困惑する"状況は残っています。 MVCCでは、開き続けているもっとも古いトランザクションの開始時点以降に"古い"ものとされたタプルは削除できません。 長時間動くトランザクションは障害を招きますので、定期購読ノードであっても極力さけるべきです。

15. pg_dumpを使用してバックアップを始めたところ、Slonyが突然止まりました。

うーん、以下の2つで競合が発生しています。

  • pg_dumpでは、Slony-I用のテーブルを含め、データベース内のすべてのテーブルに対してAccessShareLockを獲得します。

  • Slony-Iの同期イベントでは、sl_eventテーブルに対するAccessExclusiveLockを獲得します。

ブロックされる最初の問い合わせは以下のようなものです。

select "_slonyschema".createEvent('_slonyschema, 'SYNC', NULL);	  

postgresql.confで問い合わせ表示を有効にしていた場合、pg_stat_activityでこれを確認することができます。)

ロックの原因となる実際の問い合わせの組み合わせは、Slony_I_ClusterStatus()関数から発生します。 これはslony1_funcs.c内にあり、以下のようなコードが記載されています。

  LOCK TABLE %s.sl_event;
  INSERT INTO %s.sl_event (...stuff...)
  SELECT currval('%s.sl_event_seq');

LOCK文のところで止まり、pg_dump(またはsl_eventへの何らかのアクセスロックを獲得している何か)の完了を待機します。

その後に提出されるsl_eventを変更する問い合わせはすべて、createEvent呼び出しの背後でブロックします。

この質問には複数の答えがあります。

  • スキーマを指定したpg_dumpを行うこと。 --schema=whateverを使用して、クラスタのスキーマをダンプしないようにします。

  • --exclude-schemaというオプションがpg_dumpにあれば、Slonyのクラスタスキーマを除外させることができるのですが。 8.1では改善されているかもしれません。。。

  • 1.0.5では、排他処理が少ない、より精密なロックを使用していますので、この問題は起きにくくなっています。

16. slonのコミットに時間がかかります。また、同期させるのにも長時間かかります。

sl_log_1/sl_log_2テーブルを参照してください。 そして、かなり巨大なSlony-Iトランザクションがないか確認してください。 少なくとも1.0.2まででは、SYNCイベントを発生させるために、オリジナルに接続しているslonがなければなりません。

何も生成されていなければ、次が生成されるまでの更新はすべて、1つの巨大なSlony-Iトランザクションにまとめられます。

まとめ:定期購読ノードに渡すものがなかったとしても、オリジナルノードへ提供するためにslonが稼動させるべきです

Slony-I 1.1では、slonデーモンが稼動していなかったとしてもcronジョブに基づいて、オリジナルにおいてSYNCカウントを更新させることができる、ストアドプロシージャを提供します。

17. 定期購読中のノードを他のプロバイダに変更しましたが、複製中に止まってしまいます。

以下のような設定においてノードを再度初期化しようとした時にこれが起こることが分かっています。

  • ノード1 - プロバイダ

  • ノード2 - ノード1の定期購読ノード - 再初期化対象のノード

  • ノード3 - ノード2の定期購読ノード - 複製を維持し続けるノード

ノード3の定期購読はノード1をプロバイダとするように変更になりました。 ノード2でDROP SET /SUBSCRIBE SETを行い、再度データ投入させました。

残念ながらノード3への複製処理は突然停止します。

問題は、ノード1からノード3へイベントを伝播するためのsl_listen内の"リスナ経路"の適切なセットが存在しないことです。 このイベントはノード2を通して伝播しようとしますが、ノード2が作業中であるというSUBSCRIBE SETイベントのためブロックされます。

以下のslonikスクリプトは、ノード3はノード2経路でなければならないというリスナ経路を削除し、ノード1とノード3間を直接つなげるリスナ経路を追加します。

cluster name = oxrslive;
 node 1 admin conninfo='host=32.85.68.220 dbname=oxrslive user=postgres port=5432';
 node 2 admin conninfo='host=32.85.68.216 dbname=oxrslive user=postgres port=5432';
 node 3 admin conninfo='host=32.85.68.244 dbname=oxrslive user=postgres port=5432';
 node 4 admin conninfo='host=10.28.103.132 dbname=oxrslive user=postgres port=5432';
try {
  store listen (origin = 1, receiver = 3, provider = 1);
  store listen (origin = 3, receiver = 1, provider = 3);
  drop listen (origin = 1, receiver = 3, provider = 2);
  drop listen (origin = 3, receiver = 1, provider = 2);
}

このスクリプトを実行するとすぐに、SYNCイベントがノード3に伝播するようになります。 これにより2つの原則が分かります。

  • 複数のノードがあり、定期購読ノードを数珠つなぎにしている場合、STORE LISTEN項目の設定、および、また、複製"ツリー"構成を変更する際の変更に十分注意しなければなりません。

  • バージョン1.1ではこの管理用により優れたツールを用意すべきです。

    実際、改良されました。 発見的にリスナ項目を生成する項8.3を提供します。 以前のバージョンに拘束されている場合用に、項18.25というPerlスクリプトが用意されています。 これは活動中の Slony-Iインスタンスを確認し、リスナ経路ネットワークを生成するためのslonikコマンドを生成します。

項8"リスナ経路"に関するより詳細な説明があります。

18. ノードを削除した後、sl_log_1が消去されなくなりました。

1.0.5より前のバージョンではよく発生した状況です。 ノードを削除する時に行われる"整理作業"に、Slony-Isl_confirmテーブルから削除されたノード向けの古い項目の削除が含まれていなかったためです。

そのノードはもはや、どのような同期をノードに適用するかに関する更新確認を行わなくなります。 従って、ログ項目を消去する整理用スレッドは、最後のsl_confirmより新しい項目を安全に削除できないとみなし、古いログの消去機能を無効にします。

診断:以下の問い合わせを実行して、sl_confirm内に"ファントム/古い/ブロック中"の項目がないか確認してください。

oxrsbar=# select * from _oxrsbar.sl_confirm where con_origin not in (select no_id from _oxrsbar.sl_node) or con_received not in (select no_id from _oxrsbar.sl_node);
 con_origin | con_received | con_seqno |        con_timestamp                  
------------+--------------+-----------+----------------------------
          4 |          501 |     83999 | 2004-11-09 19:57:08.195969
          1 |            2 |   3345790 | 2004-11-14 10:33:43.850265
          2 |          501 |    102718 | 2004-11-14 10:33:47.702086
        501 |            2 |      6577 | 2004-11-14 10:34:45.717003
          4 |            5 |     83999 | 2004-11-14 21:11:11.111686
          4 |            3 |     83999 | 2004-11-24 16:32:39.020194
(6 rows)

バージョン1.0.5では、削除されたノード用のDROP NODE関数はsl_confirm内の項目を消去します。 以前のバージョンでは、手作業で行わなければなりませんでした。 ノード番号が3だとすると、この問い合わせは以下のようなものになります。

delete from _namespace.sl_confirm where con_origin = 3 or con_received = 3;

他に、"すべてのファントム"を処理した後に以下を使用することができます。

oxrsbar=# delete from _oxrsbar.sl_confirm where con_origin not in (select no_id from _oxrsbar.sl_node) or con_received not in (select no_id from _oxrsbar.sl_node);
DELETE 6

一般的な"注意事項"としてまずBEGINを実行してください。 事前にsl_confirmの内容を検査し、想定した記録のみが削除されていることを確認した後、本当にその後にのみに、COMMITで変更を承認してください。 間違ったノードの変更で削除を承認すると、一日を無駄に過ごすことになります。

残りのすべてのノードでこれを行う必要はありません。。。

1.0.5の時点で、何も行う必要がなくなりました。 次の2箇所で不要となった項目はsl_confirmから削除されます。

  • ノードが削除された時点。

  • cleanupEventが実行した時点。 これは、古いデータがsl_log_1sl_seqlogから削除されるイベントです。

19. リプリケーションの失敗。一意性制約違反

複製処理はしばらく正常に稼動します。 ノードが"故障"すると、複製処理のログに以下が繰り返し出力されます。

DEBUG2 remoteWorkerThread_1: syncing set 2 with 5 table(s) from provider 1
DEBUG2 remoteWorkerThread_1: syncing set 1 with 41 table(s) from provider 1
DEBUG2 remoteWorkerThread_1: syncing set 5 with 1 table(s) from provider 1
DEBUG2 remoteWorkerThread_1: syncing set 3 with 1 table(s) from provider 1
DEBUG2 remoteHelperThread_1_1: 0.135 seconds delay for first row
DEBUG2 remoteHelperThread_1_1: 0.343 seconds until close cursor
ERROR  remoteWorkerThread_1: "insert into "_oxrsapp".sl_log_1          (log_origin, log_xid, log_tableid,                log_actionseq, log_cmdtype,		log_cmddata) values	  ('1', '919151224', '34', '35090538', 'D', '_rserv_ts=''9275244''');
delete from only public.epp_domain_host where _rserv_ts='9275244';insert into "_oxrsapp".sl_log_1	  (log_origin, log_xid, log_tableid,		log_actionseq, log_cmdtype,		log_cmddata) values	  ('1', '919151224', '34', '35090539', 'D', '_rserv_ts=''9275245''');
delete from only public.epp_domain_host where _rserv_ts='9275245';insert into "_oxrsapp".sl_log_1	  (log_origin, log_xid, log_tableid,		log_actionseq, log_cmdtype,		log_cmddata) values	  ('1', '919151224', '26', '35090540', 'D', '_rserv_ts=''24240590''');
delete from only public.epp_domain_contact where _rserv_ts='24240590';insert into "_oxrsapp".sl_log_1	  (log_origin, log_xid, log_tableid,		log_actionseq, log_cmdtype,		log_cmddata) values	  ('1', '919151224', '26', '35090541', 'D', '_rserv_ts=''24240591''');
delete from only public.epp_domain_contact where _rserv_ts='24240591';insert into "_oxrsapp".sl_log_1	  (log_origin, log_xid, log_tableid,		log_actionseq, log_cmdtype,		log_cmddata) values	  ('1', '919151224', '26', '35090542', 'D', '_rserv_ts=''24240589''');
delete from only public.epp_domain_contact where _rserv_ts='24240589';insert into "_oxrsapp".sl_log_1	  (log_origin, log_xid, log_tableid,		log_actionseq, log_cmdtype,		log_cmddata) values	  ('1', '919151224', '11', '35090543', 'D', '_rserv_ts=''36968002''');
delete from only public.epp_domain_status where _rserv_ts='36968002';insert into "_oxrsapp".sl_log_1	  (log_origin, log_xid, log_tableid,		log_actionseq, log_cmdtype,		log_cmddata) values	  ('1', '919151224', '11', '35090544', 'D', '_rserv_ts=''36968003''');
delete from only public.epp_domain_status where _rserv_ts='36968003';insert into "_oxrsapp".sl_log_1	  (log_origin, log_xid, log_tableid,		log_actionseq, log_cmdtype,		log_cmddata) values	  ('1', '919151224', '24', '35090549', 'I', '(contact_id,status,reason,_rserv_ts) values (''6972897'',''64'','''',''31044208'')');
insert into public.contact_status (contact_id,status,reason,_rserv_ts) values ('6972897','64','','31044208');insert into "_oxrsapp".sl_log_1	  (log_origin, log_xid, log_tableid,		log_actionseq, log_cmdtype,		log_cmddata) values	  ('1', '919151224', '24', '35090550', 'D', '_rserv_ts=''18139332''');
delete from only public.contact_status where _rserv_ts='18139332';insert into "_oxrsapp".sl_log_1	  (log_origin, log_xid, log_tableid,		log_actionseq, log_cmdtype,		log_cmddata) values	  ('1', '919151224', '24', '35090551', 'D', '_rserv_ts=''18139333''');
delete from only public.contact_status where _rserv_ts='18139333';" ERROR:  duplicate key violates unique constraint "contact_status_pkey"
 - qualification was: 
ERROR  remoteWorkerThread_1: SYNC aborted

トランザクションはロールバックし、Slony-Iが何度も何度も再試行します。 この問題は、log_cmdtype = 'I'で見ると最後のSQL文で発生しています。 あまり明確ではありません。 Slony-Iはネットワーク上の経過時間を減少させるために10個の更新問い合わせにまとめています。

これはおそらく到達性に関する問題です。

問題が発生したことに気付いた時、見かけ上失われた削除トランザクションがsl_log_1から除去されていました。 そのため、リカバリができないように見えていました。 この段階で必要だろうと思われるのは、リプリケーションセット(とノード)の削除、そのノードにおけるリプリケーションを一から再開することです。

Slony-I 1.0.5では、sl_log_1の消去処理はより保守的になりました。 少なくとも10分間ですべてのノードで同期が成功しなかった項目は消去しません。 この"問題"の発生を防ぐことができるかどうかは確実ではありませんが、こうした状態から復旧できる、もしくは、より正確に診断できるのに十分なsl_log_1データを維持できることはもっともらしく思えます。 またおそらく、この問題は、sl_log_1の消去が積極的過ぎることが原因だと思われますので、完全に解消できたはずです。

このために大規模なリプリケーションノードを再構成しなければならないのは恥ずかしいので、もし問題が再発することが分かったら、リプリケーションの再開に含まれる作業を減らすために、リプリケーションセットを複数のセットに分割するというのも手かもしれません。 もし1つのセットのみで障害が発生したら、その1つのセットのみに対して定期購読解除と削除、そして定期購読の再設定を行えばいいのです。

ログファイル内で同じ内容のsl_log_1への挿入が含まれるSQLエラーメッセージが2行になる場合がありました。 これはsl_log_1のプライマリキーであることから不可能でなければなりませんこの問題の原因はおそらくそのPKインデックスが破壊している(PostgreSQLのバグ)ためであり、以下の問い合わせによりこの問題は軽減されるはずであるというのが、最後の(多少)穴のある理屈です。

# reindex table _slonyschema.sl_log_1;

少なくとも1つの場所では、これにより問題は解消しましたので、試す価値があります。

この問題はSlony-Iのバグではなく、PostgreSQLのバグであることが分かりました。 バージョン7.4.8では競合状態に対する2つの対応を行いましたので、この問題は解決したはずです。 従って、7.4.8より前のバージョンのPostgreSQLを使用しているのであれば、問題を解決するためにアップグレードを検討してください。

20. 以下のようなslonikスクリプトを考えていますが、ハングアップし、完了しません。 なぜなら、tryブロック内ではwait for eventを持つことができない、および、待機しているイベントはそのトランザクションに届かないからです。

try {
      echo 'Moving set 1 to node 3';
      lock set (id=1, origin=1);
      echo 'Set locked';
      wait for event (origin = 1, confirmed = 3);
      echo 'Moving set';
      move set (id=1, old origin=1, new origin=3);
      echo 'Set moved - waiting for event to be confirmed by node 3';
      wait for event (origin = 1, confirmed = 3);
      echo 'Confirmed';
} on error {
      echo 'Could not move set for cluster foo';
      unlock set (id=1, origin=1);
      exit -1;
}

"try"ブロック内ではWAIT FOR EVENTを呼び出してはいけません。

21. セットにおけるテーブルの順序は重要ですか?

ほとんどの場合は重要ではありません。 何らかの外部キー制約関係を持つテーブル間で"親"の項目を "子"の前にするために、何らかの方法でテーブルの順序付けする値を想定してください。 外部キー制約用のトリガは定期購読ノードでは無効になりますので、これは問題にはなりません

(Jan Wieckのコメント:) テーブルIDの順序は、切り替え準備におけるLOCK SETの時にのみ重要です。 順番がアプリケーションのロックの取得順序と異なる場合、デッドロックを導き、そのアプリケーションかslonをアボートさせることになるかもしれません。

(David Parker)セットにおけるテーブル順序が重要となる場合が他にもありました。 継承したテーブルが存在する場合です。 セットの中で子テーブルが親テーブルより先に現れた場合、最初の定期購読により、子テーブルがおそらくデータを受け取った後で削除されてしまいました。 copy_setのロジックでは、delete onlyではなくdeleteを行うためです。 従って親テーブルの削除により、子テーブルの新しい行も削除されてしまいます。

22. Slony-Iで複製されたテーブルにおけるルールとトリガはどう動作するのですか?

まず、STORE TRIGGER Slonikコマンドにおける特別な扱いの欠落をどう扱うかを見てみましょう。

関数schemadocaltertableforreplication( integer )は各テーブルの複製準備を行います。

  • オリジナルノードでは、これはschemadoclogtrigger( )関数をそのテーブルに対して使用して、トリガの追加を行います。

    トリガはそのテーブルへのすべての更新をSlony-I sl_log_1に記録する動作で初期化されます。

  • 定期購読ノードでは、これはトリガとルールを無効にします。 そして、複製されたテーブルに対するdenyAccess()関数を使用して、書き込みアクセスを定義するトリガを追加します。

    1.1まで(もしかすると今後も)では、この"無効化"pg_triggerpg_rewritetgrelidを、テーブル自身ではなくテーブル上の"プライマリキー"のOIDを指し示すように変更することで行われていました。

残念ながら多少副作用があります。 このルールとトリガの取り扱いにより少しルールとトリガを壊してしまうことです。 ルールとトリガは存在し続けますが、テーブルに適切に関連付けられていません。 "定期購読"ノードでpg_dumpを行うと、インデックスに関連づいているとは想定していませんので、ルールとトリガを見つけることができません。

さてここで、STORE TRIGGERが何を行うかを見てみましょう。

簡単に言うと、このコマンドによりSlony-IalterTableRestore(table id)を使用してトリガをリストアします。 この関数はテーブルのOIDを対象とするテーブルのpg_triggerpg_rewritetgrelid列に戻します。

これは、定期購読ノードからバックアップを作成するのであれば、オリジナルノードからスキーマを引き出す必要があることを意味します。 このための方法は以下の通りです。

% pg_dump -h originnode.example.info -p 5432 --schema-only --schema=public ourdb > schema_backup.sql
% pg_dump -h subscribernode.example.info -p 5432 --data-only --schema=public ourdb > data_backup.sql

23. のノードにおける定期購読通知の後、リプリケーションが以下のようなエラーメッセージにより失敗します。

ERROR  remoteWorkerThread_1: "begin transaction; set transaction isolation level serializable; lock table "_livesystem".sl_config_lock; select "_livesystem".enableSubscription(25506, 1, 501); notify "_livesystem_Event"; notify "_livesystem_Confirm"; insert into "_livesystem".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', '4896546', '2005-01-23 16:08:55.037395', '1745281261', '1745281262', '', 'ENABLE_SUBSCRIPTION', '25506', '1', '501', 't'); insert into "_livesystem".sl_confirm      (con_origin, con_received, con_seqno, con_timestamp)    values (1, 4, '4896546', 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,501) is not present in table "sl_path".

この後、以下のように同期が失敗し、slonが停止します。

DEBUG2 remoteListenThread_1: queue event 1,4897517 SYNC
DEBUG2 remoteListenThread_1: queue event 1,4897518 SYNC
DEBUG2 remoteListenThread_1: queue event 1,4897519 SYNC
DEBUG2 remoteListenThread_1: queue event 1,4897520 SYNC
DEBUG2 remoteWorker_event: ignore new events due to shutdown
DEBUG2 remoteListenThread_1: queue event 1,4897521 SYNC
DEBUG2 remoteWorker_event: ignore new events due to shutdown
DEBUG2 remoteListenThread_1: queue event 1,4897522 SYNC
DEBUG2 remoteWorker_event: ignore new events due to shutdown
DEBUG2 remoteListenThread_1: queue event 1,4897523 SYNC

slonignore new events due to shutdownといったログを残して停止していた場合、通常、問題の原因の大元が何かを確認するために、失敗し始めたまで戻って対応しなければなりません。

今回の場合に特化すると、問題は、SUBSCRIBE SETコマンドが伝播する前にSTORE PATHコマンドの一部がノード4においてなされていないことです。

これは、拙速に対応してはいけない例でもあります。 今後の設定変更のに物事が正しく動作していることを確認しなければなりません。

24. MOVE SETを使ってオリジナルを新しいノードに移動しました。 残念なことに一部の定期購読ノードが以前のオリジナルノードを参照しています。 このため、更新の受付を止めない限り、そのノードを保守作業のためにサービスから取り出すことができません。 どうすればいいでしょうか?

SUBSCRIBE SETを使用して、こうしたノードの定期購読が、保守作業中に支えている予定のプロバイダを購読するように変更しなければなりません。

警告

UNSUBSCRIBE SETを行ってはいけません。 これを行うと、そのノードで、後で一からすべてのデータの再読み込みが必要になります。

25. リプリケーションが低速になりました。 FETCH 100 FROM LOG という問い合わせが長時間動いていることが分かっています。 sl_log_1が大きくなり、性能はだんだんと悪化しています。

実際、この種の問題の原因には多くがあります。 似たような質問として、バキュームされていないためpg_listenerが巨大になるという問題があります。

巨大化に関する他の"似たような原因"は、非常に長時間IDLE IN TRANSACTION し続けるノードへ接続する接続がある場合です。

このような開いているトランザクションは複数の悪影響を生じます。また、すべて性能を悪化させてしまいます。

  • pg_listenerを含むすべてのテーブルのバキュームが、待機中のトランザクションが始まる前からあるデッドタプルの掃除を行いません。

  • 整理用スレッドがsl_log_1sl_seqlog内の項目を整理できなくなります。 その結果、トランザクションが閉じるまで、これらのテーブルは確実に巨大になります。

PostgreSQLpostgresql.conf 内のパラメータstats_command_stringを真に設定している場合のみ、データベース内のこの状態を監視することができます。 真の場合、 select * from pg_stat_activity where current_query like '%IDLE% in transaction'; という問い合わせを実行してください。これで関連する活動状況が分かります。

また、プロセステーブルから"待機中のトランザクション"を探し出し、年老いたトランザクションを保持しているプロセスを見つけることができるはずです。

何らかの理由で非常に長時間開いているトランザクションが問題の原因となる場合も(より少ないのですが)あります。 pg_stat_activity 内の query_start を確認することで、長時間稼動している問い合わせが分かります。

PostgreSQLにタイムアウトパラメータopen_idle_transaction_timeout を持たせるという計画があります。 これは、ある未使用時間の後に古いトランザクションをタイムアウトさせるというものです。 バグのある接続プールロジックがよくこうした状況を発生させます。 pgpool では、より優れた代替案を計画しています。 結局のところ、C言語で作成された接続プールの内部で接続を共有するものです。 使用中のJavaアプリケーションやPHPアプリケーションでは、多少バグのある接続プールを使用しているかもしれません。 pgpool内で保持されている本物の接続数が少なければ、アプリケーションが多くの接続がトランザクションの途中で数時間に渡って待機状態となっていると想定していることをデータベースから隠すことになります。

26. 動作:すべての定期購読ノードがオリジナルの背後に落ち始めます。 そして、定期購読ノードのログには以下のようなエラーメッセージが繰り返し記録されます。 (この現象に遭遇した時、かなり長めのSQL文が各項目に記録されていました。)

ERROR remoteWorkerThread_1: helper 1 finished with error
ERROR remoteWorkerThread_1: SYNC aborted

原因:おそらくslonik EXECUTE SCRIPT コマンドを使わずに直接データベースに対してalter tableを行ったこと。

解決方法は影響を受けたテーブル上のトリガを再構築し、手作業でsl_log_1内の項目を修正することです。

  • slonのログまたはPostgreSQLデータベースのログからどの文がエラーを引き起こしたかを特定する必要があります。

  • 問題のテーブル上のSlonyが定義したトリガを修正する必要があります。 これは以下のような手順で行います。

    BEGIN;
    LOCK TABLE table_name;
    SELECT _oxrsorg.altertablerestore(tab_id);--tab_id is _slony_schema.sl_table.tab_id
    SELECT _oxrsorg.altertableforreplication(tab_id);--tab_id is _slony_schema.sl_table.tab_id
    COMMIT;

    そして、sl_log_1から問題のある項目を持つ行を探し、それを修正します。 マスタ以外のすべてのノードにおいてslonデーモンを停止する方がいいでしょう。 そうすれば、間違えたとしても、すぐには定期購読ノードに伝播しません。

    以下に例を示します。

    BEGIN;
    
    LOCK TABLE customer_account;
    
    SELECT _app1.altertablerestore(31);
    SELECT _app1.altertableforreplication(31);
    COMMIT;
    
    BEGIN;
    LOCK TABLE txn_log;
    
    SELECT _app1.altertablerestore(41);
    SELECT _app1.altertableforreplication(41);
    
    COMMIT;
    
    -- timestamp with timezone型に""を挿入しようとしていた、customer_accountを修正します。
    BEGIN;
    
    update _app1.sl_log_1 SET log_cmddata = 'balance=''60684.00'' where pkey=''49''' where log_actionseq = '67796036';
    update _app1.sl_log_1 SET log_cmddata = 'balance=''60690.00'' where pkey=''49''' where log_actionseq = '67796194';
    update _app1.sl_log_1 SET log_cmddata = 'balance=''60684.00'' where pkey=''49''' where log_actionseq = '67795881';
    update _app1.sl_log_1 SET log_cmddata = 'balance=''1852.00'' where pkey=''57''' where log_actionseq = '67796403';
    update _app1.sl_log_1 SET log_cmddata = 'balance=''87906.00'' where pkey=''8''' where log_actionseq = '68352967';
    update _app1.sl_log_1 SET log_cmddata = 'balance=''125180.00'' where pkey=''60''' where log_actionseq = '68386951';
    update _app1.sl_log_1 SET log_cmddata = 'balance=''125198.00'' where pkey=''60''' where log_actionseq = '68387055';
    update _app1.sl_log_1 SET log_cmddata = 'balance=''125174.00'' where pkey=''60''' where log_actionseq = '68386682';
    update _app1.sl_log_1 SET log_cmddata = 'balance=''125186.00'' where pkey=''60''' where log_actionseq = '68386992';
    update _app1.sl_log_1 SET log_cmddata = 'balance=''125192.00'' where pkey=''60''' where log_actionseq = '68387029';

27. ノードにおける定期購読通知の後、ある定期購読ノードにおいてリプリケーションが以下のエラーで失敗します。

ERROR  remoteWorkerThread_1: "begin transaction; set transaction isolation level serializable; lock table "_livesystem".sl_config_lock; select "_livesystem".enableSubscription(25506, 1, 501); notify "_livesystem_Event"; notify "_livesystem_Confirm"; insert into "_livesystem".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', '4896546', '2005-01-23 16:08:55.037395', '1745281261', '1745281262', '', 'ENABLE_SUBSCRIPTION', '25506', '1', '501', 't'); insert into "_livesystem".sl_confirm      (con_origin, con_received, con_seqno, con_timestamp)    values (1, 4, '4896546', 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,501) is not present in table "sl_path".

そして何回か同期に失敗し、slonが停止します。

DEBUG2 remoteListenThread_1: queue event 1,4897517 SYNC
DEBUG2 remoteListenThread_1: queue event 1,4897518 SYNC
DEBUG2 remoteListenThread_1: queue event 1,4897519 SYNC
DEBUG2 remoteListenThread_1: queue event 1,4897520 SYNC
DEBUG2 remoteWorker_event: ignore new events due to shutdown
DEBUG2 remoteListenThread_1: queue event 1,4897521 SYNC
DEBUG2 remoteWorker_event: ignore new events due to shutdown
DEBUG2 remoteListenThread_1: queue event 1,4897522 SYNC
DEBUG2 remoteWorker_event: ignore new events due to shutdown
DEBUG2 remoteListenThread_1: queue event 1,4897523 SYNC

slonignore new events due to shutdownをログに残して停止しているかどうかを確認してください。 通常は、この問題の根本的な原因を示すものを確認するために、失敗が始まるまでログファイルを遡らなければなりません。

上で示した状況では、SUBSCRIBE SETコマンドが伝播する前にSTORE PATHコマンドの一部がノード4で行われていなかったことが問題です。

28. SUBSCRIBE SETの実行よりかなり高速にpg_dumpを行い、データをロードすることができます。 なぜ?

Slony-Iはプライマリキーに既存のインデックスがあることに依存しています。 そして、すべてのインデックスを残したまま、PostgreSQLCOPYコマンドを使用してデータをロードします。 さらに性能を悪化させているのは、COPY SETイベントがテーブルの内容を削除してから始まることです。 このため、多くのデッドタプルが残る可能性があります。

pg_dumpを使用してデータベースの内容をダンプし、それをロードする時、インデックスはかなり終わりの方まで遅延されて作成されます。 テーブルに各行を追加する度に増分的にインデックスを構築するよりも、最後にまとめてテーブル全体に対してインデックスを作成する方がかなり効率的です。

残念ながら、インデックスを"その場で"削除し再作成することが難しいことが分かっています。 自動的にこれを行う機能は実装されていません。

COPYを行うところで不要なインデックスを削除することができるのであれば、多少性能が上がります。 除去しようとしているデータを含むテーブルをTRUNCATEすることができるのであれば、かなり性能が上がります。

PostgreSQL実装におけるTODO項目には、インデックスの再作成を遅延させ、最後にまとめてインデックスを再作成するための、例えばBULKLOADという名前の新しいオプションの追加があります。 これはPostgreSQL 8.1まで利用できないようですが、これができれば大いに性能が向上するはずです。

29. ネットワーク"障害"のため、FAILOVERが動き、他のノードへフェイルオーバーしました。 データベースを破壊するようなディスク障害ではありません。 なぜ一から障害発生ノードを再構築しなければならないのですか?

FAILOVERは、障害発生ノードを破棄してしまいます。 このため、そのノードからまたはそのノードへの Slony-Iの活動はその後一切なくなります。 障害が回復すると、障害発生ノードは失敗し始め、また、同期もされません。

障害発生ノードの復旧試行に関する大きな問題は、オリジナルでは行われていない更新が含まれるかもしれないことです。 また、再処理したとすると、新しいオリジナルで更新が競合するかもしれません。 たとえ"物理的な"ディスク障害でなくても、とにかくデータには何らかの"論理的な"破損が生じます。

項7で説明したように、 FAILOVERの使用は最後の手段であると考えてください。 これはオリジナルノードを壊れたものとみなして破棄することを意味しています。

30. リプリケーションさせるためにslonyという"スーパーユーザ"アカウントを作成しました。 推奨されたとおり、以下の問い合わせを使ってスーパーユーザの設定を行いました。 update pg_shadow set usesuper = 't' where usename in ('slony', 'molly', 'dumpy'); (また、このコマンドで他にも、バキューム用およびバックアップ用のユーザも扱いました。)

しかし残念なことに、新しいセットに購読させた後に問題が起こりました。

DEBUG1 copy_set 28661
DEBUG1 remoteWorkerThread_1: connected to provider DB
DEBUG2 remoteWorkerThread_78: forward confirm 1,594436 received by 78
DEBUG2 remoteWorkerThread_1: copy table public.billing_discount
ERROR  remoteWorkerThread_1: "select "_mycluster".setAddTable_int(28661, 51, 'public.billing_discount', 'billing_discount_pkey', 'Table public.billing_discount with candidate primary key billing_discount_pkey'); " PGRES_FATAL_ERROR ERROR:  permission denied for relation pg_class
CONTEXT:  PL/pgSQL function "altertableforreplication" line 23 at select into variables
PL/pgSQL function "setaddtable_int" line 76 at perform
WARN   remoteWorkerThread_1: data copy for set 28661 failed - sleep 60 seconds

slonを再起動しpostgresとして接続しなおすまで、何度も繰り返し失敗しました。

この問題のメッセージの中に原因が記載されています。pg_classシステムテーブルへの権限が不足しています。

以下のように"修正"してください。

update pg_shadow set usesuper = 't', usecatupd='t' where usename = 'slony';

31. マスタとスレーブからslonyリプリケーションクラスタを完全にアンインストールしても、予期できない何かが刺さっています。

警告

slonyクラスタ全体を削除する時にマスタデータベースに対して動作しているアプリケーションが停止していることを確認してください。 もしくは、少なくともこのイベントの後に開いている接続をすべて開き直してください。

接続はノードのアンインストールスクリプトで削除されたOIDを"記憶"もしくは参照します。 この結果、多くのエラーが発生します。

PostgreSQLには問い合わせ計画とOIDをキャッシュする注意しなければならない点が2つあります。

  • 準備された文

  • pl/pgSQL 関数

この問題はSlony-I固有のものではありません。 データベーススキーマに重大な変更が行われた時に発生する可能性があります。 データの損失をもたらすものではありませんが、OIDに関連した多種のエラーが発生します。

古い接続を再利用し続ける、何らかの"接続プール"を使用している場合、この問題は発生します。 その後にアプリケーションを再起動すれば、新しい接続が新しい問い合わせ計画を生成し、エラーは発生しなくなります。 使用中の接続プールが古い接続を削除し、新しい接続を生成すれば、新しい接続が新しい問い合わせ計画を生成し、エラーは発生しなくなります。

コードでは、想定していない状態のエラーが起きたとき接続を削除します。 これにより想定外の問題の接続はすべて、1接続当たり一回エラーが発生した後に、最終的には作り直されます。 もちろん、エラーが制約違反として顕在化した場合は想定内の状態ですので、これは役に立ちません。 そして、この問題が永続的であれば、接続は再利用され続け、プールの意味がなくなります。 後者の場合、プール用コードで管理者に注意を促すことはできます。

32. ノード1をDROP NODEを使用して削除したところ、他のノードのslonが以下のようなエラーメッセージで失敗し続けるようになりました。

ERROR  remoteWorkerThread_3: "begin transaction; set transaction isolation level
 serializable; lock table "_mailermailer".sl_config_lock; select "_mailermailer"
.storeListen_int(2, 1, 3); notify "_mailermailer_Event"; notify "_mailermailer_C
onfirm"; insert into "_mailermailer".sl_event     (ev_origin, ev_seqno, ev_times
tamp,      ev_minxid, ev_maxxid, ev_xip, ev_type , ev_data1, ev_data2, ev_data3
   ) values ('3', '2215', '2005-02-18 10:30:42.529048', '3286814', '3286815', ''
, 'STORE_LISTEN', '2', '1', '3'); insert into "_mailermailer".sl_confirm
(con_origin, con_received, con_seqno, con_timestamp)    values (3, 2, '2215', CU
RRENT_TIMESTAMP); commit transaction;" PGRES_FATAL_ERROR ERROR:  insert or updat
e on table "sl_listen" violates foreign key constraint "sl_listen-sl_path-ref"
DETAIL:  Key (li_provider,li_receiver)=(1,3) is not present in table "sl_path".
DEBUG1 syncThread: thread done

どうやら、ノード1が削除される前にSTORE LISTEN要求が伝播しなかったようです。

ここでのポイントは、1つ以上のノードで"イベントの手術"を行わなければならない状況であることです。 ノード1とノード1を指し示すすべての経路が存在しなくなったため、作成することができないリスナ経路を追加させるためのSTORE_LISTENイベントが未解決のまま残っています。

詳しく説明するために、ノード2とノード3が残存するノードであり、上のエラーはノード3で報告されているものと仮定します。

これは、ノード2にイベントが格納されていることを意味します。 イベント処理が成功していなかったとすると、ノード3上にはありません。 この状況に対応するための最も簡単な方法は、ノード2上の問題のsl_event項目を削除することです。 ノード2のデータベースに接続し、以下のようにしてSTORE_LISTENイベントを検索してください。

select * from sl_event where ev_type = 'STORE_LISTEN';

複数の項目があるかもしれませんが、削除しなければならないのはその一部のみです。

 
-# begin;  -- 単純に削除しないでください。問題が起きても対応できるようトランザクションを開きます。
BEGIN;
-# delete from sl_event where ev_type = 'STORE_LISTEN' and
-#  (ev_data1 = '1' or ev_data2 = '1' or ev_data3 = '1');
DELETE 3
-# -- 問題ないようです。。。
-# commit;
COMMIT

ノード3で次にslonを起動した時には、"問題"STORE_LISTENイベントはもうなく、リプリケーションを継続することができます。 (この後、古い格納されたイベントが存在しない設定を参照していることによる他の問題が残るかもしれません。)

33. FFPM (Frotznik Freenix Package Manager)パッケージ管理システムで管理された Frotznik Freenix 4.5を使用しています。 FFPMにはPostgreSQL 7.4.7が付随しており、これをデータベースとして使用していますが、このパッケージにはSlony-Iが含まれていません。 どのようにSlony-Iを追加するのでしょうか?

著者はFrotznik Freenixをあまり知りません。 ですので、確定的な答えを示すことができません。

答えは、PostgreSQLSlony-Iのバージョンの組み合わせによって多少変わります。 一般的に古いバージョンよりも新しいバージョンの方が多少簡単に対応できます。 通常、ほとんど確実にソースからSlony-Iをコンパイルしなければなりません。 Slony-IPostgreSQLのバージョンに依存しますが、 PostgreSQLを初めからコンパイルしなおす必要があるかもしれません。 (コンパイルしたPostgreSQL使用しなければならないかどうかは別問題です。多分必要ないと思います。)

  • Slony-Iバージョン1.0.5以前では、Slony-Iをコンパイルするために完全に構築された PostgreSQLのソースのコピーが必要でした。

    できる限りパッケージ版のPostgreSQLで使われた設定条件と似た設定を行ってください。 パッケージ版の設定は pg_config --configureコマンドを使用して確認することができます。

  • Slony-I バージョン1.1ではこれがかなり簡単になっています。 PostgreSQLのソースの完全なコピーは不要です。 代わりに、PostgreSQLのライブラリやバイナリ、設定、 #include ファイルの格納場所を参照するようになりました。

  • PostgreSQL 8.0以上では、通常、"デフォルト"のインストールですべての #include ファイルが含まれますので、扱いがより簡単になりました。

    使用中のPostgreSQLのバージョンが古い場合、もしパッケージ版が"サーバ用の#include"ファイルをインストールしていなければ、ソースからのインストールに頼らなければならなくなります。 このファイルは make install-all-headers コマンドでインストールすることができます。

実際、"最悪の"シナリオは、1.1より前のSlony-I"古め"のバージョンのPostgreSQLを使用している場合です。 この場合、Slony-Iのコンパイルに必要なものをすべて用意するために、"パッケージ版の"PostgreSQLを使用していたとしても、PostgreSQLを初めからコンパイルしなければならないはずです。

最近のPostgreSQL、最近のSlony-Iを稼動させていれば、互いの依存関係はかなり小さいため、更なるPostgreSQLのソースは不要になります。 こうした改良によって、Slony-Iのパッケージ作成が簡単になったはずです。 ですので、じきにSlony-Iをコンパイルすることがなくなるかもしれません。

34. Bug #1226 では、 単なるシーケンスから構成されるリプリケーションセットの場合に、発生する可能性があるエラー条件を示しています。

簡単に答えると、シーケンスのみから構成されるリプリケーションは最善の運用ではありません。

シーケンスのみのセットの問題は、ある定期購読ノードの有効な定期購読が、"シーケンスのみ"のセットを持つあるプロバイダへのものだけであった場合に発生します。 ノードがこうした状態になると、sl_log_1のデータを検索する問い合わせがテーブルを見つけられず、問い合わせが壊れ、失敗しますので、リプリケーションは失敗します。 テーブルを持つリプリケーションセットが追加されると、うまく動作するようになります。 臆病なだけなようです

この問題はSlony-I 1.1.0以降のいつかで解決しなければなりません。

35. 以下の問い合わせを試しましたが、動作しません。

sdb=# explain select query_start, current_query from pg_locks join
pg_stat_activity on pid = procpid where granted = true and transaction
in (select transaction from pg_locks where granted = false); 

ERROR: could not find hash function for hash operator 716373

Slony-Ixxid関数はハッシュ機能を持っているはずですが、実際にはそうなっていなかったようです。

どうしたらいいのでしょうか?

Slony-IはトランザクションIDを操作できるようにXXIDデータ型とその演算子を定義しています。 これを使用して、同一トランザクションに関連した更新をまとめることができます。

演算子はPostgreSQL 7.3以前のバージョンでは使用できません。 バージョン7.3をサポートするためには独自の関数を追加しなければなりません。 =演算子は、ハッシュをサポートするものと記録されていますが、正常に動作させるには、その結合演算子がハッシュインデックス演算子クラス内に存在しなければなりません。 これは定義されていなければ、その結果として、ハッシュ結合を使用するものと決めた(上のような)問い合わせは失敗します。

これは、"致命的な"バグとみなしていません。 ハッシュ結合を使用するような問い合わせを、Slony-Iが内部的に生成することがないからです。 この問題は、Slony-Iのリプリケーション機能を損なってはいけません。

そのため、今後のSlony-Iのリリース(例えば1.0.6、1.1)では、HASHES指示子が省略されます。

既存のインスタンスの修復を行いたいのであれば、独自の問い合わせがこの問題に遭遇しないように、以下を行ってください。

/* cbbrowne@[local]/dba2 slony_test1=*/ \x     
Expanded display is on.
/* cbbrowne@[local]/dba2 slony_test1=*/ select * from pg_operator where oprname = '=' 
and oprnamespace = (select oid from pg_namespace where nspname = 'public');
-[ RECORD 1 ]+-------------
oprname      | =
oprnamespace | 2200
oprowner     | 1
oprkind      | b
oprcanhash   | t
oprleft      | 82122344
oprright     | 82122344
oprresult    | 16
oprcom       | 82122365
oprnegate    | 82122363
oprlsortop   | 82122362
oprrsortop   | 82122362
oprltcmpop   | 82122362
oprgtcmpop   | 82122360
oprcode      | "_T1".xxideq
oprrest      | eqsel
oprjoin      | eqjoinsel

/* cbbrowne@[local]/dba2 slony_test1=*/ update pg_operator set oprcanhash = 'f' where 
oprname = '=' and oprnamespace = 2200 ;
UPDATE 1

36. 7.2ベースのPostgreSQLシステムがあります。 Slony-I本当に使用したいのですが、8.0にアップグレードしたいと考えています。 Slony-Iを動作させるためには何を行えばいいのでしょうか?

Rod Taylorが以下の報告をしています。

必要な作業はおおよそ以下の通りです。

  • 7.3のテンプレートを取り出し、7.2にコピーします。 もしくは、7.3のテンプレートをまとめるために、使用中のもののバージョンを書き換えます。

  • コードとSQLテンプレートからスキーマの跡をすべて削除します。 私は基本的に"."を"_"に変更しました。

  • XIDデータ型とその関数に関連した多くの作業を行います。 例えば、SlonyはXIDからXXIDへのキャストとその逆のキャストを生成します。 しかし、7.2では新しいキャストを作成することができませんので、システムカタログを手作業で変更しなければなりません。 私は演算子クラスの作成と多くの関数の変更を再度行いました。

  • sl_log_1には、データ量に依存した、深刻な性能上の問題があります。 必要なインデックスと問い合わせの数は、7.2に最適になるように変更されています。 7.3以上では、適用可能な最適化方法がより賢くなっています。

  • シーケンスの動作を正常にさせることに悩むことはありません。 アップグレードの後で、pg_dumpとgrepを使用して手作業で行ってください。

もちろん、上をすべて行ったとしても、標準的なSlonyと同じにはなりません。 そのため、互いに通信できるように、少しハックして7.2を実装しなければなりません。もしくは、slonyをハックしてPostgreSQLの新しいバージョンのスキーマなしで動作させることもできます。

データベースの7.2から7.4へのアップグレードとほぼ同時に、ハックしたSlonyを(ほとんどの部分を手作業で)アンインストールし、7.4から通常のSlonyを使用している他のマシン上の7.4への移行を始めます。 これは主に、手作業でいじっていたシステムカタログを保持しないことを確実にさせるための作業です。

私たちは、およそ30分の実際のダウンタイムで数百GBのデータを7.2から7.4へアップグレードできました。 (ダンプ・リストアの場合はおよそ48時間です。) またデータの損失もありませんでした。

これは非常に醜い"ハック"の集まりであり、開発者は、実コードに含めようとは思いません。 誰かがこれを実用レベルまでにすることに興味があったとしても、おそらく、互換性の維持や複製の長期の保守性を維持しようとしないという明確な計画の下で、Slony-I 1.0ブランチを元にすることには意味があるでしょう。

この道を進むのは、非常に多くコードをハックする覚悟ができるほど十分に、PostgreSQLSlony-Iに不自由がない場合にのみとしてください。