Hadoop-1.1.2を完全分散モードで動作させるまでの設定

CentOS6にHadoop-1.1.2を設定したのでその時のメモを記載します。
以下の手順は完全分散モードで動作するように設定を行った手順になります。[ ]はそれぞれの環境に合わせて調整してください。
ここでは、SELinuxはtargetedでファイアウォールiptables)は有効にしたままで動作する手順を紹介します。(ちなみにIPv6は無効)

■全環境でユーザ作成とHadoopのインストール、各種設定を実施

hadoopユーザの作成


groupadd hadoop
useradd -g hadoop hadoop
passwd hadoop

hadoop-1.1.2.tar.gzを解凍しオーナをhadoopに設定した後、シンボリックリンクを作成。


cd [path_to_install_dir]
tar -zxvf hadoop-1.1.2.tar.gz
chown -R hadoop:hadoop hadoop-1.1.2/
ln -s hadoop-1.1.2/ hadoop

hadoopユーザにsuしJAVA_HOME、HADOOP_HOMEの設定を行う。


vi .bash_profile
JAVA_HOME=[path_to_jdk]
HADOOP_HOME=[path_to_hadoop_home]
PATH=$PATH:$JAVA_HOME/bin
export JAVA_HOME
export HADOOP_HOME
export PATH

Hadoopの設定

$HADOOP_HOME/conf/core-site.xmlの設定

<configuration>
    <property>
        <name>hadoop.tmp.dir</name>
        <value>[path_to_hadoop_tmp_dir]</value>
    </property>
    <property>
        <name>fs.default.name</name>
        <value>hdfs://[master_host_name]:[port]</value>
    </property>
</configuration>

$HADOOP_HOME/conf/hdfs-site.xmlの設定

<configuration>
    <property>
        <name>dfs.name.dir</name>
        <value>${hadoop.tmp.dir}/dfs/name</value>
    </property>
    <property>
        <name>dfs.data.dir</name>
        <value>${hadoop.tmp.dir}/dfs/data</value>
    </property>
</configuration>

$HADOOP_HOME/conf/mapred-site.xmlの設定

<configuration>
    <property>
        <name>mapred.job.tracker</name>
        <value>[master_host_name]:[port]</value>
    </property>
</configuration>

$HADOOP_HOME/conf/hadoop-env.shの設定


export JAVA_HOME=[path_to_jdk]
export HADOOP_PID_DIR=[path_to_hadoop_pid_dir] #デフォルトでも良いが、他のサービスに習って/var/run/配下にhadoop用のディレクトリを作成するほうがわかりやすい?

ルートユーザでデータディレクトリとPIDファイル格納ディレクトリを作成


mkdir [path_to_hadoop_tmp_dir]
chown -R hadoop:hadoop [path_to_hadoop_tmp_dir]
mkdir [path_to_hadoop_pid_dir]
chown -R hadoop:hadoop [path_to_hadoop_pid_dir]

■マスタノードでhadoopユーザでssh用の公開キーを作成し各ノードに配布する


ssh-keygen -t rsa -P ""
cd .ssh
cat id_rsa.pub >> authorized_keys
chmod 600 authorized_keys

完全にローカルなネットワーク内であれば上記のような空パスフレーズを設定しても問題ないのですが、そうでない場合はセキュリティ上の問題が発生します。
何か適当なパスフレーズを設定し、毎回ssh-agentを起動してパスフレーズをキャッシュするのもいいのですが、今回はPAMを利用してhadoopユーザでssh可能なホストに制限をかける方法を紹介します。

SSHでPAM認証を利用するように設定し、hadoopユーザによるssh接続はマスタノードの場合は自ノードのみ、スレーブノードの場合はマスタノード及び、自ノードのみからアクセス可能にする。


cd /etc/security
vi access.conf
- : hadoop : ALL EXCEPT [マスタノードのIP] 127.0.0.1 [自ノードのIP(スレーブノードの場合のみ)] #定義追加

vi /etc/pam.d/sshd
account required pam_access.so shadow nullok #定義追加

cd /etc/ssh
vi sshd_config
#PermitEmptyPasswords no

PermitEmptyPasswords yes

UsePAM no

UsePAM yes

こうすることで多少セキュリティは向上すると思います。

なお、tcpwrapperを利用しhosts.denyですべて拒否し、hosts.allowで特定のネットワークおよび、サービスに対する制御を行っている場合、少なくともhosts.allowファイル内にsshdのサービスにlocalhostからの接続を許可するように設定する必要があります。
理由はマスタ側でhadoop起動時にセカンダリのNameNodeを起動する際にHadooplocalhostに対してsshログインを行いセカンダリの起動を行っているようなので、localhostを定義していないとサーバ側で接続が拒否されてしまうからです。


vi /etc/hosts.allow
sshd : [xxx.yyy.zzz.] localhost
or
ALL : [xxx.yyy.zzz.] localhost #簡単に設定するならこちらだが・・・

上記作業が各ノードで完了したらsshdを再起動し、マスタノードで作成した公開キーを各スレーブノードに登録しまます。


service sshd restart

公開キーを各ノードに登録する。


ssh-copy-id -i .ssh/id_rsa.pub hadoop@[slave_node1]
ssh-copy-id -i .ssh/id_rsa.pub hadoop@[slave_node2]
...

■マスターノードのiptablesにとりあえず最低限以下の定義を追加しiptablesを再起動


vi /etc/sysconfig/iptables
-A INPUT -s [ローカルネットワーク]/24 -p tcp -m state --state NEW -m tcp --dport [fs.default.name用のポート] -j ACCEPT
-A INPUT -s [ローカルネットワーク]/24 -p tcp -m state --state NEW -m tcp --dport [mapred.job.tracker用のポート] -j ACCEPT

service iptables restart

■スレーブノードのiptablesにとりあえず最低限以下の定義を追加しiptablesを再起動


vi /etc/sysconfig/iptables
-A INPUT -s [ローカルネットワーク]/24 -p tcp -m state --state NEW -m tcp --dport [dfs.datanode.address用のポート(デフォルト:50010)] -j ACCEPT
-A INPUT -s [ローカルネットワーク]/24 -p tcp -m state --state NEW -m tcp --dport [dfs.datanode.ipc.address用のポート(デフォルト:50020)] -j ACCEPT
-A INPUT -s [ローカルネットワーク]/24 -p tcp -m state --state NEW -m tcp --dport [dfs.datanode.http.address用のポート(デフォルト:50075)] -j ACCEPT
-A INPUT -s [ローカルネットワーク]/24 -p tcp -m state --state NEW -m tcp --dport [mapred.task.tracker.http.address用のポート(デフォルト:50060)] -j ACCEPT

service iptables restart

■マスターノードの$HADOOP_HOME/conf/slavesにslaveノードのホスト名を設定する


cd $HADOOP_HOME/conf
vi slaves
slave_node1
slave_node2
...

■NameNodeのフォーマット処理を行い、hadoopクラスタを起動


cd $HADOOP_HOME/bin
./hadoop namenode -format
./start-all.sh

hadoopユーザでHADOOP_HOMEを設定しているため、HADOOP_HOME環境変数は非推奨云々の警告が出ますが、とりあえず動作上は問題なさそうです。
(0.20のころは出てなかった気がするが、長い間使ってなかったんでいつの間にか変わった?)

ブラウザでhadoopのWeb管理画面(http://[master]:50070)にアクセスし、各ノードが起動されているか確認できます。
念のため各ノードのログファイルも参照しエラーが発生していないか確かめたほうがいいと思います。

マスタノードではNameNode、SecondaryNameNode、JobTrackerの3プロセスが、スレーブノードではDataNode、TaskTrackerの2プロセスが起動すればまずは問題ないと思います。

停止する場合は以下のコマンドで停止させるのが簡単です。


cd $HADOOP_HOME/bin
./stop-all.sh

これで準備完了です。あとはファイルディスクリプタの設定(/etc/security/limits.conf)やカーネルパラメータのチューニング(/etc/sysctl.conf)など、必要に応じて行えばいいと思います。

この辺についてはいろいろなサイトや、書籍を参考にすればいいので省略します。

なお、sshのアクセス制御などセキュリティにかかわる設定が関係しているため、参考にする場合は利用する環境に合わせて自己責任でお願いします。
(結構面倒なので完全にプライベートなネットワークで動作させることをおススメします。)

JavaEE7が正式に発表されたようです

JavaEE7が正式に発表されたようですね。

個人的な興味はWebSocketとConcurrency Utilities for JavaEEかな。

NetBeans7.3.1も早速リリースされているようなのですぐにでも試せそうです。

JBossASの次期バージョンWildfly8のApha1もリリースされているようだし、またやることが増えそうです。

PostgreSQL-9.2のレプリケーション構成における可用性の確認

■基本構成とシナリオ


master -----> slave1
| sync
|
+--------> slave2
potencial

slave1がダウンするとslave2は自動的に同期モードになるように設定。
以前の記事ではレプリケーションの設定でslave2を非同期(async)に設定しました。slave2をpotencialにするにはマスタのpostgresql.confのsynchronous_standby_namesにslave2を追加。


synchronous_standby_names='slave1,slave2'


select application_name, state, sync_state from pg_stat_replication;
application_name | state | sync_state
------------------+-----------+------------
slave1 | streaming | sync
slave2 | streaming | potencial

システム構成が上記構成であることを前提に、今回は以下3つの障害シナリオに沿って、PostgreSQL9.2のレプリケーション構成時の可用性の確認とその運用についてみていきます。

1.slave1のノードダウン時はslave2が自動的に同期モードによる接続となること。
2.masterノードダウン後にslave1をマスタに昇格させ、新マスタslave1を停止させることなくslave2と接続可能であること。
3.障害から復旧した旧マスタが新マスタslave1に(slave1を停止させることなく)接続可能であること。

■テスト用のテーブルを準備
各ステップの途中に適当にデータを投入しリカバリの状態を確認するために適当なテーブルを準備

【例】


CREATE TABLE TEST_TBL (ID INTEGER PRIMARY KEY, COL1 VARCHAR(16));

■slave1をダウンさせてslave2が同期モードに昇格することを確認
(1)slave1のサーバでimmediateモードで疑似的にクラッシュした状態で停止させる。


pg_ctl stop -m immediate -D /var/lib/pgsql/9.2/data

(2)masterでレプリケーションの状態を確認


select application_name, state, sync_state from pg_stat_replication;
application_name | state | sync_state
------------------+-----------+------------
slave2 | streaming | sync

(3)masterサーバで事前に準備しておいたテーブルに適当なデータを投入しmasterとslave2に反映されることを確認

(4)slave1を起動しmasterでレプリケーションの状態を確認


select application_name, state, sync_state from pg_stat_replication;
application_name | state | sync_state
------------------+-----------+------------
slave1 | streaming | sync
slave2 | streaming | potencial

(5)slave1で停止中に追加されたデータが反映されていることを確認

■masterをダウンさせslave1をマスタサーバへ昇格させる
これ以降の作業を行う前に念のため全ノードのデータベースを停止し、/var/lib/pgsql/9.2/dataディレクトリのバックアップを取得することをお勧めします。
なお、今回はpg_basebackupを利用した方法でリカバリを行います。

(1)masterでレプリケーションの状態を確認


select application_name, state, sync_state from pg_stat_replication;
application_name | state | sync_state
------------------+-----------+------------
slave1 | streaming | sync
slave2 | streaming | potential

(2)masterノードを停止し、slave1をマスタに昇格
masterサーバで下記コマンドにより擬似的にクラッシュさせる


pg_ctl stop -m immediate -D /var/lib/pgsql/9.2/data

(3)slave1のサーバをマスタに昇格(slave1のサーバで実施)


pg_ctl -D /var/lib/pgsql/9.2/data promote

■pg_basebackupによりslave2のリカバリを行い新マスタslave1に同期モードで接続する
事前にpostgresql.conf、pg_hba.conf、recovery.confは退避させておく。

(1)slave2のデータベースを停止

(2)slave2の/var/lib/pgsql/9.2/dataディレクトリの削除

(3)pg_basebackupよりslave1(新マスタ)のベースバックアップを取得し/var/lib/pgsql/9.2/dataに展開


pg_basebackup -h [slave1サーバ] -p [ポート] -U [レプリケーションユーザ] -D /var/lib/pgsql/9.2/data --xlog --progress --verbose

(4)退避させておいたpostgresql.conf、pg_hba.conf、recovery.confをdataディレクトリに戻し、recovery.confのprimary_conninfoのhostをslave1に変更後slave2のデータベースを再起動

(5)slave1でレプリケーションの状態を確認する


select application_name, state, sync_state from pg_stat_replication;
application_name | state | sync_state
------------------+-----------+------------
slave2 | streaming | async

(6)slave1のpostgresql.confのsynchronous_standby_namesにslave2を追加し設定の再読み込みを行う


pg_ctl -D /var/lib/pgsql/9.2/data reload

(7)slave1とslave2が同期モードでレプリケーションされていることを確認


select application_name, state, sync_state from pg_stat_replication;
application_name | state | sync_state
------------------+-----------+------------
slave2 | streaming | sync

(8)slave1のテーブルに適当なデータを投入してslave2に反映されることを確認

■ダウンした元マスタを復旧させ新マスタslave1に接続する
旧マスタのpostgresql.conf、pg_hba.confは事前に退避させておく。recovery.confはダウンするまではマスタであったため存在しない。

(1)masterの/var/lib/pgsql/9.2/dataディレクトリの削除

(2)pg_basebackupよりslave1(新マスタ)のベースバックアップを取得し/var/lib/pgsql/9.2/dataに展開


pg_basebackup -h [slave1サーバ] -p [ポート] -U [レプリケーションユーザ] -D /var/lib/pgsql/9.2/data --xlog --progress --verbose

(3)退避させたpostgresql.confとpg_hba.confを/var/lib/pgsql/9.2/data配下に戻す

(4)postgresql.confのsynchronous_standby_namesをコメントアウト、hot_standby = onとなっていない場合は合わせて設定を行う

(5)recovery.doneファイルをrecovery.confにリネームし、primary_conninfoのhostをslave1に、application_nameをmasterに設定した後、masterを再起動

(6)新マスタslave1でレプリケーションの状態を確認する


select application_name, state, sync_state from pg_stat_replication;
application_name | state | sync_state
------------------+-----------+------------
slave2 | streaming | sync
master | streaming | async

(7)slave1のpostgresql.confのsynchronous_standby_namesを以下のように設定し、再読み込みを行う。


synchronous_standby_names = 'master,slave2'


pg_ctl -D /var/lib/pgsql/9.2/data reload

(8)新マスタslave1で再度レプリケーションの状態を確認


select application_name, state, sync_priority, sync_state from pg_stat_replication;
application_name | state | sync_priority | sync_state
------------------+-----------+---------------+------------
slave2 | streaming | 2 | potential
master | streaming | 1 | sync

後から復旧してきた旧マスタが優先順位が上がり同期モードになり、slave2はpotencialに優先度が下がっています。

これは、synchronous_standby_namesに定義した順番が関係しています。

もし、synchronous_standby_names = 'slave2,master'と定義するとレプリケーションの状態は次のようになります。


select application_name, state, sync_priority, sync_state from pg_stat_replication;
application_name | state | sync_priority | sync_state
------------------+-----------+---------------+------------
slave2 | streaming | 1 | sync
master | streaming | 2 | potential

(9)新マスタslave1で適当なデータを投入するとレプリケーションをしている全ノードにデータが反映されることを確認

■まとめ
3台構成でレプリケーションをさせた状態で、各シナリオを検証しました。当たり前ですが全ノードがダウンしない限り最低1台で運転が可能です。
実運用ではPacemaker+Heartbeat(or Corosync)+pgpool-IIあたりを利用した運用が一般的かと思いますが、今回は動作確認が主なのでそういったソフトウェアは利用していません。

WatchServiceを利用したIOイベントハンドリング

Java7ではWatchServiceを利用して特定のディレクトリ内のイベントを監視するプログラムを簡単に記述することが可能です。

用途としては常駐プロセス系のプログラムで特定ディレクトリのファイル作成、更新、削除などのイベントをハンドリングし任意の処理を行うなど。
(たとえば設定ファイルを監視しておいて、更新されたらその内容を読み取り反映を行う、もしくは特定のファイルが作成されたことをトリガにして任意の処理を開始するといったことなど。)

実際の利用方法はFileSystemクラスのnewWatchService()メソッドよりWatchServiceを取得し、捕捉したいイベント(WatchEvent)とWatchServiceオブジェクトを監視対象となるディレクトリのPathオブジェクトに登録します。

その後は、どういったアプリケーションかにもよりますが、例えば無限ループなどでWatchServiceのtakeメソッドを呼び出し、イベントがキューイングされるまで待機し、イベント発生後takeメソッドの戻り値を利用して特定の処理を行うといったところかと思います。

【サンプルプログラム】

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;

public class Main {

    private static volatile boolean stop = false;

    public static void main(String[] args) {
        FileSystem fileSystem = FileSystems.getDefault();
        // 監視対象ディレクトリのPathを
        Path path = fileSystem.getPath("path_to_dir");
        try {
            // WatchServiceの取得
            WatchService watchService = fileSystem.newWatchService();
            WatchEvent.Kind<?>[] events = {
                    StandardWatchEventKinds.ENTRY_CREATE,
                    StandardWatchEventKinds.ENTRY_MODIFY,
                    StandardWatchEventKinds.ENTRY_DELETE
            };
            // 捕捉したいイベント種別とWatchServiceをPathに登録
            path.register(watchService, events);
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    stop = false;
                }
            });
            while (!Thread.currentThread().isInterrupted() && !stop) {
                System.out.println("Watching...");
                try {
                    // イベントが発生するまで待機
                    WatchKey watchKey = watchService.take();
                    String watchableName = watchKey.watchable().toString();
                    System.out.println("Watchable : " + watchableName);
                    
                    if (watchKey.isValid()) {
                        for (WatchEvent<?> event : watchKey.pollEvents()) {
                            System.out.println("イベント種別 : " + event.kind());
                            System.out.println("対象コンテンツ : " + event.context());
                            System.out.println("イベント回数 : " + event.count());
                            Path targetPath = FileSystems.getDefault().getPath(watchableName + "\\" + event.context());
                            processContent(targetPath, event);
                            System.out.println();
                        }
                        if (!watchKey.reset()) {
                            // Is this right ?
                            System.out.println("The watch key might be invalid.");
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("See you.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static final void processContent(final Path targetPath, final WatchEvent<?> watchEvent) {
        // コンテンツがファイルかつ更新イベントの場合コンテンツ内容を出力
        if (Files.isRegularFile(targetPath)) {
            if (watchEvent.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
                try {
                    if (Files.size(targetPath) > 0) {
                        try(BufferedReader reader = Files.newBufferedReader(targetPath, Charset.defaultCharset())) {
                            String line = null;
                            while ((line = reader.readLine()) != null) {
                                System.out.println(line);
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

上記サンプルはループ内でイベントが発生するまで待機し、ファイル作成、削除の場合はその旨イベントが発生したことを表示し、更新された場合は更新内容も出力します。

サンプルプログラムなのでかなり適当に作成していますが、挙動を確かめるには十分かと。。。

pgbenchを利用したベンチマークテスト実施方法

どんなデータベースでもそうですがインストール直後の設定で性能要件を満たせることはまずないと思います。
実運用前の導入試験においては必ず性能要件を確認するためにベンチマークテストを行うと思います。

PostgreSQLにはpgbenchというベンチマークツールがあるのでこれを利用する手順と、メモリ周辺の設定に関する個人的な見解を簡単にメモします。

今回示すのは基本的な利用手順だけにとどめます。
細かなことはマニュアルを見ればわかるし、システムの特性によってどのようなベンチマーク試験を行うか異なるし、そもそもの用途は自分用のメモなので。
(PCの突然の故障により過去の調査記録を失うのはもう嫌なので・・・)

pgbenchはPostgreSQL9.2の場合postgresql92-contribパッケージに含まれているので事前にインストールしていることが前提です。

ベンチマーク用のユーザとデータベースの作成


createuser -P [ベンチマークユーザ]
createdb -O [ベンチマークユーザ] [ベンチマークDB]

postgresql.confの設定
DBのボトルネックとなるのはやはりI/O待ちによる要因が一番大きく、検索、更新を行うデータのメモリヒット率がカギになってくると思います。
また、チェックポイント処理の間隔や、PostgreSQLは追記型のデータベースであり、HOTが導入されてたとはいえバキューム処理も考慮する必要があるのではないかと思います。
個人的にはまず入り口として以下の項目を中心に見ればいいのではないかなと思います。(自分はデータベースのスペシャリストではないので、間違いもあると思います。もし指摘があればコメント願います。)


・max_connections
→デフォルトは100コネクション(規模に合わせて適切に設定)。
・shared_buffers
→デフォルトは32MBと数GBメモリが主流の現在のシステムでは小さすぎる。利用可能な実メモリの1/4〜1/2程度
・temp_buffers
→一時テーブルにアクセスする際に利用される。(デフォルト値は8MB)巨大な一時テーブルを利用する場合は適切に設定
・work_mem
→ソートなどで利用される。(大体2048〜4096KB? 大量データを頻繁にソートするなどによって変わるかと。explainでクエリを要確認・・・)
・wal_buffers
→shared_buffersの1/32程度(?)
・checkpoint_segments
→1トランザクションファイルあたり16MBでデフォルト値が3のため、16*3で48MBとなりチェックポイント処理が頻発する可能性がある。
(規模にもよるが64〜256ぐらい?)
・wal_keep_segments
→8〜32が妥当らしい(?)
・effective_cache_size
→利用可能な実メモリの1/4〜1/2程度(?)
・maintenance_work_mem
→バキューム処理などで利用されるメモリ。更新などが多い場合に値を大きめに設定すると良い(?)

■pgbenchの利用方法
postgresql.confの設定が完了するといよいよ性能試験を行うわけですが、まずは最初に作成したベンチマークDBを初期化してベンチマーク用のテーブルを作成します。


pgbench -i -U [ベンチマークユーザ] [ベンチマークDB]

上記コマンドを実行すると、スケーリングファクタ1で指定ユーザのデータベースにベンチマーク用のテーブルとデータが作成されます。
スケーリングファクタ1で10万件のデータが作成されます。従って100だと1000万件です。デフォルト以外のスケーリングファクタを設定する場合は"-s"オプションの後に値を設定してください。

データベースの初期化が完了したら、あとはベンチマークを取るのみです。pgbenchを利用したもっとも基本的なベンチマーク試験を実行するには以下のコマンドを実行します。


pgbench -c 100 -t 100 -U [ベンチマークユーザ] [ベンチマークDB]

上記コマンドは指定したユーザのDBに対して100コネクション同時接続(-c)し、1コネクションあたり100トランザクションの処理(-t)を実行します。

ちなみにデータベース初期化時、及びベンチマーク実行時に-Uでユーザ名を指定しないとpostgresユーザで実行されます。ここでは、ベンチマーク用のユーザで実行したかったのであえて指定しましたが管理ユーザで実施する場合は不要です。

pgbenchはユーザ定義スクリプトなども利用可能なため興味がある方はマニュアルなどを見ていろいろ試してみてください。

PostgreSQLのレプリケーション設定

PostgreSQLのドキュメントや他のサイトでもすでに設定例が掲載されていますが自分用のメモとして残します。
本設定はyum経由でPostgreSQL9.2をインストールしていることを前提とているので、もし参考にする場合は以前の記事に従ってPostgreSQL9.2をインストールしてください。

■基本的な構成
マスターサーバ1台、スレーブサーバ2台。
マスタサーバはスレーブサーバ1台と同期レプリケーションを行い、もう1台とは非同期レプリケーションを行う。

■マスタ側の設定
レプリケーション用のユーザ作成


$psql -U postgres
CREATE ROLE レプリケーション用ユーザ LOGIN REPLICATION PASSWORD 'xxx';

PostgreSQLのdataディレクトリ配下にある設定ファイルをそれぞれ以下のように設定する。
(9.2をyum経由でインストールした場合は/var/lib/pgsql/9.2/data配下)
postgresql.confの設定


vi postgresql.conf
wal_level = hot_standby
fsync = on
synchronous_commit = on
wal_sync_method = fsync

max_wal_senders = 3 # スレーブ数+1
wal_keep_segments = 8
replication_timeout = 60s
synchronous_standby_names = '同期スレーブサーバのホスト名(複数指定する場合は","で区切る)'

synchronous_standby_namesはスレーブ側のapplication_nameと一致させること。
synchronous_commitオプションでレプリケーションの同期方式を設定しるのですが、設定可能な方式は以下の4通りのようです。


on:マスタとスレーブでWALのディスク書込みまでを同期する
remote_write:マスタはディスク書込みまで同期するがスレーブはメモリ書込みまでを同期
local:マスタは同期するがスレーブは非同期
off:完全非同期

pg_hba.confの設定
スレーブからのアクセス設定を行う。


vi pg_hba.conf
[個別に設定する場合]
host replication レプリケーション用ユーザ スレーブのIP/32 password

[同一ネットワーク内を対象とする場合]
host replication レプリケーション用ユーザ xxx.yyy.zzz.0/24 password

それぞれのネットワーク環境によって適切に設定すること。
同一ネットワーク内で複数台構成する場合はxxx.yyy.zzz.0/24などとする方が楽。

マスター側のPostgreSQLの起動


service postgresql-9.2 start (既に起動済みの場合はrestart)

スレーブ設定の前に適当なDBの作成とテーブルの作成を行っても良い。

■スレーブ側の設定
pg_basebackupでマスターのベースバックアップをスレーブ側に展開する。(マスタ側にデータベースなどを作成している場合は、すべてのスレーブで実施すること。)
実施前に/var/lib/pgsql/9.2配下のdataディレクトリを削除もしくは退避させておくこと。特に、postgresql.confとpg_hba.confは退避させておくことをおすすめします。


pg_basebackup -h マスタサーバ -p ポート番号 -U [レプリケーション用ユーザ] -D /var/lib/pgsql/9.2/data --xlog --progress --verbose

pg_basebackupはストリーミング・レプリケーションに依存したツールらしく、ユーザにレプリケーション用のユーザを指定する必要があります。ユーザを指定しない場合はpostgresユーザで実行されるようですが、今回の例ではマスタ側のpg_hba.confにレプリケーション用のユーザにpostgresを指定していないためエラーとなります。

postgresql.confの設定
pg_basebackupによりマスタサーバのpostgresql.confがスレーブにバックアップされているため、それを直接修正する。


hot_standby = on
↓以下3つをコメントアウト(必要ないかも)
#max_wal_senders = 3
#wal_keep_segments = 8
#replication_timeout = 60s

↓synchronous_standby_namesを無効化
#synchronous_standby_names = '' #無効化

recovery.confの設定
recovery.confのサンプルをコピー


cp /usr/pgsql-9.2/share/recovery.conf.sample /var/lib/pgsql/9.2/data/recovery.conf

vi recovery.conf
standby_mode = on
primary_conninfo = 'host=マスタサーバのホスト名 port=ポート番号 user=[レプリケーション用ユーザ] password=xxx application_name=スレーブサーバ名'

各スレーブを起動しレプリケーションの状態を確認


psql -U postgres
select application_name, state, sync_state from pg_stat_replication;
application_name | state | sync_state
------------------+-----------+------------
slave1 | streaming | sync
slave2 | streaming | async

application_nameはrecovery.confで設定した名前が表示される。

■カスケードレプリケーション
カスケード型のレプリケーションの設定方法です。
マスタサーバとスレーブサーバ1が同期レプリケーションを行い、スレーブサーバ1とスレーブサーバ2が非同期レプリケーションを行うようにします。なおカスケード時のスタンバイサーバへの接続は現状非同期のみののようです。

なお本設定例は先に述べたレプリケーションの設定との差分となる部分のみを示します。

構成イメージ


master ---> slave1 ---> slave2
同期 非同期

マスタサーバとスレーブサーバ1のpostgresql.confを以下のように設定


max_wal_senders = 2 #スレーブ数+1

#スレーブサーバ1で以下の設定をコメントアウトしている場合はコメントを外してください。
wal_keep_segments = 8
replication_timeout = 60s

スレーブサーバ2のrecovery.confを以下のように設定


primary_conninfo = 'host=スレーブサーバ1のホスト名 port=ポート番号 user=[レプリケーション用ユーザ] password=xxx application_name=スレーブサーバ2名'

マスタサーバ、スレーブサーバ1、スレーブサーバ2の順番に起動し状態を確認

マスタサーバで状態確認


psql -U postgres
select application_name, state, sync_state from pg_stat_replication;
application_name | state | sync_state
------------------+-----------+------------
slave1 | streaming | sync

スレーブサーバ1で状態確認


psql -U postgres
select application_name, state, sync_state from pg_stat_replication;
application_name | state | sync_state
------------------+-----------+------------
slave2 | streaming | async

CentOS6にYumを利用してPostgreSQL-9.2をインストールする

つい先日CentOSPostgreSQLをインストールする際のメモを記載しましたが、CentOS6の標準ではPostgreSQLのバージョンが8.4なので最新版ではありません。
サーバが破損する以前までは、昔からの名残でバージョン8を利用していたのですが、ストリーミング・レプリケーションを利用したかったので9.2をインストールしなおしました。

今回はその時の手順を記載します。

PostgreSQL8.4のインストールされているパッケージの確認と削除


$yum list | grep postgres
$su -
#yum remove postgresql-server
#yum remove postgresql
#yum remove postgresql-libs

PostgreSQLのダウンロードサイトより取得したコミュニティリポジトリrpmを追加


#rpm -ivh pgdg-centos92-9.2-6.noarch.rpm
#cd /etc/yum.repos.d
#ls
...
pgdg-92-centos.repo
...
pgdg-92-centos.repoというファイルが存在すればOKです。

GPGキーのインポート


#rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-PGDG-92

PostgreSQLyumに登録されているか確認


#yum list | grep postgres*
postgresql92〜と言う名前のリストが出力されるとOK。この時点では8.4のパッケージリストも表示される。

CentOSのベースリポジトリからpostgresql関連のパッケージ情報を取得しないようにするために"exclude=postgresql*"をCentOS-*.repoファイルのenabled=0となっていないブロックに追加。(実施前にバックアップをとるのもよし。)

自分の場合はCentOS-Base.repoのみだったのでそれぞれ以下のように設定。


#vi CentOS-Base.repo
[base]
...
exclude=postgresql*

[updates]
...
exclude=postgresql*

[extras]
...
exclude=postgresql*

旧バージョンのパッケージが表示されていないことを確認


#yum list | grep postgres*

PostgreSQLのインストール


#yum -y install postgresql92-server

サービス名の確認


#chkconfig --list | grep postgres

→サービス名はpostgresql-9.2

データベースの初期化を行いデータベースディレクトリの作成を行う


#service postgresql-9.2 initdb

postgresユーザにスイッチしてその後の作業を実施。これ以降は8.4と同じなので省略します。

[注意事項]
postgresql.confなどの設定ファイルを含めインストールディレクトリは/var/lib/pgsql/9.2/data配下となっています。
・postgresユーザのパスワード変更とファイアウォール設定は8.4インストール時に設定していれば不要。