2011年8月18日木曜日

EC2でMySQL(Spider編1 Spiderってなんじゃ?)

今回はEC2上での、MySQLとSpiderの話になります。
MySQLでの負荷分散というとレプリケーションがメインでしたが、参照系の負荷は分散できても更新処理は分散することが難しく、それがボトルネックになっていました。
このSpiderを利用すると、更新も参照も負荷分散をすることができます。

Spider斯波健徳さんが開発したMySQLのストレージエンジンで、MySQLでのシャーディング(データを分散して保存することで負荷を分散すること)がすることができます。
Spiderには以下の機能と特徴があります。

  • 異なるMySQLインスタンスのテーブルを同一のインスタンスのテーブルのように扱うことを可能にします。
  • xaトランザクションを含むトランザクションをサポートしているため、更新系DBのクラスタリングに利用することが可能です。
  • テーブルパーティショニングをサポートしているため、パーティショニングのルールを利用して、同一テーブルのデータを複数サーバに分散配置することが可能です。
  • spiderストレージエンジンのテーブルを作成すると、MySQL内部ではファイルへのシンボリックリンクのように、リモートサーバのテーブルへのテーブルリンクを生成します。
  • テーブルリンクは、具体的にはローカルMySQLサーバからリモートMySQLサーバへのコネクションを確立することで実現されます。
  • リンク先のテーブルのストレージエンジンに制限はありません。


Spiderの構成

Spiderはストレージエンジンなのでテーブル単位で分散ができます。Spiderテーブルはデータそのものは保持しておらず、データ自体は接続先の分散用テーブルに保持され、Spider自体はデータノードへの分散、集約のためのゲートウェイとして機能します。

分散と集約には、パーティションの機能を利用しています。
本来パーティションは、そのテーブル内のデータ領域を内部で分けておくことによって、検索などの効率をあげるためのシステムですが、Spiderはこの設定を擬似的に利用することで、その領域を他のDBインスタンスにまで拡大して分散、集約するように作られています。
言い換えれば他のDBを全て1つのDBの1パーティションとしてあつかえるストレージエンジンです。

今回はサンプルとしてmemberテーブルに対する書き込みを分散するという目的で、以下のようなEC2インスタンスの構成で試してみます。
[]内は仮のIPです。
ここでは、123.123.123.123をSpiderノード、残りのDB1,DB2をデータノードと呼ぶことにします。
Spiderは更新/参照するべきデータノードをテーブルパーティション設定によって判断します。

│[123.123.123.123]
            ┌──┴──┐
            │  Spider  │
            └──┬──┘
                  │
                  │
    ┌──────┴───────┐
    │[111.111.111.111]           │[222.222.222.222]
┌─┴─┐                    ┌─┴─┐
│ DB1  │                    │  DB2 │
└───┘                    └───┘

Spider、DB1、DB2の各データベースは共通して、以下のデータベースを持つことにします。
また、3つのノードで別々のDBやユーザー、パスワードのものを接続することも可能です。
  • データベース名:cloudpack
  • DBのユーザー名:cloudpack_user
  • DBのパスワード:cloudpack_pass



データノードの設定

データノードは普段使用している通常のMySQLでかまいません。特別なインストールも必要なしです。
前々回と同様、Linuxバイナリを使用してインストールします。


MySQLのインストールと起動
 mysqlのダウンロードページから適切なバイナリを選んでダウンロードします。
su -
cd /usr/local/src

wget http://downloads.mysql.com/archives/mysql-5.5/mysql-5.5.14-linux2.6-i686.tar.gz

tar xzvf mysql-5.5.14-linux2.6-i686.tar.gz
mv mysql-5.5.14-linux2.6-i686 /usr/local/mysql-5.5.14
ln -s /usr/local/mysql-5.5.14 /usr/local/mysql

groupadd mysql
useradd -r -g mysql mysql

cd /usr/local/mysql
chown -R mysql:mysql .
yum list installed | grep libaio
./scripts/mysql_install_db --user=mysql
chown -R root .
chown -R mysql data
cp support-files/my-medium.cnf /etc/my.cnf
cp support-files/mysql.server /etc/init.d/mysqld
mkdir -p /var/log/mysql
chown -R mysql:mysql /var/log/mysql

/etc/init.d/mysqld start
chkconfig mysqld on

ユーザーの作成
mysql -u root

mysql> GRANT ALL PRIVILEGES ON *.* TO 'cloudpack_user'@localhost IDENTIFIED BY 'cloudpack_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'cloudpack_user'@'%' IDENTIFIED BY 'cloudpack_pass';
mysql> flush privileges;

データベースの作成
mysql> create database cloudpack;

テーブルの作成
mysql> use cloudpack;
mysql> create table member(
id int(11) auto_increment,
name varchar(256),
primary key(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

接続の許可
 データノードのセキュリティグループに3306を追加、許可IPに接続先サーバーのIPを指定します。





Spiderノードの設定

前述のとおり、Spiderは更新/参照するべきデータノードをテーブルパーティション設定によって判断します。
今回はKEYパーティションを利用した分散をしてみます。
Spiderを導入するには、素のMySQLのパッチ適用やコンパイルなどが必要ですが、Spiderやパッチ込みのLinuxバイナリが提供されているので、今回はこれを使用します。

Spiderビルド済みMySQLのインストール
 Spiderのダウンロードページからビルド済みバイナリをダウンロードして展開します。
su -
cd /usr/local/src

wget http://spiderformysql.com/downloads/spider-2.26/mysql-5.5.14-spider-2.26-vp-0.15-hs-1.0-linux-i686-glibc23.tgz
tar xzvf mysql-5.5.14-spider-2.26-vp-0.15-hs-1.0-linux-i686-glibc23.tgz
mv mysql-5.5.14-spider-2.26-vp-0.15-hs-1.0-linux-i686-glibc23 /usr/local/
ln -s /usr/local/mysql-5.5.14-spider-2.26-vp-0.15-hs-1.0-linux-i686-glibc23 /usr/local/mysql

groupadd mysql
useradd -r -g mysql mysql
cd /usr/local/mysql
chown -R mysql:mysql .
scripts/mysql_install_db --user=mysql
chown -R root .
chown -R mysql data
cp support-files/my-medium.cnf /etc/my.cnf
cp support-files/mysql.server /etc/init.d/mysqld

mkdir -p /var/log/mysql
chown -R mysql:mysql /var/log/mysql

/etc/init.d/mysqld start
chkconfig mysqld on

初期化スクリプトの実行
 mysqlデータベースにSpiderがバックエンドで使用するのに必要なテーブルを作成するためのSQLファイルを同じページからダウンロードして実行します。
cd /usr/local/src
wget http://spiderformysql.com/downloads/spider-2.26/spider-init-2.26-for-5.5.14.tgz

tar xzvf spider-init-2.26-for-5.5.14.tgz 
mysql -u root < install_spider.sql

ユーザーの作成
mysql -u root

mysql> GRANT ALL PRIVILEGES ON *.* TO 'cloudpack_user'@localhost IDENTIFIED BY 'cloudpack_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'cloudpack_user'@'%' IDENTIFIED BY 'cloudpack_pass';
mysql> flush privileges;

データベースの作成
mysql> create database cloudpack;
mysql> use cloudpack;

テーブルの作成
 いよいよSpiderストレージエンジンの作成を行います。
mysql> create table member(
id int(11) auto_increment,
name varchar(256),
primary key(id)
) engine = Spider DEFAULT CHARSET=utf8
CONNECTION ' table "member", user "cloudpack_user", password "cloudpack_pass" '
PARTITION BY KEY() (
    PARTITION db1 comment 'host "111.111.111.111", port "3306"',
    PARTITION db2 comment 'host "222.222.222.222", port "3306"'
);
SpiderはMyISAMやInnoDBと同じくストレージエンジンなので、engine=Spiderと記載します。
そして、このCREATE TABLE文でのPARTITION節とCONNECTIONがSpiderの分散設定の要です。
ここでは、KEYパーティションによりパーティションをデータノードの数だけ、つまり2つに分けてあります。

KEYパーティションは簡単に言うとPRIMARY KEYのHash値を元にデータを格納すべきパーティションを決定する方式です。
もちろんそれ以外のパーティションタイプを使用することも可能です。
Spiderはここで定義したPARTITION分割ルールにしたがって、更新・集約するデータノードを決定します。

そしてそれぞれのデータノードの接続先情報を定義するのが、PARTITION節のCOMMENT文字列と、ストレージエンジンの後のCONNECTION文字列です。
これらは通常は別の目的で使用されるものですが、Spiderエンジンはこれらをデータノードの接続情報の設定として解釈するように動作します。
どちらもデータノードへの接続情報などの情報を記載することができますが、主な利用の仕方としては、
  • CONNECTION文字列:テーブル全体としての共通の接続設定 
  • COMMENT文字列:各データノード用の独自の接続設定
というように分けて設定することが多いようです。
これらの設定文字列には多数の細やかな設定ができるので、詳しくはプロダクト同包のマニュアルを参照してください。

ここでは、CONNECTION文字列に、DB名、DBユーザー名、DBパスワードを、各PARTITIONのCOMMENT文字列には、各データノードのホスト名とポート番号を記載しました。
もしデータノードが3つだった場合はPARTITION句を3つ設定しますし、それぞれDB名やテーブル名が異なっている場合には、databaseやtableなどの情報もPARTITION節ののCOMMENTのほうにそれぞれ記載します。


動作の確認

それでは実際にどのようにSpiderが動作するのか、確認してみます。
まず、Spiderノードで何件かINSERTしてみます。
mysql> INSERT INTO member (name) VALUES('memorycraft'),('ichiro'),('jiro'),('sub-LOW'),('shiro');
Query OK, 5 rows affected (0.01 sec)
Records: 5 Duplicates: 0 Warnings: 0

mysql> select * from member;
+----+-------------+
| id | name        |
+----+-------------+
|  1 | memorycraft |
|  3 | jiro        |
|  5 | shiro       |
|  2 | ichiro      |
|  4 | sub-LOW     |
+----+-------------+
5 rows in set (0.00 sec)
一見、普通の1つのテーブルに見えます。idの順がばらばらですが、通常のテーブルではauto incrementなカラムがあれば、その順にSELECTされることが多いです。
しかし、基本的にORDER BY句がないと順序保証はされないので、特別変わった動作ではなく通常のMySQLの仕様の範囲です。

SpiderテーブルはDROP TABLEしてもデータノードのテーブルは削除されません。これはSpiderテーブルが接続や分散/集約のハブとして機能しているだけで、データの保持、管理を行っていないことをあらわします。DROP TABLEしたあとに再度CREATE TABLEをするだけで、SELECT結果は元通りのデータが返ってきます。
mysql> create table member(
id int(11) auto_increment,
name varchar(256),
primary key(id)
) engine = Spider DEFAULT CHARSET=utf8
CONNECTION ' table "member", user "cloudpack_user", password "cloudpack_pass" '
PARTITION BY KEY() (
    PARTITION db1 comment 'host "111.111.111.111", port "3306"',
    PARTITION db2 comment 'host "222.222.222.222", port "3306"'
 );
Query OK, 0 rows affected (0.03 sec)

mysql> 
mysql> select * from member;
+----+-------------+
| id | name        |
+----+-------------+
|  1 | memorycraft |
|  3 | jiro        |
|  5 | shiro       |
|  2 | ichiro      |
|  4 | sub-LOW     |
+----+-------------+
5 rows in set (0.00 sec)

一方、TRUNCATE TABLEはデータの除去クエリなので、データノードのデータは削除されます。
mysql> truncate table member;Query OK, 0 rows affected (0.01 sec)

mysql> select * from member;
Empty set (0.00 sec)

再度INSERTをしなおして、各データノードを見てみます。

SpiderテーブルでINSERT
mysql> INSERT INTO member (name) VALUES('memorycraft'),('ichiro'),('jiro'),('sub-LOW'),('shiro');
Query OK, 5 rows affected (0.01 sec)
Records: 5 Duplicates: 0 Warnings: 0

db1でSELECT
mysql> select * from member;
+----+-------------+
| id | name        |
+----+-------------+
|  1 | memorycraft |
|  3 | jiro        |
|  5 | shiro       |
+----+-------------+
3 rows in set (0.00 sec)

db2でSELECT
mysql> select * from member;
+----+---------+
| id | name    |
+----+---------+
|  2 | ichiro  |
|  4 | sub-LOW |
+----+---------+
2 rows in set (0.00 sec)
このようにきれいに分散されて保存されていることがわかります。

ここで、データノードのidカラムにそれぞれauto_incrementが設定されているにもかかわらずidが重複しないのは、
Spiderのテーブル設定のauto_increment_modeパラメータ(CONNECTION文字列で設定できるパラメータ)の動作に基づきます。
auto_increment_modeの動作として、
  • 0:通常モード。(リモートサーバにロック付き問い合わせで取得した最新付番を利用して、付番を行う。) 遅い。テーブルパーティショニングを利用しており、auto incrementカラムが indexの第一カラムである場合は、簡易モードで動作する。
  • 1:簡易モード。(Spiderテーブル内のカウントで付番を行う。) 速いが、更新は1テーブルからのみに限定しないと値の重複が発生する。
  • 2:割愛
  • 3:割愛
デフォルトは0
となっており、今回の場合1の簡易モードが有効になり、Spider側で自動採番しているためです。
この様に、複数の分散されたDBをまったく1つのDBとほぼ同じように扱えるため、読込みだけでなく書込みにも負荷分散でき、非常に有用なプロダクトだといえます。

疲れた、、、、今回はここまで。