2010-02-19

MySQL Clusterを動かしてみたよ。

 MySQL Clusterに関する情報が少ないので、役に立たないかもしれないけど動かした時のレポートを残しておくよ。

 ちなみにWindows版の7.0.9(プレビュー版)を使ったけど、いつのまにか http://dev.mysql.com/downloads/cluster/ からなくなってますね。。。注意書きを無視する人が多かったせいじゃろか。

参考URL

 基本的に奥野さんの記事と、MySQLの公式ドキュメントを読みながら。

 公式ドキュメントの日本語版は記述が古いので、英語版を読んだほうがよさそう。

起動手順

 詳しいことは上記の参考URLを読む前提で。

  • ndb_mgmd を起動する
  • ndbd を起動する(start待ちにする)
  • ndb_mgmで all start する
  • mysqld を起動する

――でよさげ。

補足
  • ndbd(データノード)は基本的に一斉に起動する必要がある(らしい)。
    • 必要なデータノードが起動していないと(接続できないと)、データノードは自動的に停止するため。
  • そのため -n オプションで起動待ちにし、ndb_mgm から起動コマンドを送信して立ち上げる。
  • MySQL Clusterで面倒なのは、ndbdのパラメータ設定なんだと思う。
    • 起動して使うところまではそんなに難しくはないけど、チューニングはイマイチよく分からない。

ndbd(データノード)が停止した場合について

  • ndbdを再起動すれば、勝手に同期を始める
    • 「-n」で起動待ちにする必要はない。起動待ちにした場合は ndb_mgm から startさせるのを忘れないこと。
  • ハードを入れ替える場合は、IdはそのままでHostnameを変えればいい(はず)
    • データは自動的に同期をとるので、起動するだけでいいみたい。
  • 同期には時間がかかる(ネットワークが遅かったからかもしれない)
    • バックアップがあるならそれでリストアしてから起動すれば同期に時間がかからないのかな? このへんはもうちょっと調べる必要がありそう。

データノードを増やす方法について

  • config.ini の [NDBD] セクションの追加で増やせる(はず)
  • [NDBD]は2つセットで増やす必要がある
  • データノードを増やす場合は、一時的に全てのデータノードを停止する必要がある
    • その間にengine=ndbなテーブルにアクセスした場合はエラーになる

手順
  • config.iniの書き換え
  • ndb_mgmで ndb_mgmdを restart -i(-i でconfig.iniを読み込ませる)
  • ndb_mgmで all stopして全てのndbd(データノード)を停止
  • 全ての ndbd を再起動
  • ndb_mgmで all startして全てのndbdを開始
  • ndb_mgmで create nodegroup ,してノードグループを増やす(不要?)
    • ノードグループを増やす必要があるのかはちょっと分からなかった。

 そうそう、データノードが全て停止するとSQLノード(mysqld)との接続も切れます。この間はengine=ndbなテーブルにはアクセスできません(エラーになる)。「show tables」をするとレスポンスが返って来ずに待ち状態になりましたが、これがプレビュー版だからなのか正式版でもそうなのかは不明。

 データノードが復活すれば自動的にSQLノードと再接続し、普通にアクセスできるようになります。

Tokyo Tyrantについてのおさらい

 Tokyo Tyrantについては平林幹雄さんのサイトにいろいろあるし、使用例も探せば出てくるのですが、せっかく使ってみたので記録を残しておく。

  • Tokyo Tyrantは基本的にTokyo Cabinetのラッパーである
  • Tokyo Cabinetでは以下のものが扱える
    • ハッシュデータベース(.tch)
    • B+木データベース(.tcb)
    • 固定長データベース(.tcf)
    • テーブルデータベース(.tct)
  • Tokyo Tyrantは上記のいずれかのデータベースにアクセスできるように設定できる
    • 複数種類のデータベースにアクセスしたい場合は、サーバを別々に立てる必要がある
    • テーブルデータベースを立てておけばたいていの要件には合う、かもしれない
  • チューニングのためにパラメータをどうすればいいかはまだよく分からないので、あとで調べる
    • というか、TTでファイルオープンする時のオプションをちゃんと調べる必要がある。途中でパラメータが増えてそうだし(動的デフラグとか)

使用例:PHPでのセッション情報の保存

  • セッション情報をマスタ/マスタ構成のTokyo Tyrantに保存する
    • メインが落ちていたらサブにアクセスする
  • テーブルデータベースを利用する
    • セッションの有効時間が切れていたら削除するため
  • openpearNet_TokyoTyrantを利用する

 もっといい方法があるかもしれませんが、いちおうこんな感じで。

Tokyo Tyrantの設定

 Tokyo Tyrantは設定ファイルとかないみたいなので、起動スクリプトに設定を書いておく。

 http://1978th.net/tech/promenade.cgi?id=58 を参考にした。

ttserver \
  -port 1978 \               # 自分のポート指定
  -dmn \                     # デーモン化
  -le \                      # ログはエラーログのみ
  -pid /hoge/ttserver.pid \  # プロセスIDファイル指定
  -log /hoge/log \           # ログファイル指定
  -ulog /hoge/ulog \         # 更新ログファイル指定
  -ulim 1024m \              # 更新ログサイズ指定
  -sid 10001 \               # サーバID指定
  -mhost tthost-02 \         # マスタホスト指定
  -mport 1978 \              # マスタポート指定
  -rts /hoge/ttserver.rts \  # レプリケーションタイムスタンプファイル指定
  '/hoge/session.tct#idx=at:dec#dfunit=8#bnum=2000000'	# テーブルDB、atに数値インデックス、動的デフラグ

PHPのコード

 セッションの差し替え方法はこれでいいはずだけど、今のPHPならもっといい方法があるかもしれません。


require_once 'Net/TokyoTyrant/Table.php';
class Session_TokyoTyrant {
	static $host_list = array(
		'tthost-01',
		'tthost-02',
	);
	static $port = 1978;
	static $timeout = 1;
	static $retry_count = 10;

	static $ttt;
	static $expire_count_limit = 100;

	static $save_path;
	static $session_name;
	function open($save_path, $session_name)
	{
		self::$save_path = $save_path;
		self::$session_name = $session_name;

		$ttt = new Net_TokyoTyrant_Table();

		$port = self::$port;
		$timeout = self::$timeout;
		$i = self::$retry_count;
		while ($i > 0) {
			foreach (self::$host_list as $host) {
				try {
					$ttt->connect($host, $port, $timeout);
					break 2;
				}
				catch (Net_TokyoTyrantException $e) {
					--$i;
				}
			}
		}

		if ($i <= 0) {
			header("HTTP/1.0 500 Internal Server Error");
			echo "500 Internal Server Error";
			exit;
		}

		self::$ttt = $ttt;
		return true;
	}

	function close()
	{
		if (self::$ttt) {
			self::$ttt->close();
		}
		return true;
	}

	function read($id)
	{
		$row = self::$ttt->get(self::_create_id($id));
		return $row['sdata'];
	}

	function write($id, $sess_data)
	{
		$row = array(
			'sdata' => $sess_data,
			'at' => $_SERVER['REQUEST_TIME'],
		);
		self::$ttt->put(self::_create_id($id), $row);

		return true;
	}

	function destroy($id)
	{
		self::$ttt->out(self::_create_id($id));
		return true;
	}

	function gc($maxlifetime)
	{
		$ut_expire = $_SERVER['REQUEST_TIME'] - $maxlifetime;

		$ttq = self::$ttt->getQuery();
		$ttq->addcond("at", Net_TokyoTyrant_Query::QCNUMLT, $ut_expire);
		$ttq->setorder("at", Net_TokyoTyrant_Query::QONUMASC);

		//$ttq->searchout();	// searchout()が使えなかったので、下記で代用
		$ttq->setlimit(self::$expire_count_limit);
		$sid_list = $ttq->search();
		if ($sid_list) {
			foreach ($sid_list as $sid) {
				self::$ttt->out($sid);
			}
		}

		return true;
	}

	function _create_id($id) {
		return self::$session_name . "\0" . $id;
	}

	function setup() {
		$class = __CLASS__;
		session_set_save_handler(
			array($class, "open"),
			array($class, "close"),
			array($class, "read"),
			array($class, "write"),
			array($class, "destroy"),
			array($class, "gc")
		);

		register_shutdown_function('session_write_close');
	}
}

Session_TokyoTyrant::setup();


余談

 いままでちゃんとTokyo Tyrant(Tokyo Cabinet)に触ったことなかったけど、なかなかいい感じです(特にテーブルデータベース)。

 いま使ってるO/Rマッパー(Class::DBIみたいな感じのもの)と互換性があるものを作れば、一部のDBテーブルについては差し替えられる気がしました。(MySQLを使ってるので)auto_incrementをどうする? ってのはありますけど。シーケンスみたいなのを作るべきかな……。

 まあ、バックアップや障害時の対応などの運用を考えると、それだけではダメなんですけどね。

追記:Net_TokyoTyrantの修正

 Net/TokyoTyrant/Query.php に以下のメソッドを追加すれば searchout() を使えるようになって使い勝手がよくなる、はず。


    public function searchout()
    {
        $params = array_values($this->params);
        $params[] = 'out';
        $values = $this->tttable->misc('search', $params, 0);

        return isset($values)? true: false;
    }