2012年6月6日水曜日

JSXを使ってみたメモ


話題のjsxを使ってみたので、その際のメモを残します。


■JSXの売り
・高速(関数のインライン展開、定数の畳み込みなど最適化)
・静的型付け言語でチームでの大規模開発に向いている
・javascriptの技術者は容易に習得可能
・Javaっぽく書ける

特徴はここにまとまっています
http://ginpen.com/2012/05/31/jsx/

JSXはなぜ「速い」のか
http://d.hatena.ne.jp/kazuhooku/20120602/1338641072

チュートリアル
http://jsx.github.com/tutorial.html

ブラウザからコンパイラの動作を試せます
http://jsx.github.com/try/


■使ってみた感想
・javaっぽく、かなり綺麗に書ける
・クラス分割してモジュール開発といった手法がかなりとりやすい
・外部ライブラリや既存の資産のうち、javascriptの特性(prototype継承など)に依存しているものは移植しにくい
・javascriptの特性を柔軟に利用したいならcoffee-scriptの方が向いている

外部ライブラリ(jQuery)を組み込んだ例
http://d.hatena.ne.jp/nishiohirokazu/20120531/1338462986


■コンパイラの動き(わかったことだけ)
・デバッグモードとリリースモードがある。
・jsxとjsで関数名が変わる(引数の型や数を表す識別子がつく)
・実行時エラーが飛ぶような型チェックは、デバッグモードでのみ行われる。
・リリースモードの場合、パフォーマンスのために型チェックは行われない。(公式ドキュメントに記述あり)
※ Run-time type checks can be omitted by compiling the source code with the --release option


2012年5月26日土曜日

hbaseでトランザクションを利用する方法


hbaseのトランザクションについて補足
デフォルトではトランザクションをサポートしていないけど、
プラグインを入れることでトランザクションを使えるようになるとのこと。(RDBMSのそれとは若干異なる。)

方法を2つ見つけたので共有。
  1. contribのプラグインhbase-trx/hbase-transactional-tableindexedをhbaseに追加して起動する。@github
    • 楽観的平行性制御というトランザクション形式
    • →begin()時にロックするのではなく、割り込みがあった場合は全更新をロールバックして例外を返す。
  2. サードパーティーのライブラリhbase rowlog libraryを「クライアントサイドに」入れる。
    • ロックしないのでデッドロックを考えずに分散スキーマに向く。
    • 方法(あってるかな)
      1. キューにトランザクションを全てためる。
      2. 先頭クエリでこけた場合は処理を全て行わない。
      3. 2つ目以降のクエリでこけた場合は、バックグラウンドの別スレッドがキュー上の残クエリを実行する。(非同期)
参考:
HBase0.89におけるトランザクションについて -Hadoopユーザー会 | Google グループ

2012年5月13日日曜日

MongoDBのObjectIDから作成日時を取り出す

■ 概要
ObjectIDの構成は下記になっており、先頭4byteからcreated timeを取得できる。

0123456 7891011
timemachine pidinc


mongos> obj = new ObjectId()
ObjectId("4faf45d47f8e775a2d1a6948")
mongos> createdDt = new Date(parseInt(obj.toString().substr(0, 8), 16) * 1000)
ISODate("2012-05-13T05:25:40Z")

2012年5月12日土曜日

MongoDBのマイグレーション(Migration)中のcountの挙動について検証したときのメモ

# ----------------------------------
概要
# ----------------------------------
MongoDBのマイグレーション中に起きるcount()の問題を検証した。
↓↓問題については@doryokujinさんのslide share資料
Mongo sharding
↓↓こちらの記事も検証、図がわかりやすかったです。
MongoDBのShardingを試してみた。その2 Migration中の挙動について


シャードキーで検索、全件検索、snapshot()での検索、index()での検索、いずれの場合でもcount()結果がずれる。


# ----------------------------------
# mongo dbのバージョンは2系
# ----------------------------------
mongod --version
db version v2.0.4, pdfile version 4.5
Fri May 11 21:59:13 git version: 329f3c47fe8136c03392c8f0e548506cb21f8ebf

# ----------------------------------
# 構成
# ----------------------------------
macbook-air-13inch: {
  mongos : [27017],
  config : [11100],
  sharding: {
    replica_set1 : {
      shards: [10011, 10012],
    },
    replica_set2 : {
      shards: [10021, 10022],
    },
  }
}
# arbiterは省略

# 1,000万件のデータ投入
use ryooo
ins_humongous = function(){
  n = Math.random() * 100000000
  db.test_humongous.insert({number: parseInt(n)})
}
for (i = 0; i <= 10000000; i++) {
  ins_humongous()
}


# 最初は798969件だった
mongos> use ryooo
switched to db ryooo
mongos> db.test_humongous.find({ number : {$gt: 100,  $lt: 8000000}}).count();
798969

# numberにindexはついていない
mongos> db.test_humongous.stats()
{
 ・
 ・
 "totalIndexSize" : 324603552,
 "indexSizes" : {
  "_id_" : 324603552
 },
}


# ----------------------------------
# migration開始
# ----------------------------------
mongos> use admin
switched to db admin
mongos> db.runCommand({shardcollection : "ryooo.test_humongous", key: {_id : 1}});
{ "collectionsharded" : "ryooo.test_humongous", "ok" : 1 }


# migration中
mongos> use ryooo
switched to db ryooo
mongos> db.test_humongous.getShardDistribution()

Shard Shard1 at Shard1/localhost:10011,localhost:10012
 data : 311.77Mb docs : 8172914 chunks : 13
 estimated data per chunk : 23.98Mb
 estimated docs per chunk : 628685

Shard Shard2 at Shard2/localhost:10021,localhost:10022
 data : 75Mb docs : 1966082 chunks : 3
 estimated data per chunk : 25Mb
 estimated docs per chunk : 655360

Totals
 data : 386.77Mb docs : 10138996 chunks : 16
 Shard Shard1 contains 80.6% data, 80.6% docs in cluster, avg obj size on shard : 40b
 Shard Shard2 contains 19.39% data, 19.39% docs in cluster, avg obj size on shard : 40b


# migration後
mongos> db.test_humongous.getShardDistribution()

Shard Shard1 at Shard1/localhost:10011,localhost:10012
 data : 181.47Mb docs : 4757215 chunks : 8
 estimated data per chunk : 22.68Mb
 estimated docs per chunk : 594651

Shard Shard2 at Shard2/localhost:10021,localhost:10022
 data : 200Mb docs : 5242887 chunks : 8
 estimated data per chunk : 25Mb
 estimated docs per chunk : 655360

Totals
 data : 381.47Mb docs : 10000102 chunks : 16
 Shard Shard1 contains 47.57% data, 47.57% docs in cluster, avg obj size on shard : 40b
 Shard Shard2 contains 52.42% data, 52.42% docs in cluster, avg obj size on shard : 40b


# migration中の件数(約20秒ごと)(コマンドは一部省略)
# indexを張っていないnumberで検索
mongos> use ryooo
switched to db ryooo
mongos> b.test_humongous.find({ number : {$gt: 100,  $lt: 8000000} } ).count();
798969 ←開始時
798969
820015 ←ずれはじめ
828251
851267
・
(すべて正常値以上の値)
・
890386
840958
798969 ←終了時(開始時と同じ)




# ----------------------------------
# 次は、indexを張ってやればどうか。
# 初期化
use ryooo
db.test_humongous.drop()

# indexをはる
db.test_humongous.ensureIndex({number:1})

# 1,000万件のデータ投入
use ryooo
ins_humongous = function(){
  n = Math.random() * 100000000
  db.test_humongous.insert({number: parseInt(n)})
}
for (i = 0; i <= 10000000; i++) {
  ins_humongous()
}

# numberにindexがついている状態
mongos> db.test_humongous.stats()
{
 ・
 ・
 "indexSizes" : {
  "_id_" : 324464560,
  "number_1" : 356841520
 },
}

# ----------------------------------
# migration開始
# ----------------------------------
# 最初は799226件だった(コマンドは一部省略)
mongos> use ryooo
switched to db ryooo
mongos> db.test_humongous.find({ number : {$gt: 100,  $lt: 8000000}}).count();
799226 ←開始時
803179
809402
814710
・
(すべて正常値以上の値)
・
813520
802336
799226 ←終了時(開始時と同じ)



# 全件検索(コマンドは一部省略)
mongos> db.test_humongous.count()
10000001 ←開始時
10000001
10481154
10470561
10438490
・
(すべて正常値以上の値)
・
10215171
10067667
10000001 ←終了時(開始時と同じ)



# snapshot()しても結果は変わらず(コマンドは省略)
mongos> db.test_humongous.find().snapshot().count()
10013371 ←途中から気づいてとり始めた
10009113
10005426
・
(すべて正常値以上の値)
・
10222059
10092911
10000001 ←終了時(全件検索数と一致)



# シャードキーでの検索も同様にずれる。
# 1回目のmigrationでの計測では、初回の1回だけ異常値で後は正常値であった。
# 2回目のmigrationでの計測では、すべてのcountがずれた。
#   これはシャードキーでの検索ではglobalでなく、
#   対象のchunkが存在するシャードにのみ検索が走るため、
#   対象のchunkがいつmoveするかで結果が異なる。

# 1回目のmigration(コマンドは一部省略)
mongos> db.test_humongous.find({ _id : {$lt: ObjectId('4fad11b830e2c797dc8ec095')}}).count();
2966108 ←開始時
1810734
1810734
・
(すべて正常値)
・
1810734
1810734 ←終了時

# 2回目のmigration
mongos> db.test_humongous.find({ _id : {$lt: ObjectId('4fadb6a76d5f062fe7f155eb')}}).count();
6022486 ←開始時
6310732
6966093
7621454
6719822
5753037
6210336
5592768
5655372
5233997
5000011 ←終了時

やはりProduction環境ではauto-shardingを停止した方が良いな。

Macにsleepy.mongooseをインストールしたときのメモ

# ----------------------------------
■概要
# ----------------------------------
mongodb のRestインターフェース sleepy.mongooseをインストールしたときのメモ
このAPIをたたいてグラフ化したら、可視化の開発スピードが早くなるねー!

# ----------------------------------
■install
# ----------------------------------
easy_install pymongo
git clone https://github.com/kchodorow/sleepy.mongoose.git

cd sleepy.mongoose/
vi sleepymongoose/handlers.py
python httpd.py
Traceback (most recent call last):
  File "httpd.py", line 17, in 
    from handlers import MongoHandler
  File "/Users/matsumura/dev/mongoose/sleepy.mongoose/sleepymongoose/handlers.py", line 16, in 
    from pymongo.son import SON
ImportError: No module named son

# エラーでた
#from pymongo.son import SON
from bson.son import SON

python httpd.py

=================================
|      MongoDB REST Server      |
=================================

listening for connections on http://localhost:27080

# 起動した

# とれたー
curl http://localhost:27080/ryooo/userActivity/_find
{"ok": 1, "results": [{"_id": {"$oid": "4fabb349dfac802c09d4d6ab"}, "type": "login", "name": "ruffy", "at": {"$date": 1336233600000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6ac"}, "type": "login", "name": "zoro", "at": {"$date": 1336237200000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6ad"}, "type": "login", "name": "usopp", "at": {"$date": 1336240800000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6ae"}, "type": "login", "name": "nami", "at": {"$date": 1336244400000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6af"}, "type": "login", "name": "ruffy", "at": {"$date": 1336161600000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6b0"}, "type": "login", "name": "zoro", "at": {"$date": 1336165200000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6b1"}, "type": "login", "name": "nami", "at": {"$date": 1336168800000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6b2"}, "type": "login", "name": "ruffy", "at": {"$date": 1336086000000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6b3"}, "type": "login", "name": "usopp", "at": {"$date": 1336089600000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6b4"}, "type": "login", "name": "nami", "at": {"$date": 1336093200000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6b5"}, "type": "login", "name": "ruffy", "at": {"$date": 1336010400000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6b6"}, "type": "login", "name": "zoro", "at": {"$date": 1336014000000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6b7"}, "type": "login", "name": "ruffy", "at": {"$date": 1335931200000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6b8"}, "type": "login", "name": "zoro", "at": {"$date": 1335934800000}}, {"_id": {"$oid": "4fabb349dfac802c09d4d6b9"}, "type": "login", "name": "usopp", "at": {"$date": 1335938400000}}], "id": 0}

2012年5月5日土曜日

MongoDBのMapReduceを使ってみたときのメモ

■MapReduce の決まり
・map()でemit()した値の形式とreduce()の戻り値の形式が一致していること reduce関数は一度のMapReduce内で何度も呼ばれ、全てのreduceの戻り値をreduceした結果がMapReduceの戻り値となる。
→最終的な結果 = reduce(key, [values_3, reduce(key, [values_2, reduce(key, [values_1])])])

また、map()内で1件しかemit()されなかったkeyと値は、reduce()で一度も処理されない!!

すべてreduceした後の値を処理したい場合は、finalize functionを利用できる。

mongoの公式マニュアルが詳しい。
MapReduce - Docs-Japanese - 10gen Confluence

(mongoは集計できるので)mongoでは使いどころは限られると思うが、簡単にjavascriptでMapReduceできるなんてMongoDBやっぱいいね(・∀・)


■ MapReduceでやる意味(長所)
・集計対象のデータが巨大になっても負荷分散できる。
・oracleのマテビューのように結果を保存できる。(merge, replaceも可能)
・そもそもcassandraやhbaseでは集計関数やキー以外の条件検索ができないが、MapReduceなら集計できる。
そのため集計用のテーブルなどを別に作成せず、ユーザーのトランザクションデータを直接集計できる。


■ データの初期化
// データの初期化
db.userActivity.remove()

// 登録
db.userActivity.insert({name: 'ruffy', type: 'login', at: new Date('2012/05/06 1:00:00')});
db.userActivity.insert({name: 'zoro', type: 'login', at: new Date('2012/05/06 2:00:00')});
db.userActivity.insert({name: 'usopp', type: 'login', at: new Date('2012/05/06 3:00:00')});
db.userActivity.insert({name: 'nami', type: 'login', at: new Date('2012/05/06 4:00:00')});
db.userActivity.insert({name: 'ruffy', type: 'login', at: new Date('2012/05/05 5:00:00')});
db.userActivity.insert({name: 'zoro', type: 'login', at: new Date('2012/05/05 6:00:00')});
db.userActivity.insert({name: 'nami', type: 'login', at: new Date('2012/05/05 7:00:00')});
db.userActivity.insert({name: 'ruffy', type: 'login', at: new Date('2012/05/04 8:00:00')});
db.userActivity.insert({name: 'usopp', type: 'login', at: new Date('2012/05/04 9:00:00')});
db.userActivity.insert({name: 'nami', type: 'login', at: new Date('2012/05/04 10:00:00')});
db.userActivity.insert({name: 'ruffy', type: 'login', at: new Date('2012/05/03 11:00:00')});
db.userActivity.insert({name: 'zoro', type: 'login', at: new Date('2012/05/03 12:00:00')});
db.userActivity.insert({name: 'ruffy', type: 'login', at: new Date('2012/05/02 13:00:00')});
db.userActivity.insert({name: 'zoro', type: 'login', at: new Date('2012/05/02 14:00:00')});
db.userActivity.insert({name: 'usopp', type: 'login', at: new Date('2012/05/02 15:00:00')});
db.userActivity.insert({name: 'nami', type: 'login', at: new Date('2012/05/02 16:00:00')});
db.userActivity.insert({name: 'ruffy', type: 'login', at: new Date('2012/05/01 17:00:00')});
db.userActivity.insert({name: 'usopp', type: 'login', at: new Date('2012/05/01 18:00:00')});

db.userActivity.insert({name: 'ruffy', type: 'event_login', at: new Date('2012/05/06 19:00:00')});
db.userActivity.insert({name: 'nami', type: 'event_login', at: new Date('2012/05/06 20:00:00')});
db.userActivity.insert({name: 'zoro', type: 'event_login', at: new Date('2012/05/05 21:00:00')});
db.userActivity.insert({name: 'nami', type: 'event_login', at: new Date('2012/05/05 22:00:00')});
db.userActivity.insert({name: 'usopp', type: 'event_login', at: new Date('2012/05/04 23:00:00')});
db.userActivity.insert({name: 'nami', type: 'event_login', at: new Date('2012/05/04 1:00:00')});
db.userActivity.insert({name: 'ruffy', type: 'event_login', at: new Date('2012/05/03 2:00:00')});
db.userActivity.insert({name: 'zoro', type: 'event_login', at: new Date('2012/05/03 3:00:00')});
db.userActivity.insert({name: 'ruffy', type: 'event_login', at: new Date('2012/05/02 4:00:00')});
db.userActivity.insert({name: 'nami', type: 'event_login', at: new Date('2012/05/02 5:00:00')});
db.userActivity.insert({name: 'ruffy', type: 'event_login', at: new Date('2012/05/01 6:00:00')});


// 検索
db.userActivity.find({name: 'ruffy'}).sort({at:-1});
db.userActivity.find({name: 'zoro'}).sort({at:-1});


■ 誤った方法
MapReduceの決まりに沿っていないため、動きはするがデータ量が増えるとNG
決まり:map()でemit()した値の形式とreduce()の戻り値の形式が一致していること
// ------------------------------------------------
// ユーザー軸の集計
map = function () {
  // 'ruffy', {type: login, dt: 2012-05-05}
  emit(this.name, {'type': this.type, 'dt': this.at});
}
reduce = function(key, values) {
  var result = {login: [], event_login: []};
  values.forEach(function(value) {
    type = value.type
    dt = value.dt
    // javascriptの日付操作、なんとかならんかの。
    dt.setHours(0);dt.setMinutes(0);dt.setSeconds(0);dt.setMilliseconds(0);
    result[type].push(dt)
  });
  return result;
}
// 実行(inlineオプションは、結果を保存せず画面出力するオプション)
db.userActivity.mapReduce(map, reduce, {out: { inline : 1}});

// とはいえ、これはユーザーでorderすれば同じようなことができる
db.userActivity.find().sort({name: 1, at: -1, type:1})


// ------------------------------------------------
// 日付軸の集計
map = function () {
  dt = this.at;
  dt.setHours(0);dt.setMinutes(0);dt.setSeconds(0);dt.setMilliseconds(0);
  // 2012-05-05, {type: login, name: 'ruffy'}
  emit(dt, {'type': this.type, 'name': this.name});
}
reduce = function(key, values) {
  var result = {login: [], event_login: []};
  values.forEach(function(value) {
    type = value.type
    result[type].push(value.name)
  });
  return result;
}
// 実行(inlineオプションは、結果を保存せず画面出力するオプション)
db.userActivity.mapReduce(map, reduce, {out: { inline : 1}});

// sqlだとdate型のカラムがない状態で集計は面倒だけど楽にできちゃうヽ(・∀・)ノ

// 対象データをfilterして集計とかもできちゃう
db.userActivity.mapReduce(map, reduce, {query: {at: {$gte: new Date('2012/05/03')}}, out: {inline : 1}});


// ------------------------------------------------
// 活動軸の集計
map = function () {
  // login, {name: 'ruffy', dt: 2012-05-05}
  emit(this.type, {'name': this.name, 'dt': this.at});
}
reduce = function(key, values) {
  var result = {};
  values.forEach(function(value) {
    dt = value.dt
    dt.setHours(0);dt.setMinutes(0);dt.setSeconds(0);dt.setMilliseconds(0);
    if (typeof result[dt] == 'undefined') {
      result[dt] = 0
    }
    result[dt] += 1
  });
  return result;
}
// 実行(inlineオプションは、結果を保存せず画面出力するオプション)
db.userActivity.mapReduce(map, reduce, {out: { inline : 1}});

db.userActivity.mapReduce(map, reduce, {query: {type: 'login'}, out: { inline : 1}});


■ 正しい方法
// ------------------------------------------------
// ユーザー軸の集計
map = function () {
  // 'ruffy', {type: login, dt: 2012-05-05}
  dt = this.at
  dt.setHours(0);dt.setMinutes(0);dt.setSeconds(0);dt.setMilliseconds(0);
  value = {}
  value[this.type] = [dt]
  emit(this.name, value);
}
reduce = function(key, values) {
  var result = {};
  values.forEach(function(value) {
    for (type in value) {
      for (i in value[type]) {
        if (typeof result[type] == 'undefined') { result[type] = []; }
        dt = value[type][i]
        result[type].push(dt)
      }
    }
  });
  return result;
}
// 実行(inlineオプションは、結果を保存せず画面出力するオプション)
db.userActivity.mapReduce(map, reduce, {out: { inline : 1}});


// ------------------------------------------------
// 日付軸の集計
map = function () {
  dt = this.at
  dt.setHours(0);dt.setMinutes(0);dt.setSeconds(0);dt.setMilliseconds(0);
  value = {}
  value[this.type] = [this.name]
  emit(dt, value);
}
reduce = function(key, values) {
  var result = {};
  values.forEach(function(value) {
    for (type in value) {
      for (i in value[type]) {
        if (typeof result[type] == 'undefined') { result[type] = []; }
        dt = value[type][i]
        result[type].push(dt)
      }
    }
  });
  return result;
}
// 実行(inlineオプションは、結果を保存せず画面出力するオプション)
db.userActivity.mapReduce(map, reduce, {out: { inline : 1}});



// ------------------------------------------------
// 活動軸の集計
map = function () {
  dt = this.at
  dt.setHours(0);dt.setMinutes(0);dt.setSeconds(0);dt.setMilliseconds(0);
  value = {}
  value[dt] = 1
  emit(this.type, value);
}
reduce = function(key, values) {
  var result = {};
  values.forEach(function(value) {
    for (dt in value) {
      if (typeof result[dt] == 'undefined') { result[dt] = 0; }
      count = value[dt];
      result[dt] += parseInt(count)
    }
  });
  return result;
}
// 実行(inlineオプションは、結果を保存せず画面出力するオプション)
db.userActivity.mapReduce(map, reduce, {out: { inline : 1}});



■ 参考サイト MongoDBのMapReduceは、その他にも様々なオプションでいろいろできる!
MapReduce - Docs-Japanese - 10gen Confluence - MongoDB

Macにmongodbをインストールしたときのメモ

■ Homebrewで楽々インストール

brew install mongodb

mkdir -p ~/dev/mongo/data

# 起動
mongod --dbpath=~/dev/mongo/data

# クライアント起動
mongo


■ クライアントでいくつかのクエリを実行

db.UserReport.insert({
  name: 'ruffy',
  pt: 1000,
  crystal: {
    big: 0,
    small: 300
  },
});
db.UserReport.insert({
  name: 'zoro',
  pt: 2000,
  crystal: {
    big: 45,
    small: 600
  },
});
db.UserReport.insert({
  name: 'nami',
  pt: 3000,
  crystal: {
    big: 40,
    small: 400
  },
});
db.UserReport.insert({
  name: 'sanji',
  pt: 4000,
  crystal: {
    big: 20,
    small: 100
  },
});
db.UserReport.insert({
  name: 'usopp',
  pt: 5000,
  crystal: {
    big: 30,
    small: 0
  },
});


// 全件取得
db.UserReport.find()

// where pt = 1000
db.UserReport.find({pt: 1000})

// 多次元(組み込みobject)の値で検索
db.UserReport.find({"crystal.small": 100})
db.UserReport.find({"crystal.small": {$lt: 300}}).sort({pt: -1})

// 全件削除
db.UserReport.remove()
やっぱ運用の評判は悪くても、mongoがいいねヽ(・∀・)ノ

■クエリはこちらを参考に。
クエリー - Docs-Japanese - 10gen Confluence


■Mongoのベンチマークとってみた。

・gem
sudo gem install mongo bson bson_ext

・ソース
#!/usr/bin/env ruby
require 'rubygems'
require 'mongo'
require 'benchmark'

# ---------------------------------------
# Insert / Select 性能
# ---------------------------------------
count = 100000
p 'count:' + count.to_s
 
# ---------------------------------------
# mongodb
m = Mongo::Connection.new('localhost', 27017)
db = m.db('testdb')

row = {
  :user_id => 0,
  :name => 'name0',
  :created_at => Time.now.to_i
}
Benchmark.bm { |rep|
  rep.report("mongodb insert ") {
    for num in 1..count
      db = m.db('testdb')
      name = 'name' + num.to_s
      row[:user_id] = num
      row[:name] = name
      db['user'].insert(row)
    end
  }
}
Benchmark.bm { |rep|
  rep.report("mongodb select") {
    for num in 1..count
      id = rand(count).to_i + 1
      db['user'].find({:user_id => id})
    end
  }
}

・結果
mongod --version
db version v2.0.4, pdfile version 4.5

# MongoDB
"count:100000"
      user     system      total        real
mongodb insert  13.430000   1.030000  14.460000 ( 14.457051)
      user     system      total        real
mongodb select  4.680000   0.070000   4.750000 (  4.768458)

# 過去の結果
# MySQLはInnoDB
"count:100000"
      user     system      total        real
mysql(o/r) insert  74.320000   5.670000  79.990000 (110.039184)
      user     system      total        real
mysql(o/r) select 50.610000   2.650000  53.260000 ( 65.465168)
      user     system      total        real
mysql(sql) insert   4.430000   1.530000   5.960000 ( 29.590552)
      user     system      total        real
mysql(sql) select  5.470000   1.780000   7.250000 ( 13.550391)
      user     system      total        real
cassandra insert  47.180000   3.140000  50.320000 ( 61.347904)
      user     system      total        real
cassandra select  69.150000   3.470000  72.620000 (111.568860)
      user     system      total        real
memcache insert  14.930000   2.280000  17.210000 ( 17.935214)
      user     system      total        real
memcache select  16.710000   2.080000  18.790000 ( 19.264883)
      user     system      total        real
handlersocket insert   1.430000   1.510000   2.940000 ( 26.721995)
      user     system      total        real
handlersocket select   1.320000   1.520000   2.840000 (  7.867557)

# MySQLはSpider
"count:100000"
      user     system      total        real
mysql(o/r) insert 101.200000   7.280000 108.480000 (341.227877)
      user     system      total        real
mysql(o/r) select 71.910000   3.450000  75.360000 (294.436478)
      user     system      total        real
mysql(sql) insert   9.000000   2.600000  11.600000 (235.837247)
      user     system      total        real
mysql(sql) select  8.750000   2.700000  11.450000 (245.967628)

# 前提として、1台のmacで行っているため、スケールアウトのメリットを享受できていません。
# そのため、Cassandra やhbaseでは期待する効果が出ていません!!


■ その他のベンチマーク詳細
Macのsandboxを使ってspiderを動かしてみたメモ
MacのMySQLでHandlerSocketのベンチマークをとった
※すべてMac1台で行っているので、適切にスケールの効果が見られないものもあります。Spiderとかね。

Macにhbaseをinstallした時のメモ

■概要
hbaseインストールしたときのメモ

■インストール

brew install hbase
# Homebrewで楽しちゃいました

vi /usr/local/Cellar/hbase/0.92.0/libexec/conf/hbase-site.xml

  
    hbase.rootdir
    file:///usr/local/Cellar/hbase/databases/hbase-${user.name}/hbase
    The directory shared by region servers and into
    which HBase persists.  The URL should be 'fully-qualified'
    to include the filesystem scheme.  For example, to specify the
    HDFS directory '/hbase' where the HDFS instance's namenode is
    running at namenode.example.org on port 9000, set this value to:
    hdfs://namenode.example.org:9000/hbase.  By default HBase writes
    into /tmp.  Change this configuration else all data will be lost
    on machine restart.
    
  


# 起動
start-hbase.sh
# 停止
# stop-hbase.sh

# Rest I/F起動
hbase rest start

# ブラウザ確認
http://localhost:8080/
# → テーブルがないので白画面

# rubyでテーブル作成
gem install json
gem install hbase-stargate

require 'rubygems'
require 'stargate'

client = Stargate::Client.new("http://localhost:8080")
p client.list_tables

client.create_table('cats', 'profile', 'status')
p client.list_tables
# → create文なので、2回目からはコメントアウトすること

■パフォーマンスどれくらいか見てみよう
#!/usr/bin/ruby
# -*- encoding: utf-8 -*-
require 'rubygems'
require 'stargate'
require 'benchmark'

# 接続
client = Stargate::Client.new("http://localhost:8080")

# ---------------------------------------
# Insert / Select 性能
# ---------------------------------------
count = 100000
p 'count:' + count.to_s
 
# ---------------------------------------
# hbase
Benchmark.bm { |rep|
  rep.report("hbase insert ") {
    for num in 1..count
      name = 'name' + num.to_s
      # 登録
      client.create_row('cats', num.to_s, Time.now.to_i,
        [{:name => 'profile:name', :value => name}])
    end
  }
}
Benchmark.bm { |rep|
  rep.report("hbase select") {
    for num in 1..count
      id = rand(count).to_i + 1
      row = client.show_row('cats', id.to_s)
    end
  }
}

■結果
"count:100000"
      user     system      total        real
hbase insert  45.120000   5.880000  51.000000 (223.785493)
      user     system      total        real
hbase select 51.610000   6.440000  58.050000 (273.129300)

# 前提として、1台のmacで行っているため、スケールアウトのメリットを享受できていません。
# そのため、Cassandra やhbaseでは期待する効果が出ていません!!

■参考サイト
Getting Hbase and Thrift Running on a Mac
hbase-stargateを使ってHBaseのCRUD

2012年5月4日金曜日

Macのsandboxを使ってspiderを動かしてみたメモ

■概要
以前にmysql(sql), cassandra, memcache, mysql(handlersocket)でパフォーマンス計測を行った。
spiderでどれくらい変わるのか気になったので測ってみた。
MacのMySQLでHandlerSocketのベンチマークをとった

■注意
spiderは異なるサーバーにスケールすることでパフォーマンスを高める仕組みなので、
1台のMacにMySQL::sandboxでスケールしたところで遅くなることは承知の上です。

本当は、spider onlyとspider + handlersocketの比較がしたかったんだけど、
spider + handlersocketがエラーで比較できず。。。残念。


■準備
MySQL::sandboxに4node立ち上げて、spiderノードとする。
3306portのmysqlにspider masterとなるテーブルを作成する。

・spiderやsandboxのインストールはこちら
Macにmysql5.5.14とspiderとhandlersocketを入れる

■準備作業

# sandbox 4node立ち上げて、テーブルをインストール
./use_all 'use spider_test;
CREATE TABLE `user` (
    `user_id` INT(10) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    `birthday` DATE NULL DEFAULT NULL,
    `memo` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`user_id`)
);'


# 3306mysqlにspiderテーブルをインストール
use spider_test;
CREATE TABLE `user` (
  `user_id` INT(10) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NULL DEFAULT NULL,
  `birthday` DATE NULL DEFAULT NULL,
  `memo` VARCHAR(255) NULL DEFAULT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=SPIDER DEFAULT CHARSET=utf8
PARTITION BY HASH(user_id) (
  PARTITION p1 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13915", table "user",',
  PARTITION p2 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13916", table "user",',
  PARTITION p3 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13917", table "user",',
  PARTITION p4 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13918", table "user",'
);

# 3306mysqlのspiderテーブルからinsert
insert into user (name) values('name1');
insert into user (name) values('name2');
insert into user (name) values('name3');


# sandboxに分散されているか確認
./use_all 'use spider_test;
select * from user;
'
# server: 1: 
# server: 2: 
user_id name birthday memo
1 name1 NULL NULL
# server: 3: 
user_id name birthday memo
2 name2 NULL NULL
# server: 4: 
user_id name birthday memo
3 name3 NULL NULL



■ベンチマークをとるrubyソース

#!/usr/bin/ruby
# -*- encoding: utf-8 -*-
require 'rubygems'
require 'active_record'
require 'benchmark'
 
# ---------------------------------------
# init
# ---------------------------------------
# mysql :
#   mysql
#   use Test; truncate table user;
# memcacha :
#   ps aux | grep mem
#   kill -TERM xxxx
#   memcached -d
# cassandra 
#   cassandra-cli
#   use Test;
#   truncate Users;
 
# ---------------------------------------
# Insert / Select 性能
# ---------------------------------------
count = 10
p 'count:' + count.to_s
 
# ---------------------------------------
# mysql
ActiveRecord::Base.establish_connection(
  :adapter => 'mysql2',
  :host => 'localhost',
  :username => 'root',
  :password => '',
  :database => 'spider_test'
)
class User < ActiveRecord::Base
  self.table_name = 'user'
end
 
# active record 利用
Benchmark.bm { |rep|
  rep.report("mysql(o/r) insert ") {
    for num in 1..count
      name = 'name' + num.to_s
      User.create(:name => name)
    end
  }
}
Benchmark.bm { |rep|
  rep.report("mysql(o/r) select") {
    for num in 1..count
      id = rand(count).to_i + 1
      User.find(id)
    end
  }
}
# すべて削除
User.connection.execute('truncate table user')
 
# SQL利用
Benchmark.bm { |rep|
  rep.report("mysql(sql) insert ") {
    for num in 1..count
      name = 'name' + num.to_s
      User.connection.execute(sprintf('insert into user (user_id, name) values(%s, "%s");', num.to_s, name))
    end
  }
}
Benchmark.bm { |rep|
  rep.report("mysql(sql) select") {
    for num in 1..count
      id = rand(count).to_i + 1
      User.connection.execute(sprintf('select user_id, name from user where user_id = %s;', num.to_s))
    end
  }
}
# すべて削除
User.connection.execute('truncate table user')


■結果

# 結果
"count:100000"
      user     system      total        real
mysql(o/r) insert 101.200000   7.280000 108.480000 (341.227877)
      user     system      total        real
mysql(o/r) select 71.910000   3.450000  75.360000 (294.436478)
      user     system      total        real
mysql(sql) insert   9.000000   2.600000  11.600000 (235.837247)
      user     system      total        real
mysql(sql) select  8.750000   2.700000  11.450000 (245.967628)


# (参考)前回(spiderでなくinnodb)の結果
"count:100000"
      user     system      total        real
mysql(o/r) insert  74.320000   5.670000  79.990000 (110.039184)
      user     system      total        real
mysql(o/r) select 50.610000   2.650000  53.260000 ( 65.465168)
      user     system      total        real
mysql(sql) insert   4.430000   1.530000   5.960000 ( 29.590552)
      user     system      total        real
mysql(sql) select  5.470000   1.780000   7.250000 ( 13.550391)
      user     system      total        real
cassandra insert  47.180000   3.140000  50.320000 ( 61.347904)
      user     system      total        real
cassandra select  69.150000   3.470000  72.620000 (111.568860)
      user     system      total        real
memcache insert  14.930000   2.280000  17.210000 ( 17.935214)
      user     system      total        real
memcache select  16.710000   2.080000  18.790000 ( 19.264883)
      user     system      total        real
handlersocket insert   1.430000   1.510000   2.940000 ( 26.721995)
      user     system      total        real
handlersocket select   1.320000   1.520000   2.840000 (  7.867557)
やっぱ1台じゃ駄目だね。

2012年5月3日木曜日

Macにmysql5.5.14とspiderとhandlersocketを入れる

■モチベーション
spiderとhandlersocketでスケーラブルかつキャッシュいらずなmysql作れないかな。
# spider2.24からhandlersocketと一緒に使えるそうだ。
# 入れてみよう。
http://wild-growth-ja.blogspot.jp/2011/02/mysqlspidervpotherspider-224-vp-013.html

■結果概要
・インストールと、spider, handlersocketの単体の動作は問題なくできた。
・spider経由でMySQL::sandbox上のテーブルにhandlersocketで接続することができなかった。(調査中)
※ 本来はhandlersocketを使ってspiderのテーブルにアクセスできるはずだが。

■備考
mysqlはhomebrewで入れたかったけど、spiderを5.5系に入れる場合はソースから入れる方法しかなく、
なので、仕方なくmysqlとspiderを両方ソースから入れました。
mysqlとspiderとhandlersocketとq4mなども一緒にしたソースを下記から取得できますが、Macでのインストールではいろいろ苦戦。
https://launchpad.net/spiderformysql/spider-2.x/2.28-for-5.5.14


■前準備
# brewが入っていなければ
## /usr/bin/ruby -e "$(/usr/bin/curl -fksSL https://raw.github.com/mxcl/homebrew/master/Library/Contributions/install_homebrew.rb)"

# cmakeやwgetが入っていない人は
## brew install cmake
## brew install wget

■まずはmysqlとspiderをインストール

# ダウンロード
https://launchpad.net/spiderformysql/spider-2.x/2.28-for-5.5.14
mysql-5.5.14-spider-2.28-vp-0.17-hs-1.1-q4m-0.95.tgz

# macで綺麗に動いてくれないので、少し修正する。
http://bugs.mysql.com/bug.php?id=65050
http://lists.mysql.com/commits/140413

# q4mとhandlersocketもmacで綺麗にmakeしてくれないので、削除。(handlersocketは後で別で入れる)
mv storage/q4m ~/bk
mv plugin/handlersocket ~/bk

# configure
cmake -DCMAKE_INSTALL_PREFIX=/usr/local/mysql-5.5.14-spider \
        -DMYSQL_DATADIR=/var/lib/mysql/data \
        -DMYSQL_UNIX_ADDR=/var/lib/mysql/mysql.sock \
        -DDEFAULT_CHARSET=utf8 \
        -DWITH_EXTRA_CHARSETS=complex \
        -DDEFAULT_COLLATION=utf8_general_ci \
        -DWITH_PARTITION_STORAGE_ENGINE=1 \
        -DWITHOUT_ARCHIVE_STORAGE_ENGINE=1 \
        -DWITHOUT_BLACKHOLE_STORAGE_ENGINE=1 \
        -DWITHOUT_EXAMPLE_STORAGE_ENGINE=1 \
        -DWITHOUT_FEDERATED_STORAGE_ENGINE=1 \
        -DWITH_PIC=1 .

# makeするも失敗。
sudo make 
[ 93%] Building C object unittest/mysys/CMakeFiles/my_atomic-t.dir/my_atomic-t.c.o
/var/tmp//cco9mhO6.s:596:suffix or operands invalid for `add'
make[2]: *** [unittest/mysys/CMakeFiles/my_atomic-t.dir/my_atomic-t.c.o] Error 1
make[1]: *** [unittest/mysys/CMakeFiles/my_atomic-t.dir/all] Error 2
make: *** [all] Error 2

# これは解決策が見つからず、-iで無理やりインストール。。。
sudo make -i
sudo make install -i


# 起動スクリプトをコピー
cd /usr/local/mysql-5.5.14-spider
sudo cp support-files/mysql.server bin/

$ spider インストールスクリプトを取得
sudo wget http://launchpad.net/spiderformysql/spider-2.x/2.26-for-5.5.14/+download/spider-init-2.26-for-5.5.14.tgz
tar zxvf spider-init-2.26-for-5.5.14.tgz

# spiderをインストール
mysql -u root < install_spider.sql 
ERROR 1049 (42000) at line 20: Unknown database 'mysql'
# mysqlがない。

#rootでログインしてshow databasesしても、mysqlデータベースがなかった。
sudo ./scripts/mysql_install_db --datadir=/var/lib/mysql/data --user=mysql
# databaseを初期化

# 再度実行
mysql -u root < install_spider.sql 

ERROR 1436 (HY000) at line 211: Thread stack overrun:  13056 bytes used of a 131072 byte stack, and 128000 bytes needed.  Use 'mysqld --thread_stack=#' to specify a bigger stack.
# 次はスタック数のエラー

# 設定ファイル変更
sudo cp support-files/my-small.cnf /etc/my.cnf
vi /etc/my.cnf
thread_stack = 256K

# 再度実行
mysql -u root < install_spider.sql 
# 通った!

mysql> show engines;
+--------------------+---------+------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                    | Transactions | XA   | Savepoints |
+--------------------+---------+------------------------------------------------------------+--------------+------+------------+
| SPIDER             | YES     | Spider storage engine                                      | YES          | YES  | NO         |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                         | NO           | NO   | NO         |
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES          | YES  | YES        |
| MyISAM             | YES     | MyISAM storage engine                                      | NO           | NO   | NO         |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                      | NO           | NO   | NO         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables  | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                         | NO           | NO   | NO         |
+--------------------+---------+------------------------------------------------------------+--------------+------+------------+
7 rows in set (0.00 sec)


■続いてhandlersocket
https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL

git clone git://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL.git
cd HandlerSocket-Plugin-for-MySQL/

./autogen.sh
./configure --with-mysql-source=/usr/src/mysql-5.5.14-spider-2.28-vp-0.17-hs-1.1-q4m-0.95 --with-mysql-bindir=/usr/local/mysql-5.5.14-spider/bin/ --with-mysql-plugindir=/usr/local/mysql-5.5.14-spider/lib/plugin
make
make install

# 別コンソールでmysqlを起動し、
mysql> install plugin handlersocket soname 'handlersocket.so';
 
# プラグインが入ったかな。
ls /usr/local/Cellar/mysql/5.5.20/lib/plugin/
 
# handlersocketの設定を入れる。
sudo vi /etc/my.cnf 
[mysqld]セクションに追記
handlersocket_port = 9998
handlersocket_port_wr = 9999
handlersocket_address =
handlersocket_verbose = 0
handlersocket_timeout = 300
handlersocket_threads = 16
thread_concurrency = 128
open_files_limit = 65535

mysql> show plugins;
+-----------------------+--------+--------------------+------------------+---------+
| Name                  | Status | Type               | Library          | License |
+-----------------------+--------+--------------------+------------------+---------+
| binlog                | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| mysql_native_password | ACTIVE | AUTHENTICATION     | NULL             | GPL     |
| mysql_old_password    | ACTIVE | AUTHENTICATION     | NULL             | GPL     |
| CSV                   | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| MEMORY                | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| MyISAM                | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| MRG_MYISAM            | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| InnoDB                | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| INNODB_TRX            | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_LOCKS          | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_LOCK_WAITS     | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_CMP            | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_CMP_RESET      | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_CMPMEM         | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_CMPMEM_RESET   | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| PERFORMANCE_SCHEMA    | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| partition             | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| SPIDER                | ACTIVE | STORAGE ENGINE     | ha_spider.so     | GPL     |
| handlersocket         | ACTIVE | DAEMON             | handlersocket.so | BSD     |
+-----------------------+--------+--------------------+------------------+---------+
19 rows in set (0.01 sec)
# よし、両方入った


■spiderを使ってみる

# ここを参考に、MySQL::sandboxを立ち上げる
http://nippondanji.blogspot.jp/2010/04/spider.html
http://odokuchang.blogspot.jp/2012/04/mysqlspidersandboxsandbox.html

export SANDBOX_BINARY=$HOME/opt/mysql
export SANDBOX_HOME=$HOME/opt/sandboxes
 
mkdir ~/opt/mysql
cd ~/opt/mysql
 
ln -s /usr/local/mysql-5.5.14-spider 5.5.14
 
make_multiple_sandbox --how_many_nodes=4 5.5.14

cd ~/opt/sandboxes/multi_msb_5_5_14/
./use_all 'create database spider_test character set utf8'
cp /usr/local/mysql-5.5.14-spider/install_spider.sql .
./use_all 'source install_spider.sql'  

# 再起動
./stop_all
./start_all

# 各nodeのportを調査
./use_all 'select @@port'
# server: 1: 
@@port
13915
# server: 2: 
@@port
13916
# server: 3: 
@@port
13917
# server: 4: 
@@port
13918

# 各nodeの接続先を登録
# msandboxはmysql::sandboxのデフォルトユーザー名,パスワード
./use_all "CREATE SERVER n1 FOREIGN DATA WRAPPER mysql OPTIONS (USER 'root', PASSWORD '', HOST '127.0.0.1', PORT 3306);
CREATE SERVER n2 FOREIGN DATA WRAPPER mysql OPTIONS (USER 'msandbox', PASSWORD 'msandbox', HOST '127.0.0.1', PORT 13915);  
CREATE SERVER n3 FOREIGN DATA WRAPPER mysql OPTIONS (USER 'msandbox', PASSWORD 'msandbox', HOST '127.0.0.1', PORT 13916);  
CREATE SERVER n4 FOREIGN DATA WRAPPER mysql OPTIONS (USER 'msandbox', PASSWORD 'msandbox', HOST '127.0.0.1', PORT 13917);  
CREATE SERVER n5 FOREIGN DATA WRAPPER mysql OPTIONS (USER 'msandbox', PASSWORD 'msandbox', HOST '127.0.0.1', PORT 13918);"

# 消すときは
# ./use_all "drop server n1;drop server n2;drop server n3;drop server n4;drop server n5;"

# ※通常のmysqlクライアントで各sandboxに繋ぐのも、いつもと同じ要領
# mysql -umsandbox -pmsandbox -h 127.0.0.1 -P 13916


# 3306のmysqlにテーブルを作り、同時にspiderのマスターにする。
mysql -uroot -h localhost -P 3306
use spider_test;
CREATE TABLE salaries (
  emp_no int(11) NOT NULL,
  salary int(11) NOT NULL,
  from_date date NOT NULL,
  to_date date NOT NULL,
  PRIMARY KEY (emp_no)
) ENGINE=SPIDER DEFAULT CHARSET=utf8
PARTITION BY HASH(emp_no) (
  PARTITION p1 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13915", table "salaries",',
  PARTITION p2 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13916", table "salaries",',
  PARTITION p3 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13917", table "salaries",',
  PARTITION p4 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13918", table "salaries",'
);

# node1からnode4までにテーブルを作る
./use_all "
use spider_test;
CREATE TABLE salaries (
    emp_no int(11) NOT NULL,
    salary int(11) NOT NULL,
    from_date date NOT NULL,
    to_date date NOT NULL,
    PRIMARY KEY (emp_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;"

# 消す場合は
# ./use_all 'use spider_test; drop table salaries;'
# 3306にデータをinsert
mysql -uroot -h localhost -P 3306
use spider_test;
insert into salaries(emp_no, salary) values(1, 1000);
insert into salaries(emp_no, salary) values(2, 2000);
insert into salaries(emp_no, salary) values(3, 3000);


./use_all 'use spider_test;
select * from salaries;'
# server: 1: 
# server: 2: 
emp_no salary from_date to_date
1 1000 0000-00-00 0000-00-00
# server: 3: 
emp_no salary from_date to_date
2 2000 0000-00-00 0000-00-00
# server: 4: 
emp_no salary from_date to_date
3 3000 0000-00-00 0000-00-00

# node1から4に分散されてます。
# sandbox便利だのう。


■handlersocket経由でspiderを使ってみる

# ruby用のhandlersocketクライアントを入れる
cd /usr/src/HandlerSocket-Plugin-for-MySQL/
sudo wget https://bitbucket.org/winebarrel/ruby-handlersocket/get/tip.tar.gz
sudo tar zxvf tip.tar.gz
sudo mv winebarrel-ruby-handlersocket-c19841e47ea2/* .
sudo rm -r winebarrel-ruby-handlersocket-c19841e47ea2/
sudo ruby extconf.rb
sudo make
sudo make install


# spiderとhandlersocketを同時に利用できるように、設定を追加
# ここを参照
http://wild-growth-ja.blogspot.jp/2011/02/mysqlspidervpotherspider-224-vp-013.html

vi /etc/my.cnf
spider_use_hs_read=1
spider_use_hs_write=1

# 全部再起動
mysql.server restart
cd ~/opt/sandboxes
./start_all


■ruby -> spider(9999) -> sandbox spider-node へ接続
vi ahndler_sample.rb

require 'rubygems'
require 'handlersocket'
 
hs = HandlerSocket.new('127.0.0.1', 9999)
# Args (default value)
# host(localhost), port(9998), timeout(600), listen_backlog(256)
 
# 3は識別子
hs.open_index(3, 'spider_test', 'salaries', 'PRIMARY', 'emp_no,salary');
 
# select 接続、条件、条件値、count、offset
p res = hs.execute_single(3, '>=', [2], 100, 0);

 
hs.close

## エラーでとれない

ruby handler_sample.rb 
[-1, "read: eof"]

# しかも、mysqlがクラッシュ
tail /var/log/system.log
May  3 18:40:19 xxx-no-MacBook-Air librariand[356]: no ubiquity account configured, not creating collection
May  3 18:40:19 xxx-no-MacBook-Air librariand[356]: error in handle_client_request: LibrarianErrorDomain/10/Unable to configure the collection.

# 起動しようとするとエラー。
mysqld
120503 18:43:11 [Warning] Setting lower_case_table_names=2 because file system for /var/lib/mysql/data/ is case insensitive
120503 18:43:11 InnoDB: The InnoDB memory heap is disabled
120503 18:43:11 InnoDB: Mutexes and rw_locks use GCC atomic builtins
120503 18:43:11 InnoDB: Compressed tables use zlib 1.2.5
120503 18:43:11 InnoDB: Initializing buffer pool, size = 128.0M
120503 18:43:11 InnoDB: Completed initialization of buffer pool
120503 18:43:11 InnoDB: highest supported file format is Barracuda.
120503 18:43:11  InnoDB: Waiting for the background threads to start
120503 18:43:12 InnoDB: 1.1.8 started; log sequence number 1604997
[Warning] handlersocket: open_files_limit is too small.
[Warning] handlersocket: open_files_limit is too small.
120503 18:43:12 [Note] Recovering after a crash using tc.log
120503 18:43:12 - mysqld got signal 11 ;
This could be because you hit a bug. It is also possible that this binary
or one of the libraries it was linked against is corrupt, improperly built,
or misconfigured. This error can also be caused by malfunctioning hardware.
We will try our best to scrape up some info that will hopefully help diagnose
the problem, but since we have already crashed, something is definitely wrong
and this may fail.

key_buffer_size=16384
read_buffer_size=262144
max_used_connections=0
max_threads=151
thread_count=17
connection_count=0
It is possible that mysqld could use up to 
key_buffer_size + (read_buffer_size + sort_buffer_size)*max_threads = 50051 K
bytes of memory
Hope that's ok; if not, decrease some variables in the equation.

Thread pointer: 0x0
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went
terribly wrong...
stack_bottom = 0x0 thread_stack 0x40000
0   mysqld                              0x00000001073c00fe my_print_stacktrace + 46
1   mysqld                              0x000000010720d13b handle_segfault + 523
2   libsystem_c.dylib                   0x00007fff87054cfa _sigtramp + 26
3   ???                                 0x00007fff66d47ae0 0x0 + 140734918589152
4   mysqld                              0x00000001071e43e5 _ZN11TC_LOG_MMAP4openEPKc + 1301
5   mysqld                              0x00000001072134a5 _Z11mysqld_mainiPPc + 10965
6   mysqld                              0x000000010714a7f4 start + 52
7   ???                                 0x0000000000000001 0x0 + 1

# あれ、そういえばhandlersocketのportは[9998-9999] なんだけど、
# sandbox使ってる場合、sandboxのmysqlのhandlersocketはどのportになるんだろう。
# しかも、handlersocket プラグインを各sandboxにinstallせねばならないのではなかろうか。
# spider-nodeがhandlersocket非対応ならsqlでデータ取得するというspiderの仕様だったと思うが、その動きは見られず。



■sandboxを1台に変更する
ruby -> spider(9997) -> sandbox spider-node(9999) へ接続

# spiderからsandboxのspider-nodeにhandlersocketでアクセスする際の設定を変更したいけど、
# spider -> sandboxへのアクセスでのport指定をどこでやればよいのか見つからないため、sandboxを1台に変更する。(デフォルトの9999で繋いでくれるかな。)

# 1台のsandboxを再定義
cd ~/opt/mysql
make_multiple_sandbox --how_many_nodes=1 5.5.14

cd ~/opt/sandboxes/multi_msb_5_5_14/
./use_all 'create database spider_test character set utf8'
cp /usr/local/mysql-5.5.14-spider/install_spider.sql .
./use_all 'source install_spider.sql'  


# 再起動
./stop_all
./start_all

# handlersocket
./n1
install plugin handlersocket soname 'handlersocket.so';

vi node1/my.sandbox.cnf
[mysqld]
handlersocket_port = 9998
handlersocket_port_wr = 9999
handlersocket_address =
handlersocket_verbose = 0
handlersocket_timeout = 300
handlersocket_threads = 16
thread_concurrency = 128
open_files_limit = 65535

spider_internal_xa=1
spider_semi_trx_isolation=2
spider_use_handler=1
spider_use_hs_read=1
spider_use_hs_write=1

# 各nodeのportを調査
./use_all 'select @@port'
# server: 1: 
@@port
13915

# 各nodeの接続先を登録
# msandboxはmysql::sandboxのデフォルトユーザー名,パスワード
./use_all "CREATE SERVER n1 FOREIGN DATA WRAPPER mysql OPTIONS (USER 'root', PASSWORD '', HOST '127.0.0.1', PORT 3306);
CREATE SERVER n2 FOREIGN DATA WRAPPER mysql OPTIONS (USER 'msandbox', PASSWORD 'msandbox', HOST '127.0.0.1', PORT 13915);"

# ※通常のmysqlクライアントで各sandboxに繋ぐのも、いつもと同じ要領
# mysql -umsandbox -pmsandbox -h 127.0.0.1 -P 13916


# 3306のmysqlにテーブルを作り、同時にspiderのマスターにする。
mysql -uroot -h localhost -P 3306
use spider_test;
CREATE TABLE salaries (
  emp_no int(11) NOT NULL,
  salary int(11) NOT NULL,
  from_date date NOT NULL,
  to_date date NOT NULL,
  PRIMARY KEY (emp_no)
) ENGINE=SPIDER DEFAULT CHARSET=utf8
PARTITION BY HASH(emp_no) (
  PARTITION p1 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13915", table "salaries",'
);

# node1にテーブルを作る
./use_all "
use spider_test;
CREATE TABLE salaries (
    emp_no int(11) NOT NULL,
    salary int(11) NOT NULL,
    from_date date NOT NULL,
    to_date date NOT NULL,
    PRIMARY KEY (emp_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;"

# spiderのhandlersocketのportを変更する。
vi /etc/my.cnf
[mysqld]
handlersocket_port = 9996
handlersocket_port_wr = 9997

mysql.server restart
cd ~/opt/sandboxes/
./restart_all


■実際にhandlersocketで接続
handler_sample1.rb

require 'rubygems'
require 'handlersocket'

# spider経由
hs = HandlerSocket.new('127.0.0.1', 9997)
# 直接sandbox
#hs = HandlerSocket.new('127.0.0.1', 9999)
# Args (default value)
# host(localhost), port(9998), timeout(600), listen_backlog(256)
 
# 3は識別子
hs.open_index(3, 'spider_test', 'salaries', 'PRIMARY', 'emp_no,salary');
 
# select 接続、条件、条件値、count、offset
p res = hs.execute_single(3, '>=', [2], 100, 0);
 
# insert 接続、行
hs.execute_insert(3, [1,"10000"]);
hs.execute_insert(3, [2,"20000"]);
hs.execute_insert(3, [3,"30000"]);
 
# delete 接続、条件、条件値、count、offset ← selectして消すため
# 詳細はここ
# https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/blob/master/docs-ja/perl-client.ja.txt
hs.execute_delete(3, '=', [2], 1, 0);
 
# 高速なexecute_multi
p hs.execute_multi(
  [
    [3, '=', [3], 1, 0],
    [3, '=', [1], 1, 0]
  ]
)
 
hs.close


■実行結果

# port 9997(spider)だとエラー
# ruby -> spider -> sandbox
ruby handler_sample1.rb 
[-1, "read: eof"]
[[-1, "read: eof"]]


# port 9999(spider-node)だととれる
# ruby -> sandbox
ruby handler_sample1.rb 
[0, "3", "3000"]
[[0, "3", "3000"], [0, "1", "1000"]]

なんでやねん!! documentのspider_use_hs_writeオプションの項に下のように書いてあるけど、handlersocketを別で入れたから駄目なのかな。 「このオプションは、handlersocket用パッチ適用済みバイナリでのみ利用可能。」 今日はここまで。。

2012年4月30日月曜日

MacのMySQLでHandlerSocketのベンチマークをとった

インストールはこちらを参照
MacにMySQL(5.5系)とHandlerSocketをいれた


■前提として、1台のmacで行っているため、スケールアウトのメリットを享受できていません。
そのため、Cassandra やhbaseでは期待する効果が出ていません!!
# cassandra のキースペースを作る
cassandra-cli
[default@unknown] create keyspace Test;
[default@unknown] use Test;
[default@Test] create column family Users with column_type = 'Standard' and comparator = 'UTF8Type';
bench.rb

#!/usr/bin/ruby
# -*- encoding: utf-8 -*-
require 'rubygems'
require 'cassandra'
require 'active_record'
require 'benchmark'
require 'handlersocket'
require 'memcache'

# ---------------------------------------
# init
# ---------------------------------------
# mysql :
#   mysql
#   use Test; truncate table user;
# memcacha :
#   ps aux | grep mem
#   kill -TERM xxxx
#   memcached -d
# cassandra 
#   cassandra-cli
#   use Test;
#   truncate Users;


# ---------------------------------------
# Insert / Select 性能
# ---------------------------------------
count = 100000
p 'count:' + count.to_s

# ---------------------------------------
# mysql
ActiveRecord::Base.establish_connection(
  :adapter => 'mysql2',
  :host => 'localhost',
  :username => 'root',
  :password => '',
  :database => 'test'
)
class User < ActiveRecord::Base
  self.table_name = 'user'
end

# active record 利用
Benchmark.bm { |rep|
  rep.report("mysql(o/r) insert ") {
    for num in 1..count
      name = 'name' + num.to_s
      User.create(:name => name)
    end
  }
}
Benchmark.bm { |rep|
  rep.report("mysql(o/r) select") {
    for num in 1..count
      id = rand(count).to_i + 1
      User.find(id)
    end
  }
}
# すべて削除
User.connection.execute('truncate table user')

# SQL利用
Benchmark.bm { |rep|
  rep.report("mysql(sql) insert ") {
    for num in 1..count
      name = 'name' + num.to_s
      User.connection.execute(sprintf('insert into user (user_id, name) values(%s, "%s");', num.to_s, name))
    end
  }
}
Benchmark.bm { |rep|
  rep.report("mysql(sql) select") {
    for num in 1..count
      id = rand(count).to_i + 1
      User.connection.execute(sprintf('select user_id, name from user where user_id = %s;', num.to_s))
    end
  }
}
# すべて削除
User.connection.execute('truncate table user')


# ---------------------------------------
# cassandra
include Cassandra::Constants
client = Cassandra.new("Test", "127.0.0.1:9160")
Benchmark.bm { |rep|
  rep.report("cassandra insert ") {
    for num in 1..count
      name = 'name' + num.to_s
      client.insert(:Users, num.to_s, { 'name' => name } )
    end
  }
}
Benchmark.bm { |rep|
  rep.report("cassandra select ") {
    for num in 1..count
      id = rand(count).to_i + 1
      client.get(:Users, id.to_s)
    end
  }
}

# ---------------------------------------
# memcached
cache = MemCache.new('localhost:11211')
Benchmark.bm { |rep|
  rep.report("memcache insert ") {
    for num in 1..count
      name = 'name' + num.to_s
      cache[name] = [num, name]
    end
  }
}
Benchmark.bm { |rep|
  rep.report("memcache select ") {
    for num in 1..count
      id = rand(count).to_i + 1
      name = 'name' + id.to_s
      ret = cache[name]
    end
  }
}

# ---------------------------------------
# handlersocket
hs = HandlerSocket.new('127.0.0.1', 9999)
hs.open_index(3, 'test', 'user', 'PRIMARY', 'user_id,name');

Benchmark.bm { |rep|
  rep.report("handlersocket insert ") {
    for num in 1..count
      name = 'name' + num.to_s
      hs.execute_insert(3, [num, name]);
    end
  }
}
Benchmark.bm { |rep|
  rep.report("handlersocket select ") {
    for num in 1..count
      id = rand(count).to_i + 1
      ret = hs.execute_single(3, '=', [id], 1, 0);
    end
  }
}





100,000件で試験
mysql, cassandraはtruncate後
memcacheは再起動後
ruby bench.rb 

・1回目
"count:100000"
      user     system      total        real
mysql(o/r) insert  74.320000   5.670000  79.990000 (110.039184)
      user     system      total        real
mysql(o/r) select 50.610000   2.650000  53.260000 ( 65.465168)
      user     system      total        real
mysql(sql) insert   4.430000   1.530000   5.960000 ( 29.590552)
      user     system      total        real
mysql(sql) select  5.470000   1.780000   7.250000 ( 13.550391)
      user     system      total        real
cassandra insert  47.180000   3.140000  50.320000 ( 61.347904)
      user     system      total        real
cassandra select  69.150000   3.470000  72.620000 (111.568860)
      user     system      total        real
memcache insert  14.930000   2.280000  17.210000 ( 17.935214)
      user     system      total        real
memcache select  16.710000   2.080000  18.790000 ( 19.264883)
      user     system      total        real
handlersocket insert   1.430000   1.510000   2.940000 ( 26.721995)
      user     system      total        real
handlersocket select   1.320000   1.520000   2.840000 (  7.867557)


・2回目
"count:100000"
      user     system      total        real
mysql(o/r) insert  74.500000   5.680000  80.180000 (110.519581)
      user     system      total        real
mysql(o/r) select 48.570000   2.550000  51.120000 ( 63.455239)
      user     system      total        real
mysql(sql) insert   4.420000   1.510000   5.930000 ( 29.494878)
      user     system      total        real
mysql(sql) select  5.500000   1.800000   7.300000 ( 13.632243)
      user     system      total        real
cassandra insert  45.500000   3.130000  48.630000 ( 59.691931)
      user     system      total        real
cassandra select  66.840000   3.350000  70.190000 (108.169329)
      user     system      total        real
memcache set  14.870000   2.340000  17.210000 ( 17.894275)
      user     system      total        real
memcache get  17.220000   2.260000  19.480000 ( 20.023089)
      user     system      total        real
handlersocket insert   1.490000   1.540000   3.030000 ( 26.834126)
      user     system      total        real
handlersocket select   1.310000   1.520000   2.830000 (  7.845230)

# 前提として、1台のmacで行っているため、スケールアウトのメリットを享受できていません。
# そのため、Cassandra やhbaseでは期待する効果が出ていません!!

# Cassandraは1台だとパフォーマンスでないな。
# やっぱO/Rも重い。
# memcacheのsystem時間がたくさんかかってるのはなんでだろう。localhostだと重いのかな。



# 条件
# mac book air OSX
# 1.7GHz Intel Core i5
# 4GB 1333 MHz DDR3

mysql --version
mysql  Ver 14.14 Distrib 5.5.20, for osx10.7 (i386) using readline 5.1

cassandra -v
1.1.0

telnet localhost 11211
stats
STAT version 1.4.5 ←memcachedのバージョン

HandlerSocket-Plugin-for-MySQL
1.1.0

MacにMySQL(5.5系)とHandlerSocketをいれた


# mysqlをインストール
# 既にインストール済みなら同じverのソースを持ってくる
brew install mysql

# ここを参考に。
http://www.bigegg.net/post/4651592197/handlersocket-osx

cp /Library/Caches/Homebrew/mysql-5.5.20.tar.gz .
tar zxvf mysql-5.5.20.tar.gz 
cd mysql-5.5.20
# configure.cmakeにバグがあるので、下記をみて修正。
http://bugs.mysql.com/bug.php?id=65050
cmake .

# 起動するも失敗
mysql.server start
ERROR! The server quit without updating PID file (…)

# conf
cp /usr/local/Cellar/mysql/5.5.20/support-files/my-small.cnf /etc/my.cnf 

mysql_install_db --verbose --user=`whoami` --basedir=/usr/local/Cellar/mysql/5.5.20 --datadir=/usr/local/var/mysql

# 再度起動
mysql.server start
Starting MySQL
.. SUCCESS! 

# handler socketをインストール
# ここを参考に。
https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL

git clone git://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL.git
cd HandlerSocket-Plugin-for-MySQL/

./autogen.sh
./configure --with-mysql-source=/Users/xxx/src/mysql-5.5.20(mysqlソースパス) --with-mysql-bindir=/usr/local/Cellar/mysql/5.5.20/bin/(mysqlコマンドのパス)  --with-mysql-plugindir=/usr/local/Cellar/mysql/5.5.20/lib/plugin
make
make install

# 別コンソールでmysqlを起動し、
mysql> install plugin handlersocket soname 'handlersocket.so';

# プラグインが入ったかな。
ls /usr/local/Cellar/mysql/5.5.20/lib/plugin/

# handlersocketの設定を入れる。
sudo vi /etc/my.cnf 
handlersocket_port = 9998
handlersocket_port_wr = 9999
handlersocket_address =
handlersocket_verbose = 0
handlersocket_timeout = 300
handlersocket_threads = 16
thread_concurrency = 128
open_files_limit = 65535

# mysql再起動
mysql.server stop
Shutting down MySQL
. SUCCESS! 
mysql.server start
Starting MySQL
.. SUCCESS! 


# 別コンソールでmysqlを起動し、
mysql> show plugins;
+-----------------------+--------+--------------------+------------------+---------+
| Name                  | Status | Type               | Library          | License |
+-----------------------+--------+--------------------+------------------+---------+
| binlog                | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| mysql_native_password | ACTIVE | AUTHENTICATION     | NULL             | GPL     |
| mysql_old_password    | ACTIVE | AUTHENTICATION     | NULL             | GPL     |
| CSV                   | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| MEMORY                | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| MyISAM                | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| MRG_MYISAM            | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| InnoDB                | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| INNODB_TRX            | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_LOCKS          | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_LOCK_WAITS     | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_CMP            | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_CMP_RESET      | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_CMPMEM         | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| INNODB_CMPMEM_RESET   | ACTIVE | INFORMATION SCHEMA | NULL             | GPL     |
| PERFORMANCE_SCHEMA    | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| partition             | ACTIVE | STORAGE ENGINE     | NULL             | GPL     |
| handlersocket         | ACTIVE | DAEMON             | handlersocket.so | BSD     |
+-----------------------+--------+--------------------+------------------+---------+
18 rows in set (0.00 sec)

# 入った!!


# 使ってみる
use test;
CREATE TABLE `user` (
    `user_id` INT(10) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(255) NULL DEFAULT NULL,
    `birthday` DATE NULL DEFAULT NULL,
    `memo` VARCHAR(255) NULL DEFAULT NULL,
    PRIMARY KEY (`user_id`)
);



# ruby用クライアントを入れる
# tarはここからダウンロード
https://bitbucket.org/winebarrel/ruby-handlersocket/src


cd HandlerSocket-Plugin-for-MySQL/ ←ここでmakeせねばならん。
tar zxvf winebarrel-ruby-handlersocket-c19841e47ea2.tar.gz 
cp winebarrel-ruby-handlersocket-c19841e47ea2/* .
ruby extconf.rb
make
make install

# ------------------------------------
# sample.rb
require 'handlersocket'

hs = HandlerSocket.new('127.0.0.1', 9999)
# Args (default value)
# host(localhost), port(9998), timeout(600), listen_backlog(256)

# 3は識別子
hs.open_index(3, 'test', 'user', 'PRIMARY', 'user_id,name,birthday,memo');

# select 接続、条件、条件値、count、offset
p res = hs.execute_single(3, '>=', ['2'], 100, 0);

# insert 接続、行
hs.execute_insert(3, [1,"name1","2001-01-01","memo1"]);
hs.execute_insert(3, [2,"name2","2001-01-01","memo2"]);
hs.execute_insert(3, [3,"name3","2001-01-01","memo3"]);

# delete 接続、条件、条件値、count、offset ← selectして消すため
# 詳細はここ
# https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/blob/master/docs-ja/perl-client.ja.txt
hs.execute_delete(3, '=', ['2'], 1, 0);

# 高速なexecute_multi
p hs.execute_multi(
  [
    [3, '=', ['3'], 1, 0],
    [3, '=', ['1'], 1, 0]
  ]
)

hs.close




# ------------------------------------
sample.rb

require 'handlersocket'

hs = HandlerSocket.new('127.0.0.1', 9999)
# Args (default value)
# host(localhost), port(9998), timeout(600), listen_backlog(256)

# 3は識別子
hs.open_index(3, 'test', 'user', 'PRIMARY', 'user_id,name,birthday,memo');

# select 接続、条件、条件値、count、offset
p res = hs.execute_single(3, '>=', ['2'], 100, 0);

# insert 接続、行
hs.execute_insert(3, [1,"name1","2001-01-01","memo1"]);
hs.execute_insert(3, [2,"name2","2001-01-01","memo2"]);
hs.execute_insert(3, [3,"name3","2001-01-01","memo3"]);

# delete 接続、条件、条件値、count、offset ← selectして消すため
# 詳細はここ
# https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/blob/master/docs-ja/perl-client.ja.txt
hs.execute_delete(3, '=', ['2'], 1, 0);

# 高速なexecute_multi
p hs.execute_multi(
  [
    [3, '=', ['3'], 1, 0],
    [3, '=', ['1'], 1, 0]
  ]
)

hs.close
次回はこちら
MacのMySQLでHandlerSocketのベンチマークをとった