おれのエンジニア力がそんなに高いわけがない
Amebaでブログを始めよう!

test


本文はここから

[node.js]特定の処理のときだけclusterを使いたい

やってみた。


var cluster = require('cluster');
var cpus = require('os').cpus().length;

// メインプロセスの処理
function main() {
console.log('main start ==================================');

cluster.on('exit', function(worker, code, signal) {
console.log('worker exit.', worker.id);

for (var id in cluster.workers) {
return;
}

// 全workerの処理が終了
console.log('finish!!');
});

cluster.on('fork', function(worker) {
// workerに送信
worker.send({ id: worker.id, signal: '処理を開始して' });

// workerから終了通知を受け取る
worker.on('message', function(msg) {
var w = cluster.workers[msg.id];

w.send({
id: w.id,
signal: 'exit'
});
});
});

for (var i = 0; i < cpus; i++) {
cluster.fork();
}
}

if (cluster.isWorker) {
process.on('message', function(m) {
// masterから受信

if (m.signal === 'exit') {
// masterから終了通知されたらworkerをexit
return process.exit(0);
}

// workerの処理
console.log('workerだよん');
process.send({ id: m.id, status: '処理終わったよ' });
});

return;
}

main();

setTimeout(function() {
main();
}, 3000);


普段は1プロセスで動いてる場合を想定していて、
mainという関数を実行するときだけclusterを起動して処理を分散させます。

実行結果↓

main start ==================================
workerだよん
workerだよん
worker exit. 1
worker exit. 4
workerだよん
worker exit. 2
workerだよん
worker exit. 3
finish!!
main start ==================================
workerだよん
workerだよん
worker exit. 5
worker exit. 5
workerだよん
workerだよん
worker exit. 8
worker exit. 8
workerだよん
workerだよん
worker exit. 7
worker exit. 7
workerだよん
workerだよん
worker exit. 6
finish!!
worker exit. 6
finish!!


2回目がなんかおかしい。なぜか2回ずつ動いてる。
結構悩んだんだけど、気付いたら至極単純なミスだったんだけど、
cluster.on(type, fn);
したら
EventEmitterにaddListenerされるので
2回目はexit, forkイベントに2つずつリスナーが存在していたという罠。

いや、罠でもなんでもなくて気づくのに時間かかっただけなんだけど。。。

ということで
cluster.onする前にremoveListenerしましょうね、というお話でした。

[node.js]Object.seal(obj)とかObject.freeze(obj)とか

コンストラクタで宣言されていないプロパティへのセットを防ぎたくていろいろ探してたところ
Object.seal 関数 (JavaScript)
というのを見つけたので試してみた。


function User() {
this.name = undefined;

Object.seal(this);
}

var user = new User();
user.name = 'まどか'; // セットできる
user.age = 14; // セットできない

console.log(user); // => { name: 'まどか' }

いい感じ。

さらにObject.freeze(obj)を使うとwritableがfalseになるので、
書き込み不可なオブジェクトを使いたい場合にはいいかも。


function User(name) {
this.name = name;

Object.freeze(this);
}

var user = new User('ほむほむ');
user.name = 'まどか'; // セットできない
user.age = 14; // セットできない

console.log(user); // => { name: 'ほむほむ' }

うんうん。

node.jsで行処理

node.jsで行処理 - NullPointer's Blog

を参考にイベント駆動な行処理を書いてたんですが、
100行毎になんか処理させる みたいなことがやりたくなったので
ちょっとだけ細工してみた。

LineReader側

LineReader.prototype._onStreamData = function(chunk) {
this.rs.pause();
this.buf += chunk;

var self = this;
(function searchLine() {
var i = self.buf.indexOf('\n');
if (i >= 0) {
var line = self.buf.slice(0, i + 1);
self.buf = self.buf.slice(i + 1);
self.emit('line', line, searchLine);
} else {
self.rs.resume();
}
})();
};



ロジック

var list = [];

reader.on('line', function(line, callback) {
list.push(line);

if (list.length < 100) {
return callback();
}

hogehoge(list, function() {
list = [];
callback();
});
});



という感じ。
lineイベントを発火するときに一緒にsearchLineを渡すことで
次の行を読むタイミングをロジック側に任せてます。
で、ロジック側では一定数(↑では100件)までは溜めるだけにしておいて
溜まったらhogehogeを実行!

あんまり良いやり方じゃないかもしれないけどね。


[MongoDB]forEachとJavaScriptで複雑な問い合わせを実現する。

今日の午前中の成果。


>db.Hoge.find();
{_id:1, part1:{code:A, name:madoka}, part2:{code:B, name:homura}, part3:{code:A, name:madoka}}
{_id:2, part2:{code:B, name:homura}, part4:{code:C, name:mami}}
{_id:3, part1:{code:B, name:homura}, part2:{code:A, name:madoka}}

のようなデータに対し、「code:A」のレコードを取得したいとします。

code:Aがpart1にしか存在しないとかだと簡単なのですが、
上記のようにcode:Aがどのpartに出てくるかわからない場合、
MongoDB標準のクエリではできないみたいです。(もしあったら教えてください)


で、これを実現するために書いたクエリが↓

> db.Hoge.find().forEach(function(rec){for(field in rec) {var data = rec[field];if(data&&data.code){if(data.code=="A"){print("_id:" + rec._id + " -> " + field + ".code." + data.code)}}}});
_id:1 -> part1.code.A
_id:1 -> part3.code.A
_id:3 -> part2.code.A

という感じです。
forEachでカーソル1件ずつ処理できるので、
JavaScriptでfunction書いて全partに対してcodeのチェックをしています。
(上ではpart以外のfieldもチェックしてますがw)


JavaScriptをストアドみたいに使えるのでけっこうなんでもできるんじゃないかと思います。


実践JS サーバサイド JavaScript 入門/井上 誠一郎

¥3,570
Amazon.co.jp

Document Design for Mongodb/著者不明

¥3,570
Amazon.co.jp

スナップショットなう


本文はここから

[MongoDB]実行計画 $explain

MongoDBもSQLと同じようにオプティマイザが実行計画を作っていて、
その実行計画を元にクエリを実行しています。

実行計画を確認するには、

>db.hoge.find().explain();
{
"cursor" : "BasicCursor",
"nscanned" : 4,
"nscannedObjects" : 4,
"n" : 4,
"millis" : 0,
"nYields" : 0,
"nChunkSkips" : 0,
"isMultiKey" : false,
"indexOnly" : false,
"indexBounds" : {

}
}

みたいな。

上記だと全件取得なのでindexが使われておらず、
"cursor" : "BasicCursor" になっています。
とはいえ4件しかないからめちゃくちゃ速い("millis" : 0)ですが。


>db.hoge.find({"_id":"madoka"}).explain();
{
"cursor" : "BtreeCursor _id_",
"nscanned" : 1,
"nscannedObjects" : 1,
"n" : 1,
"millis" : 56,
"nYields" : 0,
"nChunkSkips" : 0,
"isMultiKey" : false,
"indexOnly" : false,
"indexBounds" : {
"_id" : [
[
"e39d9bfe5c521e2e",
"e39d9bfe5c521e2e"
]
]
}
}

"cursor" : "BtreeCursor _id_" になりました。
これは _id_ というindexを使ってるという意味です。
MongoDBのindexはB-Treeで構築されていることもわかります。

ちなみに _id_ はcollectionが作られた際に _id に対して自動的に貼られるindexです。
RDBで言うところのprimary keyに貼られるunique indexみたいなもんです。

・どれだけ時間かかってるのか (millis)
・indexが使われているのか (cursor)
・そのindexは効果的なのか (nscanned : スキャン対象のオブジェクト数。多いとindexの効果が薄い)
といったことがわかります。


今担当してるPJでは _id_ 以外のindexは貼らない方針なのであまり使わないですが、
どうしても _id 以外で検索かける必要が出てきたら要注意だな~と。


MongoDBのチューニングについて詳しいスライドがあったので載せておこう。


NoSQLデータベースファーストガイド/佐々木 達也

¥2,310
Amazon.co.jp

[MongoDB]$unset, $rename

最近仕事で使ったコマンドの使い方を整理しとく。

【$unset】
フィールドを削除する。

基本的な使い方は、
db.hoge.update({}, { $unset : { fieldname : 1 } });

SQLで書くと
ALTER TABLE hoge DROP COLUMN filedname;

になります。
ただ、JSONでデータを持ってるMongoDBではデータを
{ _id : code, name : madoka, score : { math : 50, english : 24, science : 61 }}
のように階層構造的に持つことができるため$unsetの使い方も注意が必要。

上の例で欠点になってる英語のデータを消したいってときは
db.hoge.update({ _id : code }, { $unset : { score.english : 1 } });
になります。

db.hoge.update({ _id : code }, { $unset : { score : 1 } });
だとscore自体が消えてしまうので"."で繋いでフィールドを指定します。

ちなみにupdateの第1引数({ _id : code })はセレクタ(SQLでいうところのWHERE句)です。
指定しない({})と、まどか以外のデータも消えてしまいます。円環の理に導かれて。

※2012/01/18 修正
第一引数を{}にしても1件しか変更されませんでした。
複数行まとめて更新する場合は第四引数(multi)をtrueにする必要があります。



【$rename】
フィールド名を変更する。

基本的な使い方は、
db.hoge.update({}, { $rename : { oldname : newname } });

SQLでは
ALTER TABLE hoge RENAME COLUMN oldname TO newname;
ですね。使ったことないけど。

$renameで便利だなぁと感じたのは、単に名前を変更するだけじゃなく
階層自体を変更できること。
例えば、上でも例にあげた
{ _id : code, name : madoka, score : { math : 50, english : 24, science : 61 }}
が、1学期のテストのスコアだとすると、2学期のスコアどうやって入れんの?ってことになる。
scoreをscore_firstみたいにrenameして、score_secondをupdateで入れてもいいけど、
score_*****って記述が複数になるのでなんかダサイ。

よし、じゃあscoreの下にもう1つレイヤー作ろう!
db.hoge.update({}, { $rename : { score : score.first } });

⇒{ _id : code, name : madoka, score : { first : { math : 50, english : 24, science : 61 }}}

これで2学期のデータはscore.secondでupdateすればOKですね。


今日は台風来てるのでこのへんで。
↓アフィリエイト貼ってみた。

MongoDB: The Definitive Guide/Kristina Chodorow

¥3,261
Amazon.co.jp

魔法少女まどか☆マギカ ねんどろいど 鹿目まどか (ノンスケール ABS&PVC塗装済み可動フ.../グッドスマイルカンパニー

¥3,500
Amazon.co.jp

2011/08/18

[java]SuperCSVでParseDateするときに空データがやっかいな件

久々に更新。
ちょっと離れてる間にチーム異動して、今JavaやってないんですがJavaネタで書きます。

CSV扱う用のライブラリはいくつもあるんだけど、
今回はこの辺を参考にSuperCSVを選択してみた。

使い方はこんな感じ。

mainクラス

public static void main(String args[]) {
try {
ICsvBeanReader file = new CsvBeanReader(new FileReader(args[0]),
CsvPreference.EXCEL_PREFERENCE);
final String[] header = file.getCSVHeader(true);
SampleBean sample = null;
while ((sample = file.read(SampleBean.class, header,
SampleBean.processors)) != null) {
System.out.println(sample.getId());
System.out.println(sample.getName());
System.out.println(sample.getDate());
}
} catch (Exception e) {
System.out.println(e);
}
}

Beanクラス

public class SampleBean {

private String id;
private String name;
aprivate Date date;

public static final CellProcessor[] processors = new CellProcessor[] {
new Unique(new StrMinMax(1,10)),
new StrMinMax(1,30)),
new ParseDateEx("yyyy/MM/dd")
};

public String getId() {
return Id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}

基本的にはこれだけで動きます。
で、やってみたのですが、、、Dateが空のデータがあると落ちます(;゚Д゚)

ParseDateのソース読むと、value == nullでNullInputExceptionをスローするらしい。
今回はDateが空のときにnullをセットしてほしいのでこれではムリ。

もしかしたらちゃんとしたやり方があるのかもしれないけど、
今回はParseDateを使わずにParseDateを継承したParseDateExを作って逃げる。

execute()の

final Date result = formatter.parse((String) value);
return next.execute(result, context);


Date result = null;
if (value != null && !"".equals((String)value)) {
result = formatter.parse((String) value);
}
return next.execute(result, context);

こうするだけです。
next.executeはresultを返却してくるだけなので問題ない。

と思ったら今度はCsvBeanReader#fillObjectでぬるぽ発生。
setterメソッド名を取得するところで、Processorで定義したフォーマットにparseした結果を使っているので
parseした結果がnullだとダメみたい。
でもMethodCache#getSetMethodはvariableTypeがnullでもある程度信頼できるメソッド名が取得できそうなので
CsvBeanReaderの実装がイマイチだなぁと思いつつ、仕方ないのでMyCsvBeanReaderを作って

fillObject()の

cache.getSetMethod(resultBean, nameMapping[i], lineResult.get(i).getClass()).invoke(resultBean, lineResult.get(i));
を、

Class resultClass = null;
Object resultObj = lineResult.get(i);
if (resultObj != null) {
resultClass = resultObj.getClass();
}
cache.getSetMethod(resultBean, nameMapping[i], resultClass).invoke(resultBean, resultObj);

で動きました。

でも空文字の対処方法はちゃんと用意されてると思うだけどなー。
英語のドキュメント読むのが面倒だったせいでこんなことになりました。

いじょ。

次回はそろそろnode.jsネタを書きたい!