2013年6月15日土曜日

Spiderってなんじゃ?(SUBPARTITIONで負荷分散しながらストレージ容量を無限追加)

Spiderシリーズです。Spiderではパーティションを使ってシャーディングしますが、パーティションの定義によって分散の特性が変わります。

1. サロゲートキーなどのHASHパーティション
  • データは均等に分散
  • 更新負荷は均等に分散
  • 増設はデータ全体の再ハッシュが必要

2. 日付の年月などのHASHパーティション
  • データは順次格納
  • 更新負荷は常に1データノードにのみかかる
  • 増設は新しい年月を追加

3. サロゲートキーなどのRANGEパーティション
  • データは順次格納
  • 更新負荷は常に1データノードにのみかかる
  • 増設は新しいキー範囲を追加

理想は均等に負荷分散しながら、データ容量を後から増やせるのがベストですが、1,2,3とも負荷分散と容量追加のどちらかにしか親和性がありません。
そこで、作者の斯波さん(@kentokushiba)に伺ったところ、 SpiderでもSUBPARTITIONが使えるとのことでRANGEとHASHを組み合わせることで実現できると教えて頂きました。



早速やってみます。
CREATE TABLE `spdoc` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created_at` datetime DEFAULT NULL,
  `content` text,
  `docname` varchar(255),
  INDEX(created_at) ,
  PRIMARY KEY (`id`)
) ENGINE=SPIDER DEFAULT CHARSET=utf8 CONNECTION=' table "spdoc", user "xxxxxxxx", password "yyyyyyyyy" auto_increment_mode "1" '
PARTITION BY RANGE (id)
SUBPARTITION BY HASH(id) (
  PARTITION p1 VALUES LESS THAN (1000000) (
    SUBPARTITION db1 COMMENT = 'host "10.0.1.141", port "3306"' ENGINE = SPIDER,
    SUBPARTITION db2 COMMENT = 'host "10.0.1.142", port "3306"' ENGINE = SPIDER
  ),
  PARTITION p2 VALUES LESS THAN (2000000) (
    SUBPARTITION db3 COMMENT = 'host "10.0.1.143", port "3306"' ENGINE = SPIDER,
    SUBPARTITION db4 COMMENT = 'host "10.0.1.144", port "3306"' ENGINE = SPIDER
  )
);


まずRANGEパーティションをidの値の範囲で定義します。試しに1000000,2000000に設定します。
次に、SUBPARTITIONでidでHASHパーティションを定義し、ここにデーターノードを指定します。

すると、idが1000000以下のデータについてdb1, db2のデータノードに均等に分散され、
1000000〜2000000の範囲では、db3, db4に均等に分散されます。

idが2000000を超えそうになった場合は、後から3つめのパーティションを追加することができます。



ALTER TABLE spdoc ADD PARTITION
(
  PARTITION p3 VALUES LESS THAN (3000000) (
    SUBPARTITION db5 COMMENT = 'host "10.0.1.145", port "3306"' ENGINE = SPIDER,
    SUBPARTITION db6 COMMENT = 'host "10.0.1.146", port "3306"' ENGINE = SPIDER
  )
);

これでデータ容量はほぼ無限に増やすことができます。

パーティションごとにSUBPARTITIONの数を変えることはできないので、負荷の分散数は同じデータノード数で賄わなければいけませんが、その場合はパーティションごとのデータノードのインスタンスタイプやPIOPSを増やすことで処理能力をアップさせることで対応できるかと思います。

斯波さんありがとうございました!

以上です。

2013年5月28日火曜日

Fluentdってなんじゃ?(設定ファイルをリモートから読み込む)

fluentdのドキュメントに以下のような記載がありました。

Configuration File - (3) Include Directive
# absolute path
include /path/to/config.conf

# if using a relative path, the directive will use 
# the dirname of this config file to expand the path
include extra.conf

# glob match pattern
include config.d/*.conf

# http
include http://example.com/fluent.conf

最後の行、、、HTTPから設定ファイルを取得してインクルードできるみたいです。

ということで試してみました。

common.conf
<source>
  type tail
  format apache
  pos_file /var/log/td-agent/httpd-access.log.pos
  path /var/log/httpd/access_log
  tag apache.access
</source>

<match apache.access>
  type s3

  aws_key_id xxxxxxxxxxxxxxxxxxx 
  aws_sec_key yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
  s3_bucket hoge-bucket
  path logs/
  buffer_path /var/log/fluent/s3
  flush_interval 10s 
</match>

これをS3にアップロードしてWEBホスティングしてみます。


そして、EC2では以下のように設定します。

/etc/td-agent/td-agent.conf
include http://s3-ap-northeast-1.amazonaws.com/hoge-bucket/conf/fluentd/common.conf
# /etc/init.d/td-agent start


httpdのコンテンツにアクセスしてしばらくたつと、、、


無事出力されていました!

これの何がいいかというと、

  • 各サーバーに共通の設定ファイルなどを外部化しておき、一括変更をかけられる (再起動は必要ですが、定期的に再起動をかけるようにしておくという手もあります)
  • いっそ全設定を外出しして完全に外部で管理する

などが可能になります。

以上です。

2013年5月25日土曜日

spiderってなんじゃ?(spider自身の冗長化)

いままではSpiderがひとつに対して、データノードが複数ある構成をとってきましたが、実戦ではSpiderが単一障害点になるため好ましくありません。ここではSpider自身の冗長化を行なってみます。

いままでのSpider x 1 + mroonga x 2 の構成に対してデータを入れ込むためのappノードを用意します。

SpoderのSPOF





mroonga

 CREATE TABLE doc (
  id INT PRIMARY KEY AUTO_INCREMENT,
  created_at datetime,
  content text,
  FULLTEXT INDEX (content)
 ) ENGINE = mroonga; 


spider

 CREATE TABLE doc (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  created_at datetime,
  content text,
  FULLTEXT INDEX (content)
 ) ENGINE = spider CHARSET utf8
 CONNECTION ' table "doc", user "memorycraft_user", password "memorycraft_pass" '
 PARTITION BY KEY() (
  PARTITION db1 comment 'host "10.0.1.136", port "3306"',
  PARTITION db2 comment 'host "10.0.1.137", port "3306"'
 ); 

app

# yum install -y php php-mbstring php-pdo php-mysql
$ vim links.php
<?php

//ランダム文字列
function getRandomString($size = 8){
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    mt_srand();
    $char = "";
    for($i = 0; $i < $size; $i++) {
        $char .= $chars{mt_rand(0, strlen($chars) - 1)};
    }
    return $char;
}

//XMLテンプレート
$tmpl =<<< END
<?xml version="1.0" encoding="UTF-8" ?>
<data>
  <info>
    <datetime>%s</datetime>
    <address>%s</address>
    <content>%s</content>
  </info>
</data>
END;

//XMLテンプレートに流しこむcontent要素の内容を読み込む
$contents = array();
for($i=0;$i<10;$i++){
  $contents[] = file_get_contents(dirname(__FILE__)."/dummy".$i.".txt");
}

//spiderホストは引数から取得する
$host = $argv[1];
//タイムゾーン
date_default_timezone_set('Asia/Tokyo');

//無限ループ
while(true){
  //XMLテンプレートに各要素を割り当てる
  $xml = sprintf($tmpl, date('Y/m/d H:i:s'), getRandomString(), $contents[rand(0,9)]);
  echo ".";
  //DB接続
  $pdo = new PDO(
    "mysql:dbname=memorycraft;host=$host;",
    "memorycraft_user",
    "memorycraft_pass",
    array(
     PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET `utf8`"
   ));
  $pdo->query("SET NAMES utf8;");
  //投入
  $st = $pdo->prepare("INSERT INTO doc VALUES(NULL,NOW(),?);");
  $rslt = $st->execute(array($xml));
}
?>
実行します
$ php links.php 10.0.1.20
.......................................


Spiderノードで投入されていることを確認します。

mysql> select id, created_at from doc limit 10;
+------+---------------------+
| id   | created_at          |
+------+---------------------+
|    1 | 2013-05-25 11:27:47 | |    2 | 2013-05-25 11:27:47 | |    3 | 2013-05-25 11:27:47 | |    4 | 2013-05-25 11:27:47 | |    5 | 2013-05-25 11:27:47 | |    6 | 2013-05-25 11:27:48 | |    7 | 2013-05-25 11:27:48 | |    8 | 2013-05-25 11:27:48 | |    9 | 2013-05-25 11:27:48 | |   10 | 2013-05-25 11:27:48 | +------+---------------------+ 10 rows in set (0.06 sec)


これで、spider x 1 + mroonga x 2 + app x 1になりました。


Spiderの冗長化


次は、Spider x 2 + mroonga x 2 + internal ELB + app x 4 にしてみます。




Spiderノードをもう1台(spider2)追加します。

テーブルはspider1と同じ内容にします。
/etc/my.cnfでauto_increment_increment = 100でoffsetを1,2でずらします。
これについては、別の記事で触れています。

mysqlってなんじゃ?(マルチマスタでauto_increment)
http://memocra.blogspot.jp/2013/05/mysqlautoincrement.html

また、spiderを複数にした場合、appからの接続は1つにまとめます。
Proxy系のソフトウェアを入れるのも1つの方法ですが、ここでは内部ELBを利用してみます。

ヘルスチェックは3306ではなく別のポート経由でMySQL監視プロセスに接続スべきですが、ここではとりいそぎhttpdを立ちあげ80番に対してヘルスチェックします。


それではappで投入プログラムを実行します。
対象のホストは内部ELBのDNSNameになります。

$ php links.php internal-spider-2040122974.ap-northeast-1.elb.amazonaws.com
........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................


spider1でデータを見てみます。

mysql> select id, created_at from doc limit 20;
+------+---------------------+
| id   | created_at          |
+------+---------------------+
|    1 | 2013-05-25 11:27:47 |
|  101 | 2013-05-25 11:27:47 |
|  201 | 2013-05-25 11:27:47 |
|  202 | 2013-05-25 11:27:47 |
|  601 | 2013-05-25 11:27:47 |
|  701 | 2013-05-25 11:27:48 |
| 1101 | 2013-05-25 11:27:48 |
| 1201 | 2013-05-25 11:27:48 |
| 1601 | 2013-05-25 11:27:48 |
| 1701 | 2013-05-25 11:27:48 |
| 2101 | 2013-05-25 11:27:49 |
| 2201 | 2013-05-25 11:27:49 |
| 2301 | 2013-05-25 11:27:49 |
| 2601 | 2013-05-25 11:27:49 |
| 2701 | 2013-05-25 11:27:49 |
| 2802 | 2013-05-25 11:27:49 |
| 3101 | 2013-05-25 11:27:49 |
| 3201 | 2013-05-25 11:27:49 |
| 3301 | 2013-05-25 11:27:49 |
| 3601 | 2013-05-25 11:27:50 |
+------+---------------------+

mysql> select count(*) from doc;
+----------+
| count(*) |
+----------+
|   111433 |
+----------+


順調に入っていってるみたいです。
あとはSpiderノードを必要に応じて追加していき、my.cnfでoffsetをずらして起動するだけです。
offsetずらしの処理はcloud-initなどで行うと、AutoScalingすることもできそうです。

今回は以上です。




mysqlってなんじゃ?(マルチマスタでauto_increment)

MySQLでマルチマスタやシャーディングなどを行った場合、auto_incrementを使いたい場合があります。

たとえばspiderだとすると、複数台のSpiderノードを経由してデータが投入されると、データノードでauto_increment値が競合してしまうことがあるため、auto_incrementのオフセットをspiderノードごとにずらします。

spider1
/etc/my.cnf
------
[mysqld]
auto_increment_increment = 100
auto_increment_offset = 1


spider2
/etc/my.cnf
------
[mysqld]
auto_increment_increment = 100
auto_increment_offset = 2


auto_increment_incrementはカウントアップされる単位です。上の例では100ずつ増えていきます。
auto_increment_offsetはauto_incrementのスタート時点の値です。

これによって、各spiderノードのauto_increment値は以下のように増えていきます。

spider1
1
101
201
301
401
.....
123401

spider2
2
102
202
302
402
....
123402

つまり3桁目以上は全体で同じ値をカウントアップしていきますが、下2桁は重複しなくなります。

これを元に投入されたデータはspider上で抽出すると以下のようになります。

1
2
101
102
201
202
301
302
401
402
.....
123401
123402

随分値を飛ばしているように感じますが、ビットずらしのような感覚でとらえれば違和感はなくなると思います。そして、この場合は、Spiderノードを99台まで増やしても値が重複しません。
将来的に何台まで増えるかわからない場合はこのようにするとよさそうです。

以上です。

2013年5月22日水曜日

jqってなんじゃ?(containsで簡単APIテスト)

jqはsedのjson版のようなコマンドラインツールで、jsonデータをフィルタしたり切り取ったり整形したりが簡単にできます。

aws界隈ではaws cliがjsonを返すことで話題になったようで、以下の記事などで紹介されています。
suz-lab : EC2の価格のJSONを"jq"でいじってみた

jqの1.2では、containsという関数が追加されたようで、jsonに特定の要素が含まれているかどうかの真偽値を返すようです。

これをつかって簡単なAPIテストが出来るんじゃないかと思い、試してみました。

こんな感じのJSONがあったとします。
$ curl -s http://aws.amazon.com/jp/ec2/pricing/pricing-on-demand-instances.json | jq '.config.regions[].region'
"us-east"
"us-west-2"
"us-west"
"eu-ireland"
"apac-sin"
"apac-tokyo"
"apac-syd"
"sa-east-1"

containsで指定(config.regions[].region)の要素にap-northeast-1があるかどうかを検査します。
$ curl -s http://aws.amazon.com/jp/ec2/pricing/pricing-on-demand-instances.json | jq 'contains({config: {regions: [{region: "ap-northeast-1"}]}})'

false

無いようです。

ではapac-tokyoがあるかどうかを検査します。
$ curl -s http://aws.amazon.com/jp/ec2/pricing/pricing-on-demand-instances.json | jq 'contains({config: {regions: [{region: "apac-tokyo"}]}})'

true

ありました!


わざわざ導入に手間がかかるツールを使うまでもない簡単な検査であれば、ワンライナーで終わるコマンドラインをシェルにいくつか書いてまとめて実行するというのも手段としてありかもしれません。


以上です。