minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

レプリケーションとは、あるデータベースから他のデータベースに複製を作ることです。
これは通常、以下のような理由から使われます。

MySQLでは「一方向レプリケーション」を採用しています。
一つのサーバを「マスタ」として機能させ、残りのサーバが「スレーブ」になります。
データの複製は「マスタ→スレーブ」という方向でのみ行われます。

そのため、データの更新は必ずマスタサーバで実行する必要があります。
マスタサーバで更新を行うと、その更新内容が全てのスレーブサーバに通知され
スレーブサーバはマスタサーバと同じ更新処理を行います。これにより、マスタとスレーブの内容が一致します。

スレーブサーバで更新を行うと、その内容はマスタサーバや他のスレーブサーバに反映されないので
整合性が崩れてしまいます。

レプリケーション処理の概要

まず、ある地点でのマスタサーバの全データをダンプします。
そして、マスタサーバで「更新ログ」を出力させます。
これは、データベースへのあらゆる更新・削除処理をログ出力したものです。

スレーブサーバ上で、先ほどマスタから取ってきたダンプデータを展開します。
この時点で、スレーブサーバの内容は(ダンプを取った段階の)マスタサーバと全く同じになります。
その後、スレーブサーバはマスタサーバで出力された更新ログを随時受け取り
その内容をスレーブサーバ上で反映させます。
これによって、スレーブサーバはマスタサーバと全く同じ内容を保つことが出来ます。

スレーブサーバは止めてもOK

何らかの事情があってスレーブサーバを止めた場合でも、スレーブサーバを再開させれば
再びマスタサーバと全く同じ内容に戻すことが出来ます。
これは、スレーブサーバが「マスタサーバの更新ログをどこまで処理したか」という情報を持っているからです。

スレーブサーバを止めている間でも、マスタサーバの更新ログは(マスタサーバ上で)蓄積されていきます。
スレーブサーバを再開したとき、スレーブはマスタに
「スレーブサーバを止めた時間以降の更新ログを送って下さい」という命令をマスタサーバに送ります。
この命令を受けて、マスタサーバはその時間から今までの更新ログをスレーブサーバに送ります。
これにより、スレーブサーバは大急ぎで今までの遅れを取り戻すのです(笑)。

レプリケーションを使ってみる

早速、レプリケーションを試してみましょう。
まず、2つのデータベースサーバを用意します。
今回は簡略化のために同一サーバ上からこれらを動かします。
MySQLのバージョンはマスタサーバが「3.23.53」、スレーブサーバが「4.0.23」です。

なお、マスタサーバで使用しているテーブルは全て「MyISAM型」です。
ISAM(古い型)やInnoDB(トランザクションテーブル)を使っている場合は、ほんの少し作業が複雑になるはずです。

レプリケーション用ユーザを作る

レプリケーションは、専用のユーザを通して行うことが推奨されています。
作業自体は簡単ですのでそれに従いましょう。
マスタサーバ上で以下を実行します。

mysql> GRANT FILE ON *.* TO repl@'%' IDENTIFIED BY '<password>';

これにより、全てのホストから接続可能な「repl」というユーザを作成してこのユーザにFILE権限を付加します。
マスタサーバがMySQL4.0.2より新しければ、代わりに以下を実行します。

mysql> GRANT REPLICATION SLAVE ON *.* TO repl@'%' IDENTIFIED BY '<password>';

マスタサーバで更新ログを有効にする

マスタサーバで更新ログ(バイナリログとも言う)を有効にします。
my.cnf ファイルに以下の記述があるか確認して下さい。

[mysqld]
log-bin

無ければ、この記述を追加した上でMySQLサーバを再起動します。

マスタデータのスナップショットを撮る

現段階でのスナップショットを撮ります。
マスタサーバ上で以下を実行します。

mysql> FLUSH TABLES WITH READ LOCK;

これにより、テーブルへの書き込みを禁止します。

スナップショットを撮るのは非常に簡単です。
MySQLのデータディレクトリ(通常は$MYSQL_HOME/data)の内容をまるごとアーカイブするだけです。

shell> cd $MASTER_MYSQL_HOME/data
shell> tar cvf /tmp/snapshot.tar .

注意点

ただし、この方法ではうまくいかないことがあります。
スレーブ側で展開したときに、テーブルが壊れていることがあるのです。
理由はよくわかりませんが、

・マスタ⇔スレーブ間でOSが異なる
・マスタ⇔スレーブ間でMySQLのバージョンが異なる

のような場合に起こるのだと思われます。
ちなみにうちの環境では両者が当てはまるのですが
一部のテーブルでデータ移行がうまくいかなかったので
そのテーブルに関しては mysqldump を使ってSQLレベルで移行しました。

更新ログの状態を取得する

スナップショットを撮ったら、MySQL上で以下を実行します。

mysql> SHOW MASTER STATUS;

実行したら、画面に出力された「File」と「Position」カラムの内容をメモしておきます。
これは、現時点での更新ログファイル名とそのオフセットです。
この後スレーブサーバ上でレプリケーションを有効にする際に必要になります。

ここまで来たら、テーブルロックを解除します。

mysql> UNLOCK TABLES;

レプリケーションの設定を行う

マスタサーバの my.cnf ファイルで以下の記述を確認します。

[mysqld]
log-bin
server-id=1

server-id は、1以上の整数であれば何でも良いです。

次に、スレーブサーバの my.cnf ファイルで以下の記述を確認します。

[mysqld]
server-id=2

server-id は、先ほどマスタサーバに設定した値とは異なる値を指定する必要があります。
もし複数のスレーブサーバを起動させる場合、それぞれの server-id は全て異なる値にします。

スナップショットを展開する

マスタサーバで撮ったスナップショットを、スレーブサーバ上で展開します。

shell> cd $SLAVE_MYSQL_HOME/data
shell> tar xvf /tmp/snapshot.tar

もし mysqldump による移行をする場合は、

shell> mysql < table_dump.sql

のようになるでしょう。

展開したら、スレーブサーバを起動させます。

shell> cd $SLAVE_MYSQL_HOME
shell> ./safe_mysqld &

レプリケーションを行う準備

スレーブサーバのMySQLで以下を実行します。

mysql> CHANGE MASTER TO
           MASTER_HOST='<master host name>',
           MASTER_USER='<replication user name>',
           MASTER_PASSWORD='<replication password>',
           MASTER_LOG_FILE='<recorded log file name>',
           MASTER_LOG_POS=<recorded log offset>;

各値は、環境に応じて書き換えて下さい。

レプリケーション開始

さぁ、ここまで来たらあとはレプリケーションを開始するだけです。
スレーブサーバ上から以下を実行しましょう。

mysql> START SLAVE;

これでレプリケーションが開始されます。
試しに、マスタサーバ上で何らかのテーブルにレコードを挿入してみて下さい。
そして、スレーブサーバ上でその内容が反映されていることを確認できれば
レプリケーションは確実に動作していることになります。

レプリケーションで使用するスレッド

レプリケーションが動いていることを確認する一番簡単な方法は、

SHOW PROCESSLIST

コマンドによりMySQLで実行中のスレッドを確認することです。

MySQLは、接続(Connection)一つにつき一つのスレッドを割り当てます。

レプリケーションのマスタサーバは、各スレーブ毎に一つのスレッドを動かしています。
スレーブサーバは、レプリケーション用のスレッドを「二つ」動かしています。

二つのスレッドの役割

一つは「I/Oスレッド」と呼ばれるもので、マスタサーバとバイナリログ等のファイル送受信を担当します。
マスタサーバからバイナリログを受け取ったら、それをスレーブサーバ上に「リレーログ」という形で保存します。
バイナリログとリレーログは全く同じ形式ですが、その役割の違いから呼び名が分かれています。

もう一つは「SQLスレッド」と呼ばれ、レプリケーションSQL文の実行を担当します。
I/Oスレッドが作成したリレーログを読み取り、それに応じたSQL文を発行します。
リレーログはSQL文を実行すれば必要が無くなるので、SQLスレッドは状況に応じてリレーログを自動的に削除します。

スレッドを分けている理由

スレッドを二つに分けているのは、両者の処理速度にはかなりの差があるからです。
I/Oスレッドはバイナリログを受け取ってリレーログとして保存するだけですが、
SQLスレッドはそのリレーログを解析してSQL文を発行しなければなりません。

通常、SQLスレッドはI/Oスレッドよりも多くの処理時間を要します。
スレッドを分けていれば、I/OスレッドはSQLスレッドを待たずにマスタサーバからバイナリログを受け取ることが出来ます。
スレーブがバイナリログを受け取れば、マスタサーバ上ではそのバイナリログを削除することが可能です。
もしスレッドを分けていなかったら、スレッドはSQL文の実行を待ってからバイナリログを受け取る必要があるので
バイナリログの取得に相当の時間が掛かってしまいます。
その為、マスタ側でバイナリログを削除できるタイミングも大幅に遅れてしまいます。

レプリケーションに失敗した場合

MySQLではレプリケーションを時系列で過去のものから処理していくので
もしレプリケーション途中でエラーが発生した場合
そこで止まってしまいます。
つまり、そのエラーを解決しない限り以降のレプリケーションは
止まったままになります。

MySQLのログにエラーの内容が出ているはずですので、それを見て解決することになります。
もし見あたらなかったら

stop slave;
start slave;

とやれば、再度レプリケーションを再開しようとするので
もし失敗した場合はまたログにエラーメッセージが出ます。