2011年8月31日水曜日

EC2でMySQL(Spider編3 Spiderの先にSpider)

EC2はあまり関係なくなってきていますが、Spiderの話題はまだ続きます。
Spiderの特性や構成を考えていくと、Spiderで分割するためのパーティショニングにより、さまざまな分割方法を考えることができます。
また、データが増加していくと、分散した先でもさらに負荷軽減が必要になる可能性も考えられます。

Spider編1で少し触れましたが、Spiderはストレージエンジンであり、リンク先のテーブルのストレージエンジンに制限はありません。
ここで、Spiderのリンク先にSpiderテーブルを指定したらどうなるか実験してみました。

構成は以下のとおりです。
ここでは全てTokyoリージョン内とします。
またIPは仮のものです。



  • SpiderノードA(111.111.0.1)
  • データノードA-1(111.111.0.2)
  • SpiderノードB(111.111.0.3)
  • データノードB-1(111.111.0.4)
  • データノードB-2(111.111.0.5)

SpiderノードBはSpiderノードAのデータノードとしての振る舞い、また一方でデータノードB-1,B-2のSpiderノードとしても振る舞います。
インストールやデータベースの作成、セキュリティグループの設定など基本的な部分は、前回、前々回と同じため省きます。


データノードの設定

SpiderノードA、SpiderノードB以外の全てのデータノードでmemberテーブルを作成します。
# mysql -u root
mysql> use cloudpack
Database changed
mysql> create table member(
id int(11) auto_increment,
name varchar(256),
primary key(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8; 

データノードA-1、SpiderノードBではSpiderノードAを許可します。
mysql> GRANT ALL PRIVILEGES ON *.* TO 'remote_user'@"spider-a" IDENTIFIED BY 'remote_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'remote_user'@"111.111.0.1" IDENTIFIED BY 'remote_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'remote_user'@"ec2-111-111-0-1.ap-northeast-1.compute.amazonaws.com" IDENTIFIED BY 'remote_pass'; 

データノードB-1、B-2ではSpiderノードBを許可します。
mysql> GRANT ALL PRIVILEGES ON *.* TO 'remote_user'@"spider-b" IDENTIFIED BY 'remote_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'remote_user'@"111.111.0.3" IDENTIFIED BY 'remote_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'remote_user'@"ec2-111-111-0-3.ap-northeast-1.compute.amazonaws.com" IDENTIFIED BY 'remote_pass';

SpiderノードAの設定

まず最終的に全てを束ねるSpiderノードAを設定します。
データノードA-1とSpiderノードBにリンクを張りますが、
ここでのパーティショニングはRANGEパーティションを採用します。
idの1~5がデータノードA-1、6~10がSpiderノードBに流れるように設定しました。
mysql> create table member(
id int(11) auto_increment,
name varchar(256),
primary key(id)
) engine = Spider DEFAULT CHARSET=utf8
CONNECTION ' table "member", user "remote_user", password "remote_pass" '
PARTITION BY RANGE (id) (
    PARTITION data-a1 VALUES LESS THAN (6) comment 'host "111.111.0.2", port "3306"',
    PARTITION spider-b VALUES LESS THAN (11) comment 'host "111.111.0.3", port "3306"'
);

SpiderノードBの設定

次にSpiderノードBを設定します。
データノードB-1,B-2を束ねますが、
ここではKEYパーティションで分散させます。
mysql> create table member(
id int(11) auto_increment,
name varchar(256),
primary key(id)
) engine = Spider DEFAULT CHARSET=utf8
CONNECTION ' table "member", user "remote_user", password "remote_pass" '
PARTITION BY KEY() (
    PARTITION data-b1 comment 'host "111.111.0.4", port "3306"',
    PARTITION data-b2 comment 'host "111.111.0.5", port "3306"'
);
これで設定は終了です。


確認

それではSpiderノードAで以下のように、10行追加してみます。
# mysql -u root
mysql> use cloudpack
Database changed
mysql> INSERT INTO member (name) VALUES ('ichiro'),('jiro'),('sub-Low'),('shiro'),('goro'),('rokuro'),('shichro'),('hachiro'),('kuro'),('juro');
Query OK, 10 rows affected (0.03 sec)
Records: 10  Duplicates: 0  Warnings: 0

SELECTすると正常に10件投入されていることが確認できます。
mysql> select * from member order by id;
+----+---------+
| id | name    |
+----+---------+
|  1 | ichiro  |
|  2 | jiro    |
|  3 | sub-Low |
|  4 | shiro   |
|  5 | goro    |
|  6 | rokuro  |
|  7 | shichro |
|  8 | hachiro |
|  9 | kuro    |
| 10 | juro    |
+----+---------+
10 rows in set (0.00 sec)

それでは、各ノードにどのように分配されているか見てみましょう。

●データノードA-1
ここにはRANGEパーティションのルールどおり、idの1~5が投入されています。
mysql> select * from member order by id;
+----+---------+
| id | name    |
+----+---------+
|  1 | ichiro  |
|  2 | jiro    |
|  3 | sub-Low |
|  4 | shiro   |
|  5 | goro    |
+----+---------+
5 rows in set (0.00 sec)

●SpiderノードB
こちらもRANGEパーティションのルールどおり、idの6~10が投入されています。
mysql> select * from member order by id;
+----+---------+
| id | name    |
+----+---------+
|  6 | rokuro  |
|  7 | shichro |
|  8 | hachiro |
|  9 | kuro    |
| 10 | juro    |
+----+---------+
5 rows in set (0.01 sec)

しかし、これは別のSpiderノードでもあり、データノードB-1,B-2にさらに分散されています。
さらに、その分散先を見てみます。

●データノードB-1
mysql> select * from member order by id;
+----+---------+
| id | name    |
+----+---------+
|  7 | shichro |
|  9 | kuro    |
+----+---------+
2 rows in set (0.00 sec)

●データノードB-2
mysql> select * from member order by id;
+----+---------+
| id | name    |
+----+---------+
|  6 | rokuro  |
|  8 | hachiro |
| 10 | juro    |
+----+---------+
3 rows in set (0.00 sec)

SpiderノードBの先で、さらにKEYパーティショニングによって、idのハッシュ値で均等に分散されていることがわかります。

このように、途中で負荷が増えても、さらに分散させて1ノードあたりの負荷を減らすことも可能です。

以上です。

EC2でMySQL(Spider編2 リージョン間SpiderでSSL接続)

少し間があいてしまいましたが、Spiderの続きです。
前回はSpiderを使用して、書き込みの分散をおこないました。

今回は、Spiderによる書き込み分散をリージョン間で試してみます。
構成は以下のとおりです。(IPは仮のものです)

  • Spiderノード(Tokyoリージョン、123.123.123.123)
  • データノード1(Tokyoリージョン、111.111.111.111)
  • データノード2(EUリージョン、222.222.222.222)

また、リージョン間の接続はインターネット越しになるため、SSLで接続するのが安全です。
インストールやデータベースの作成、セキュリティグループの設定など基本的な部分は前回と同じなので割愛します。ちなみにこのサンプルで使用している証明書類は自前の証明書です。

データノードの設定

2つのデータノードで以下の設定をします。
前回と違うのは、データノードはSpiderのSSL接続先になるためのサーバー証明書の配置です。
# mkdir -p /tmp/ssl
# cd /tmp/ssl
# openssl genrsa -out ca-key.pem 2048
# openssl req -new -x509 -nodes -days 3650 -key ca-key.pem -out ca-cert.pem
# openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-req.pem -days 3650
# openssl rsa -in server-key.pem -out server-key.pem
# openssl x509 -req -in server-req.pem -CA ca-cert.pem -CAkey ca-key.pem -out server-cert.pem -set_serial 2 -days 3650
# chown mysql:mysql *

my.cnfで証明書の場所を追記します。
# vi /etc/my.cnf
ssl-ca=/tmp/ssl/ca-cert.pem
ssl-cert=/tmp/ssl/server-cert.pem
ssl-key=/tmp/ssl/server-key.pem

SSL接続が有効になっていることを確認します。
# mysql -u root
mysql> show variables like '%ssl%';
+---------------+--------------------------+
| Variable_name | Value                    |
+---------------+--------------------------+
| have_openssl  | YES                      |
| have_ssl      | YES                      |
| ssl_ca        | /tmp/ssl/ca-cert.pem     |
| ssl_capath    |                          |
| ssl_cert      | /tmp/ssl/server-cert.pem |
| ssl_cipher    |                          |
| ssl_key       | /tmp/ssl/server-key.pem  |
+---------------+--------------------------+

spiderノードのアクセスを許可します。
もしSSL以外受け付けない場合はGRANT文の最後に REQUIRE SSLをつけます。
mysql> GRANT ALL PRIVILEGES ON *.* TO 'remote_user'@"ja-spider" IDENTIFIED BY 'remote_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'remote_user'@"123.123.123.123" IDENTIFIED BY 'remote_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'remote_user'@"ec2-123-123-123-123.ap-northeast-1.compute.amazonaws.com" IDENTIFIED BY 'remote_pass';

memberテーブルを作成します。
mysql> use cloudpack
Database changed
mysql> create table member(
id int(11) auto_increment,
name varchar(256),
primary key(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.01 sec)


Spiderノードの設定

続いてSpiderノードでの設定を行います。
前回との違いは、SpiderテーブルのSSLオプションと、クライアント証明書の配置です。
証明書を作成します。
# mkdir -p /tmp/ssl
# cd /tmp/ssl
# openssl genrsa -out ca-key.pem 2048
# openssl req -new -x509 -nodes -days 3650 -key ca-key.pem -out ca-cert.pem
# openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-req.pem -days 3650
# openssl rsa -in client-key.pem -out client-key.pem
# openssl x509 -req -in client-req.pem -CA ca-cert.pem -CAkey ca-key.pem -out client-cert.pem -set_serial 2 -days 3650
# chown mysql:mysql *

次にSpiderテーブルを作成します。
まず最初に、前回と同じSSLオプションなしで作成してみます。
# mysql -u root
mysql> use cloudpack
Database changed
mysql> create table member(
id int(11) auto_increment,
name varchar(256),
primary key(id)
) engine = Spider DEFAULT CHARSET=utf8
CONNECTION ' table "member", user "remote_user", password "remote_pass" '
PARTITION BY KEY() (
    PARTITION ap_northeast_1 comment 'host "111.111.111.111", port "3306"',
    PARTITION eu_west_1 comment 'host "222.222.222.222", port "3306"'
);
Query OK, 0 rows affected (0.04 sec)

ここで、別コンソールでSpiderノードにtcpflowをインストールし、
3306ポートの通信を見てみましょう。

コンソール2
# yum install tcpflow -y
# tcpflow -c port 3306

コンソール1でこのようにINSERTして見ます。
mysql> INSERT INTO member (name) VALUES('memorycraft'),('ichiro'),('jiro');

するとコンソール2では、以下のように平文で通信されていることがわかります。
046.137.176.190.03306-010.146.027.110.49671: ...........
010.146.027.110.49671-046.137.176.190.03306: .....SET NAMES utf8
046.137.176.190.03306-010.146.027.110.49669: ...........
010.146.027.110.49669-046.137.176.190.03306: 1....show table status from `cloudpack` like 'member'
046.137.176.190.03306-010.146.027.110.49671: ...........
010.146.027.110.49671-046.137.176.190.03306: @....insert into `cloudpack`.`member`(`id`,`name`)values(2,'ichiro')
046.137.176.190.03306-010.146.027.110.49669: .....*....def..TABLES..Name
TABLE_NAME.!...........(....def..TABLES..Engine.ENGINE.!...........*....def..TABLES..Version.VERSION.?...... ....0....def..TABLES.
Row_format
ROW_FORMAT.!...........*....def..TABLES..Rows
TABLE_ROWS.?...... ....8....def..TABLES..Avg_row_length.AVG_ROW_LENGTH.?...... ....2....def..TABLES..Data_length.DATA_LENGTH.?...... ....:....def..TABLES..Max_data_length.MAX_DATA_LENGTH.?...... ....4..
.def..TABLES..Index_length.INDEX_LENGTH.?...... .........def..TABLES..Data_free.DATA_FREE.?...... ....8....def..TABLES..def..TABLES..Create_time.CREATE_TIME.?...........2....def..TABLES..Update_time.UPDATE_TIME.?...........0....def..TABLES.
Check_time
CHECK_TIME.?...........4....def..TABLES..Collation.TABLE_COLLATION.!.`.........,....def..TABLES..Checksum.CHECKSUM.?...TABLE_COMMENT.!..................".Z....member.InnoDB.10.Compact.0.0.16384.0.0.4194304.1.2011-08-30 22:59:16...utf8_general_ci..........".
046.137.176.190.03306-010.146.027.110.49671: ...........
010.146.027.110.54750-046.051.243.054.03306: .....commit
046.051.243.054.03306-010.146.027.110.54750: ...........
010.146.027.110.49671-046.137.176.190.03306: .....commit
046.137.176.190.03306-010.146.027.110.49671: ...........
010.146.027.110.49671-046.137.176.190.03306: .....

ここで、コンソール1にて、SpiderノードのmemberテーブルをSSL仕様に作り直してみます。
SpiderテーブルにはMySQLのSSLオプションと同じ項目があるので、それを利用します。
以下のようにConnectionの部分に、ssl_ca,ssl_cert,ssl_keyの項目を追加し、それぞれに先ほど作った証明書類のパスを記載します。
mysql> DROP TABLE member;
Query OK, 0 rows affected (0.00 sec)

mysql> create table member(
id int(11) auto_increment,
name varchar(256),
primary key(id)
) engine = Spider DEFAULT CHARSET=utf8
Connection ' table "member", user "remote_user", password "remote_pass", ssl_ca "/tmp/ssl/ca-cert.pem", ssl_cert "/tmp/ssl/client-cert.pem", ssl_key "/tmp/ssl/client-key.pem" '
PARTITION BY KEY() (
    PARTITION ap_northeast_1 comment 'host "111.111.111.111", port "3306"',
    PARTITION eu_west_1 comment 'host "222.222.222.222", port "3306"'
);
Query OK, 0 rows affected (0.04 sec)

mysql> select * from member;
+----+-------------+
| id | name        |
+----+-------------+
|  1 | memorycraft |
|  3 | jiro        |
|  2 | ichiro      |
+----+-------------+
3 rows in set (3.26 sec)

ここで、コンソールを見ると、今度は暗号化されているのがわかります。
046.137.176.190.03306-010.146.027.110.51913: .... .{..5.n.?.....~...q....K..!..h18.... %C..m...3./;.K...|g..... C<XxM..
046.137.176.190.03306-010.146.027.110.51913: .... Z.?.a...qU.mex ...\...K$...
........ #5.c.AAC.l.
'.....Q3..W+.DA[Z.6.
046.137.176.190.03306-010.146.027.110.51913: .... .
......>...XQ.e;./x..!...l7........ :/........9.=........H..vAi....F
046.137.176.190.03306-010.146.027.110.51913: .... .....j..!\qr.,B-Y.e....p...]t=...... ....O....Y........Y_.+.n.9.L.TvB
046.137.176.190.03306-010.146.027.110.51913: .... ..3..'.sw..9..H....bYs.F.A....(..... /.. .Y....Yi...A..h.C....,k.w|..
010.146.027.110.51913-046.137.176.190.03306: ....p.
.)...}E8...p......o-.I.J..@%>........EW.E<.n.S..e3G.T.b....7........a......^.
046.137.176.190.03306-010.146.027.110.51913: .... -.,.eh.\g....Z0.7..PW..a..k)..........j@........._...o.^v.Qn./.G..".vf..\d.\..A..0,.0../.F..b..........+.J+l=.<....U'.3$U..RHi[V...y....N5.G.n..".?.q1.i}..A...1=~O..............vN'.>Xgj-....&W_...Y]..oL....J
010.146.027.110.49139-046.051.243.054.03306: ....0.....FOa^..K3!`.......y....9..k+.Y.e..lk.nV.g...
.t3...X....&054.03306-010.146.027.110.49139: .... .M..{.X.$Yb.\.q...^....PWy..M.S..... j..zGu..<>h.V}.#...
010.146.027.110.51913-046.137.176.190.03306: ....0$}..q.....E.......w.t..5.KW...r. eo...Ak...W..2.
046.137.176.190.03306-010.146.027.110.51913: .... .o..z...fn\S..

これで、リージョン間でも安心してSpiderが利用できます。
以上です。

2011年8月26日金曜日

FTPでシンボリックリンクが表示されないってなんじゃ?

今日はいつものAWSを少し離れて、一般的なサーバーの話題を。
ftpのサービスを立ち上げて、複数のFTPユーザーに対して同じリソースにアクセスさせたい場合があります。
しかしftpサーバーソフトウェアによっては、シンボリックリンクが表示することができません。

そんなときのTipsを。


ftpサーバーをインストールして起動します。
# yum install proftpd -y
# vi /etc/proftpd.conf
--------追記↓---------
MasqueradeAddress このサーバーのグローバルIP
PassivePorts 60000 61000
--------追記↑--------
# /etc/init.d/proftpd start


そしてユーザーを作成します。
# useradd hoge
# passwd hoge


リンクを張りたいディレクトリを作成します。
# mkdir -p /var/www/html/hoge
# touch /var/www/html/hoge/hello
# chown hoge:hoge -R /var/www/html/hoge


シンボリックリンクを張ります。
# su - hoge
$ ln -s /var/www/html/hoge www


ftp接続しても、シンボリックリンクは見れません。



このような場合、以下のようにシステム内でマウントすることでアクセスできます。

$ mkdir ~/www
$ su -
# mount --bind /var/www/html/hoge www

ftp接続すると、フォルダが表示され、クリックするとリソースが表示されていることがわかります。



以上、ちょっとしたTIPSでした。

2011年8月23日火曜日

Amazon ElastiCacheってなんじゃ?

Amazon ElastiCacheというあたらしいサービスがbeta公開されました。
これはいわゆるAmazonが提供するmemcacheサーバーで、サーバークラスタとして動作し、EC2のネットワーク上からアクセスが可能です。

今回は、簡単にElastiCacheを試してみます。

まず、ElastiCacheのページにアクセスし、右ペインにある"Sign Up Now"のボタンからElastiCacheの利用登録をします。




すると、AWSのコンソールで"ElastiCache"のタブが追加されるので、選択します。
左ペインを見ると、このサービスがまだUS-Eastでしか提供されていないことがわかります。
また、メニューツリーに、いくつか機能がならんでいるのがわかります。



まず、その中から"Cache Security Groups"を選択して、"Create Cache Security Groups"ボタンを押して、"Cache Security Group Name"を入力して登録します。
ここでは
  • Cache Security Group Name:myfirst-cache-sg
としました。



Cache Security GroupはEC2ネットワーク内からのキャッシュクラスタに対するアクセス許可をコントロールします。EC2のセキュリティグループはEC2ネットワーク外部からのアクセスに対して権限をあたえますが、ElastiCacheのキャッシュセキュリティグループは指定されたEC2のセキュリティグループに対してアクセスの権限を与えます。

新規に作成された"myfirst-cache-sg"を選択すると、下部ペインにそのキャッシュセキュリティグループ の情報が表示され、EC2のセキュリティグループに対してアクセス許可を与えることができます。


"EC2 Security Group to Link"に、EC2セキュリティグループ名、AWS Account Idには自分のアカウントIDをハイフンなしの12桁で入力します。
ここでは、あらかじめ同じus-eastで作成したEC2インスタンスが属するmyfirst-use-sgというセキュリティグループがあるので、以下のように入力しました。(AWS Account Idは伏せてあります。)
  • EC2 Security Group to Link:myfirst-use-sg
  • AWS Account Id:xxxxxxxxxxxx



次に左ペインで"Cache Cluster"を選択して、"Launch Cache Cluster"ボタンを押すと以下のような設定ウィンドウが表示されるので、ここにキャッシュクラスタの設定を入力していきます。
ここでは、Nameにクラスタ名、Node Typeにノードのキャッシュのメモリサイズ、Number of Nodesにクラスタのノード数を入力、preferrd Zoneで設置するゾーンを選択します。
ここでは、
  • Name :myfirst-cache
  • Node Type:cache.m1.small(1.3GB memory)
  • Number of Nodes:2
  • Preferred Zone:us-east-1a
で設定しました。


"Continue"ボタンを押すと、追加設定画面が表示されます。
ここで、先ほど作成したキャッシュセキュリティグループを選択します。また、"Cache Parameter Group"ではキャッシュエンジンの設定値をカスタムする場合、クラスタ上のすべてのキャッシュノードに対して共通で適用することができます。"Maintenance Window"は、もしAmazonがElastiCacheに対してバージョンアップやバッチなどの適用を行う場合に、そのタイミングを指定することができます。ここでたとえば火曜の夜中ならメンテナンスを行ってもよいという場合は、そのように設定できます。

ここでは、 キャッシュセキュリティグループの指定のみで、他の2項目はデフォルトのままで設定しました。


"Continue"を押し、確認画面で登録ボタンを押して完了すると、このようにクラスタが登録されているのがわかります。


また、クラスタ名をクリックすると、クラスタの詳細とノードの一覧を確認することが出来ます。



nodeタブのノード一覧では、各ノードのホスト名とポート番号が表示されており、ElastiCacheを使用するときはこのエンドポイントに対してアクセスすることになります。
また、それぞれのノードを選択すると、下部にそのノードのCloudWatchの各メトリクスへのリンク集が表示されます。


これで、ElastiCacheが立ち上がり、使用することが出来るようになりました。

それでは、実際にプログラムからアクセスしてみます。
us-east-1aにあるEC2インスタンスにログインします。
ここではキャッシュアクセスするプログラムはRubyを利用することにしますが、ElastiCacheはAPI経由ではなく、memcacheプロトコルでアクセスするため、いつものようにAWS Access Keyなどの認証は必要ありません。そのかわりとしてElastiCacheのCache Securithi GroupがEC2のSecurity Groupに対してアクセス許可をしているようです。

rubyはインストールしてあることが前提で、さらにmemcacheクライアントライブラリをインストールします。ここではmemcachedを利用しますが、別のクライアントライブラリでもかまいません。

# gem install memcached
Building native extensions.  This could take a while...
Successfully installed memcached-1.3
1 gem installed
Installing ri documentation for memcached-1.3...
Installing RDoc documentation for memcached-1.3...

ここでは簡易的にirbを使用してアクセスします。
ここで指定するホスト名は、ノード一覧に記載されていたendpointとportを指定します。
# irb
irb(main):001:0> require 'memcached'
=> true
irb(main):002:0> $cache = Memcached.new(["myfirst-cahce.umk9bw.0001.use1.cache.amazonaws.com:11211", "myfirst-cahce.umk9bw.0002.use1.cache.amazonaws.com:11211"])
=> (長いので割愛)
irb(main):003:0> value = 'hello'
=> "hello"
irb(main):004:0> $cache.set 'test-key', 'hello'                  
=> nil
irb(main):005:0> $cache.get 'test-key'    
=> "hello"
irb(main):006:0> 

ちゃんとsetとgetができていることが確認できました。
これで、memcacheのためだけにインスタンスやAMIを用意する必要がなくなるかもしれませんね。

今日はここまで。

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とほぼ同じように扱えるため、読込みだけでなく書込みにも負荷分散でき、非常に有用なプロダクトだといえます。

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

2011年8月16日火曜日

EC2でMySQL(リージョン間レプリケーション編)

前回に引き続き、MySQLの話です。
今回は、リージョン間でMySQLのレプリケーションを行います。
まずマスター用(Asia Pacific Tokyo)とスレーブ用(EU-West Ireland)、それぞれ1インスタンスずつ用意します。

ここでは、例として、
  • マスターのIP:mmm.mmm.mmm.mmm
  • スレーブのIP:sss.sss.sss.sss
  • データベース:clouadpack
  • レプリケーション用のユーザー:repl_user
  • レプリケーション用のパスワード:repl_pass
とします。
ここでは各IPはElasticIPでパブリックなIPを適用しておきます。

まず、マスター、スレーブ、それぞれでデータベースを作成します。
$ mysql -u root
mysql> create database cloudpack;
Query OK, 1 row affected (0.00 sec) 

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| cloudpack          |
| mysql              |
| performance_schema |
| test               |
+--------------------+

次にマスターサーバーで、スレーブサーバーからのアクセスを許可し、バイナリログを生成するようにします。
$ myql -u root 
mysql> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl_user@"sss.sss.sss.sss" IDENTIFIED BY 'repl_pass';
mysql> flush privileges;
# vi /etc/my.cnf
...
log-bin = /var/db/repl/binary-log
...
server-id = 1


スレーブサーバーでは、マスターサーバーの情報を登録します。
# vi /etc/my.cnf
...
server-id = 2
$ mysql -u root
mysql> CHANGE MASTER TO
  MASTER_HOST='mmm.mmm.mmm.mmm',
  MASTER_USER='repl_user',
  MASTER_PASSWORD='repl_pass',
  MASTER_PORT=3306;

それぞれのMySQLを起動するEC2のセキュリティグループに3306(MySQLポート。それ以外を使用する場合はそのポート番号)を互いのIPで許可します。


そして、マスターサーバー、スレーブサーバーの順で再起動します。
# /etc/init.d/mysqld restart 

実際に、レプリケーションが動いているか確認します。

マスター
$ mysql -u root cloudpack; 
mysql>
CREATE TABLE `hoge` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 
;
mysql> 
mysql> 
mysql> insert into hoge (name) values('moge');
Query OK, 1 row affected (0.02 sec)

スレーブ
$ mysql -u root cloudpack;  
mysql> show tables;
+---------------------+
| Tables_in_cloudpack |
+---------------------+
| hoge                |
+---------------------+
1 rows in set (0.00 sec)
mysql> 
mysql> 
mysql> select * from hoge;
+----+------+
| id | name |
+----+------+
|  1 | moge |
+----+------+
1 row in set (0.00 sec)

スレーブにも反映されていることが確認できました。
リージョン間の接続はオーバーインターネットになるため、SSL接続を行いたい場合は、suz-labのエントリーが参考になります。

今日は夏ばてにつき、ここまで。

2011年8月12日金曜日

EC2でMySQL(簡単インストール編)

EC2上でMySQLについて書いてみます。

今回は普通のMySQLと同じですが、まずは基本編ということで、さくっと進めたいと思います。さくっとなので今回はlinuxバイナリを使用してみます。

まずはファイルをダウンロードして展開します。
# 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 .


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にアクセスしてみます。
# mysql -u root

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.5.14-log Source distribution

Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 


動いていることを確認できました。

簡単ですが、今日はここまで。

2011年8月10日水曜日

S3ってなんじゃ?(バージョン管理編)

今回は、S3のバージョン管理機能について触れます。S3ではデフォルトではバージョン機能は無効になっていますが、バケットごとにバージョン管理を有効にすることができます。
AWS SDK for Rubyを利用して、その動作を簡単に見てみましょう。

あらかじめ認証情報設定用のファイルを用意しておきます。
$ vi config.rb
require 'rubygems'
require 'aws'

AWS.config({
:access_key_id => 'アクセスキー',
:secret_access_key => 'シークレットキー'
})

指定されたバケットのファイルとバージョンの全状態を確認するスクリプトを作ります。
$ vi check_version.rb
# 設定情報を読み込む
require File.expand_path(File.dirname(__FILE__) + '/config')

# 引数からバケット名を読み込む
bucket_name = ARGV[0]
unless bucket_name
  puts "Usage: check_version.rb <BUCKET_NAME>"
  exit 1
end

# S3インスタンスの取得
s3 = AWS::S3.new
# バケットオブジェクトの取得
bucket = s3.buckets[bucket_name]
# バケットのバージョンが有効かどうかを出力
puts "state :" + bucket.versioning_state.to_s
# バケットの全バージョン情報を取得
versions = bucket.versions
# 全てのバージョン情報を出力
versions.each(){|obj_ver|
  puts "key=" + obj_ver.object.key + "; version_id=" + obj_ver.version_id + ";"
}

これを実行して全バージョンの情報を確認してみます。
$ ruby check_version.rb hoge-bucket

state :unversioned
key=_1308236999981.png; version_id=null;
key=welcome.txt; version_id=null;

ここで、stateがunversionedとなっているのはバケットがデフォルトでバージョニング無効になっているためです。元々アップされていた_1308236999981.pngとwelcome.txtの2つのファイルのversion_idもnullになっています。

次に、このバケットをバージョニング有効にするためのスクリプトを書きます。
$ vi enable_version.rb
# 設定情報を読み込む
require File.expand_path(File.dirname(__FILE__) + '/config')

# 引数からバケット名を読み込む
bucket_name = ARGV[0]
unless bucket_name
  puts "Usage: check_version.rb <BUCKET_NAME>"
  exit 1
end

# S3インスタンスの取得
s3 = AWS::S3.new
# バージョニングの有効化
s3.buckets[bucket_name].enable_versioning

これを実行して、再度バージョニング状態を確認します。
$ ruby enable_version.rb hoge-bucket
$ ruby check_version.rb hoge-bucket

state :enabled
key=_1308236999981.png; version_id=null;
key=welcome.txt; version_id=null;

state: enabledとなっており、バージョニングが有効になっていることが確認できます。

次に、ファイルをアップロードするスクリプトを作ります。
$ vi upload.rb
# 設定情報を読み込む
require File.expand_path(File.dirname(__FILE__) + '/config')

# 引数からバケット名、ファイル名を読み込む
(bucket_name, file_name) = ARGV
unless bucket_name && file_name
  puts "Usage: upload.rb <BUCKET_NAME> <FILE_NAME>"
  exit 1
end

# S3インスタンスの取得
s3 = AWS::S3.new

# バケットがなければ作成する
b = s3.buckets[bucket_name]
unless b.exists?
  b = s3.buckets.create(bucket_name)
end

# アップロード
basename = File.basename(file_name)
o = b.objects[basename]
o.write(:file => file_name)


このアップロードスクリプトを使用して、バージョニング有効になった状態のバケットに、連続して違う内容のwelcome.txtをアップロードしてみます。
$ echo "hoge" > welcome.txt
$ ruby upload.rb hoge-bucket welcome.txt
$ echo "moge" > welcome.txt
$ ruby upload.rb hoge-bucket welcome.txt

ここで、再度バージョン情報を確認します。
$ ruby check_version.rb hoge-bucket

state : enabled
key=_1308236999981.png; version_id=null;
key=welcome.txt; version_id=1ut2HAksA7WCEkbdlDD3rnBC_OxP8Lvf;
key=welcome.txt; version_id=VpCOAn8Et9ZbOQyk_bKVJGeYaYp2x2W_;
key=welcome.txt; version_id=null;

このように、version_idがついたwelcome.txtが2つ増えていることが確認できます。
ここで、バージョンを指定してファイルを読み込むスクリプトを作成します。
$ vi read_by_version.rb
# 設定情報を読み込む
require File.expand_path(File.dirname(__FILE__) + '/config')

# 引数からバケット名、ファイル名、バージョンIDを読み込む
(bucket_name, file_name, version_id) = ARGV
unless bucket_name && file_name && version_id
  puts "Usage: version_check.rb <BUCKET_NAME>"
  exit 1
end

# S3インスタンスの作成
s3 = AWS::S3.new

# バージョンを指定して読み込んだものを出力
bucket = s3.buckets[bucket_name]
puts bucket.objects[file_name].read({:version_id=>version_id}).to_s

これを先ほどの出力で確認したバージョンIDを指定して個別に実行すると、以下のようにそれぞれのバージョンのファイルの内容を確認することができます。
$ ruby read_by_version.rb hoge-bucket welcome.txt 1ut2HAksA7WCEkbdlDD3rnBC_OxP8Lvf
> moge
$ ruby read_by_version.rb hoge-bucket welcome.txt VpCOAn8Et9ZbOQyk_bKVJGeYaYp2x2W_
> hoge  

このように、S3を使用してバージョン管理ができることを確認できました。
ちなみに、このバージョン機能は、ファイル単位でのバージョン機能のようです。

本日はここまで。

2011年8月7日日曜日

S3ってなんじゃ?(RUBY SDK鈍行編3 PaperclipでRailsからS3へファイルアップロード)

今回はRuby on RailsからS3へファイルをアップロードしてみたいと思います。
RailsにはPaperclipというファイルアップロードプラグインがあり、Marcel Molina Jr.さん謹製のAWS::S3というgemを使用したS3へのアップロードもサポートしているのですが、今回はせっかくAmazonの公式ruby sdkが公開されたので、そちらを利用したPaperclip拡張である@igor-alexandrovさんのpaperclip-awsを使用してS3ファイルアップロードを試してみます。

PaperclipはImageMagickを使用するので、まずImageMagickをインストールします。
yumからだとバージョンが古い場合があるので、ソースからインストールします。
# cd /usr/local/src
# wget ftp://ftp.imagemagick.org/pub/ImageMagick/ImageMagick.tar.gz
# tar xzvf ImageMagick.tar.gz
# cd ImageMagick-6.7.1-2
# ./configure
# make
# make install

次にldconfigでライブラリの更新をシステムに知らせます。
# ldconfig /usr/local/lib

Gemfileに以下のgemをインストールします。
  • RMagick(ImageMagickのRubyラッパー)
  • Paperclip(ファイルアップロードプラグイン)
  • paperclip-aws(paperclipでのS3アップロード機能拡張)
$ vi Gemfile
gem 'rmagick'
gem "paperclip", "~> 2.3"
gem "papepclip-aws"

gemのインストールと更新を行います。
$ bundle install vendor/bundle
$ bundle update

ImageMagickへのパスを通します。
$ vi config/environments/development.rb 
Paperclip.options[:command_path] = "/usr/local/bin/"

railsでScaffoldを作成してみます。
今回は簡易ブログを作成する想定で、blogというモデルのscaffoldを作成しました。
$ rails g scaffold blog title:string description:text

モデルにファイルアップロードの項目を追加します。
paperclipのhas_attacched_fileメソッドの引数では、デフォルトのpaperclipの項目以外にpaperclip-awsで追加されたs3用の項目も指定できます。
項目についてはここで解説されています。
ここではphotoという名前で追加してみます。このブログの鈍行編1の設定でaws.ymlにアクセスキーとシークレットキーしか設定していなかったので、今回その他の設定は直接指定していますが、これらもaws.ymlに含めてしまうこともできます。以下赤字が追加した部分です。
$ vi app/models/blog.rb
class Blog < ActiveRecord::Base

  def self.s3_config
    @@s3_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/aws.yml")).result)[Rails.env]
  end

  has_attached_file :photo,
                    :styles => {
                      :thumb => [">75x"],
                      :medium => [">600x"]
                    },
                    :storage => :aws,
                    :s3_credentials => {
                      :access_key_id => self.s3_config['access_key_id'],
                      :secret_access_key => self.s3_config['secret_access_key'],
                      :endpoint => 's3-ap-northeast-1.amazonaws.com'
                    },
                    :s3_bucket => 'myfirst-bucket',
                    :s3_host_alias => self.s3_config['s3_host_alias'],
                    :s3_acl => :public_read,
                    :s3_protocol => 'http',
                    :path => "images/:id/:style/:data_file_name"

end

続いて、viewに画像ファイルの項目を追加します。
viewは通常のpaperclipのviewの書き方とまったく同じです。
まずは、入力画面にphoto属性のファイル選択フィールドを追加します。
$ vi app/views/blogs/_form.html.erb
<%= form_for(@blog, :html => { :multipart => true } ) do |f| %>
  <% if @blog.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@blog.errors.count, "error") %> prohibited this blog from being saved:</h2>

      <ul>
      <% @blog.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :description %><br />
    <%= f.text_area :description %>
  </div>

  <div class="field">
    <%= f.label :photo %><br />
    <%= f.file_field :photo %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

詳細画面や完了画面で写真が確認できるようにを表示するようにphotoのimgタグを追加します。
$ vi app/views/blogs/show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <b>Title:</b>
  <%= @blog.title %>
</p>

<p>
  <b>Description:</b>
  <%= @blog.description %>
</p>


<%= image_tag @blog.photo.url(:medium) %>

<%= link_to 'Edit', edit_blog_path(@blog) %> |
<%= link_to 'Back', blogs_path %>

一覧画面にサムネイルを表示するようにサムネイル用のimgタグを追加します。
$ vi app/views/blogs/index.html.erb
<h1>Listing blogs</h1>

<table>
  <tr>
    <th>Title</th>
    <th>Description</th>
    <th>Photo</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @blogs.each do |blog| %>
  <tr>
    <td><%= blog.title %></td>
    <td><%= blog.description %></td>
    <td><%= image_tag blog.photo.url(:thumb) %></td>
    <td><%= link_to 'Show', blog %></td>
    <td><%= link_to 'Edit', edit_blog_path(blog) %></td>
    <td><%= link_to 'Destroy', blog, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New Blog', new_blog_path %>


次に、paperclip用の項目をDBに追加するためのマイグレーションファイルを作成します。
$ rails g migration add_photo_columns_to_blog
      invoke  active_record
      create    db/migrate/20110805111437_add_photo_columns_to_blog.rb
$ vi db/migrate/20110805111437_add_photo_columns_to_blog.rb 
class AddPhotoColumnsToBlog < ActiveRecord::Migration

  def self.up
    add_column :blogs, :photo_file_name,    :string
    add_column :blogs, :photo_content_type, :string
    add_column :blogs, :photo_file_size,    :integer
    add_column :blogs, :photo_updated_at,   :datetime
  end

  def self.down
    remove_column :blogs, :photo_file_name
    remove_column :blogs, :photo_content_type
    remove_column :blogs, :photo_file_size
    remove_column :blogs, :photo_updated_at
  end

end

マイグレーションを行います。
$ rake db:migrate
(in /var/www/html/myfirstcloud)
==  CreateBlogs: migrating ====================================================
-- create_table(:blogs)
   -> 0.0011s
==  CreateBlogs: migrated (0.0014s) ===========================================

==  AddPhotoColumnsToBlog: migrating ==========================================
-- add_column(:blogs, :photo_file_name, :string)
   -> 0.0007s
-- add_column(:blogs, :photo_content_type, :string)
   -> 0.0004s
-- add_column(:blogs, :photo_file_size, :integer)
   -> 0.0005s
-- add_column(:blogs, :photo_updated_at, :datetime)
   -> 0.0004s
==  AddPhotoColumnsToBlog: migrated (0.0028s) =================================

これで、S3にアップロードすることができるようになりました。

ブラウザで確認してみます。
http://IPアドレス/blogs
にアクセスすると、以下のようにscaffoldで作成された一覧画面が表示されます。



New Blogのリンクをクリックして、新規登録画面を表示すると、
画像のアップロードのフィールドがあることが確認できます。
テキストを入力し、画像を選択したらCreate Blogボタンをクリックします。



すると、登録完了の画面として、アップロードした写真が表示されます。



また、一覧画面に戻ると、一覧にもサムネイルが表示されているのがわかります。



表示されている画像のURLをみると以下のようにS3からホストされていることが確認できます。

http://s3-ap-northeast-1.amazonaws.com/myfirst-bucket/images/1/thumb/:data_file_name
簡単にS3ファイルアップロードを実装することができました。

本日はこれにて。

2011年8月3日水曜日

Amazon SimpleDBってなんじゃ?(RUBY SDK鈍行編2 railsでsimpledbを使う)

前回とタイトルが微妙につながってませんが、ruby on railsのインストールが終わったので、続いてawsのsdkをつかったrailsアプリケーションを作ってみます。

aws-sdkでは、完全ではないですがrailsのサポートもしており、SimpleDBをrailsのORMとして利用することができます。方法は以下のとおりです。

まず、必要なライブラリを全てインストールします。
# yum -y install ruby-sqlite3
# yum -y install sqlite
# yum -y install sqlite-devel
# yum -y install ncurses-devel
# yum -y install readline-devel
readlineのコンパイルとインストールを行います。
$ cd /usr/local/src/ruby-1.9.2-p290/ext/readline
$ ruby extconf.rb
$ make
# make install

新規にrailsアプリを作成します。(前回と同じものです)
# cd /var/www/html
# rails new myfirstcloud -J -T
# chown memorycraft:memorycraft -R myfirstcloud
$ cd /var/www/html/myfristcloud

bundlerでインストールするgemを定義します。今回sqlite3は使用しませんが、エラー回避のため便宜上入れて起きます。
$ vi Gemfile
...
gem 'aws-sdk'
#gem 'sqlite3'
gem 'sqlite3-ruby', '1.2.5', :require => 'sqlite3'
...

awsのアクセス認証用に証明書情報を定義します。rubyコードでの記述も可能ですが、ここではYAMLで定義します。記法はconfig/database.ymlとほぼ同じです。
$ vi config/aws.yml
development:
  access_key_id: アクセスキー
  secret_access_key: シークレットキー

test:
  <<: *development

production:
  <<: *development


SimpleDBはRDBMSではなくキーバリューストアなので、db:migrateは必要がなく、属性の定義はモデルで行います。aws_sdkのSimpleDBには仮想属性というものがあり、裏側で通常Railsでサポートしているデータ型とSimpleDBの属性が以下のようなメソッドで紐付けられています。
  • string_attr
    stringの属性
  • integer_attr
    integer(整数型)の属性
  • sortable_integer_attr
    値の範囲が決まっているソート可能なintegerの属性

    範囲を:rangeで指定します。
    validates_numericality_ofで最大値と最小値の制約を適用することが一般的で、
    もし範囲外だった場合、実行時エラーを引き起こします。

    通常のintegerとちがうところとして、SimpleDBは数値型をサポートしておらず全ての値は文字列に変換されます。
    そのため、数値でのソートに問題が生じます。たとえば2と10を昇順に並べると文字列比較のため10,2の順になってしまいます。
    このsortable_integerは、この問題に対応するため0詰めして保存されます。
  • float_attr
    float(浮動小数点型)の属性
  • sortable_float_attr
    値の範囲が決まっているソート可能なfloatの属性
    sortable_integerと同じくfloat値を0詰めで保持します。

    【正のfloat】
    たとえばsortable_float_attr :score, :range => (0..10)と指定した場合、
    辞書的にソートされても問題ないように5.5は"05.5"と文字列保存されます。

    【負のfloat】
    たとえば
    sortable_float_attr :position, :range => (-10..10)
      
    のように、範囲が負の値を含んでいいた場合、すべての値を正になるようにマイナス分だけずらして保存されます。
    つまり"000"から"020"として保存されることにより、ソートを可能にしています。

    注意としては、こういった方式で表示したり入力したりした値と、実際に保存される値がことなるので、
    一度運用を開始した後に値の範囲を変更したい場合、保存済みの全ての値を更新してあげる必要があるので要注意です。
  • boolean_attr
    boolean型の属性
    なにも指定しないでnewされたとき、デフォルトはfalseです。
  • datetime_attr
    日時型の属性
    created_atとupdated_atに対して指定された場合、自動管理されます。
  • timestamps
    timestamps
    datetime_attr属性のcreated_atとupdated_atを生成するための便利メソッドです。

ここでは以下のようにモデルを定義します。
$ vi app/models/my_first_domain.rb 
class MyFirstDomain < AWS::Record::Base
  string_attr :title
  integer_attr :num
  sortable_integer_attr :sort_num, :range => 0..99
  boolean_attr :bool_val
  timestamps

  validates_presence_of  :title, :num
end

ドメインがまだ存在していない場合は、rubyコンソールから作成することも可能です。
$ rails console
> MyFirstDomain.create_domain

モデルとドメインの作成が終わったら、それ以外の部分をscaffoldで作成します。
rails generate scaffold_controller MyFirstDomain title:string num:integer sort_num:sortable_integer bool_val:boolean

最後にroutesに追加します。
$ vi config/routes.rb
Myapp::Application.routes.draw do
    # 追加
    resources :my_first_domains
end

それでは確認してみましょう。

httpd.confの設定は前回のとおりであることを確認したら、
ブラウザで
http://IPアドレス/my_first_domains
を見てみましょう。
このとおり、my_first_domainのscaffoldのインデックス画面が確認できます。


それでは、アイテムを新規作成してみます。
ためしにSort Numを範囲外の1000で 登録しようとすると、下記のようにエラーが発生します。



これを防ぐため、モデルにvalidates_numericality_ofを追加します。
$ vi app/model/my_first_domain.rb
validates_numericality_of :sort_num, :greater_than_or_equal_to => 0, :less_than_or_equal_to => 99

再度登録しようとすると、今度はちゃんとバリデーションメッセージが表示されるようになりました。


範囲内の数値で登録すると、正常に登録が成功しました。


このとおり、railsからもさくっとSimpleDBを使うことができました。

つかれた。。今日はここまで。