mrubyを今更ながらhelloworld
今更ではありますが、mrubyのhello worldを書いてみましたよ
あらら、ほんとうにrubyがcから動かせちゃいましたね、と
% ./build.sh % ls a.out build.sh hello.c mruby % ./a.out Executing Ruby code from C! "hello world!"
ビルドしたコードは
https://github.com/mruby/mruby/wiki/Hello-World
こちらから拝借したもの
私が書いたものとビルドしたソース一式はとりあえず
https://github.com/doublefree/helloworld/tree/master/mruby
へおいときました。build.shをたたけば動くはず。いや、Makeファイルとか書かなくてごめんなさい。
LRUキャッシュとしてのRedis
RedisはList機能など、いろいろ高機能っぽいのでちょっとリッチなLRUキャッシュとして使ってみようと考え始めて見た
memoryがしきい値を超えたときの説明は
http://siguniang.wordpress.com/2012/03/19/redis%E3%81%AEmaxmemory-policy/
に詳しく書いてあるのでそちらを参照
ここで候補に挙がるのは以下の2つ
- volatile-lru
- allkeys-lru
ちょっとソース追っかけながら見てみる。
メモリの解放処理とかをやってるのはこの辺
http://doublefree.org:8080/source/xref/redis-2.4.13/src/redis.c#1541
1586 if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU || 1587 server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM) 1588 { 1589 dict = server.db[j].dict; 1590 } else { 1591 dict = server.db[j].expires; 1592 } 1593 if (dictSize(dict) == 0) continue;
expiresを追っかけてみると
http://doublefree.org:8080/source/xref/redis-2.4.13/src/redis.h#300
300 dict *expires; /* Timeout of keys with a timeout set */
http://doublefree.org:8080/source/xref/redis-2.4.13/src/db.c#448
448void setExpire(redisDb *db, robj *key, time_t when) { 449 dictEntry *de; 450 451 /* Reuse the sds from the main dict in the expire dict */ 452 de = dictFind(db->dict,key->ptr); 453 redisAssert(de != NULL); 454 dictReplace(db->expires,dictGetEntryKey(de),(void*)when); 455} 456
どうもexpire時間がセットされているkeyは通常のdictionary(dict)とは別のdictionary(expires)にもsetされるみたい。
ちなみに、replaceDictは
160static int dictReplace(dict *ht, void *key, void *val) { 161 dictEntry *entry, auxentry; 162 163 /* Try to add the element. If the key 164 * does not exists dictAdd will suceed. */ 165 if (dictAdd(ht, key, val) == DICT_OK) 166 return 1; 167 /* It already exists, get the entry */ 168 entry = dictFind(ht, key); 169 /* Free the old value and set the new one */ 170 /* Set the new value and free the old one. Note that it is important 171 * to do that in this order, as the value may just be exactly the same 172 * as the previous one. In this context, think to reference counting, 173 * you want to increment (set), and then decrement (free), and not the 174 * reverse. */ 175 auxentry = *entry; 176 dictSetHashVal(ht, entry, val); 177 dictFreeEntryVal(ht, &auxentry); 178 return 0; 179}
keyがあれば上書きして、なければaddするだけ。dictからexpiresにkeyを移している訳じゃない
expireに関するredisのdocumentを参照してみると
http://redis.io/commands/expire
ふむ。expireというコマンドを秒数指定で既存のkeyに打つのか。
実際にsetExpireが呼ばれているのも以下のコードから分かるようにexpireコマンドから
http://doublefree.org:8080/source/xref/redis-2.4.13/src/db.c#528
528void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) { 529 dictEntry *de; 530 long seconds; 531 532 if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return; 533 534 seconds -= offset; 535 536 de = dictFind(c->db->dict,key->ptr); 537 if (de == NULL) { 538 addReply(c,shared.czero); 539 return; 540 } 541 /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past 542 * should never be executed as a DEL when load the AOF or in the context 543 * of a slave instance. 544 * 545 * Instead we take the other branch of the IF statement setting an expire 546 * (possibly in the past) and wait for an explicit DEL from the master. */ 547 if (seconds <= 0 && !server.loading && !server.masterhost) { 548 robj *aux; 549 550 redisAssert(dbDelete(c->db,key)); 551 server.dirty++; 552 553 /* Replicate/AOF this as an explicit DEL. */ 554 aux = createStringObject("DEL",3); 555 rewriteClientCommandVector(c,2,aux,key); 556 decrRefCount(aux); 557 signalModifiedKey(c->db,key); 558 addReply(c, shared.cone); 559 return; 560 } else { 561 time_t when = time(NULL)+seconds; 562 setExpire(c->db,key,when); 563 addReply(c,shared.cone); 564 signalModifiedKey(c->db,key); 565 server.dirty++; 566 return; 567 } 568} 569 570void expireCommand(redisClient *c) { 571 expireGenericCommand(c,c->argv[1],c->argv[2],0); 572} 573 574void expireatCommand(redisClient *c) { 575 expireGenericCommand(c,c->argv[1],c->argv[2],time(NULL)); 576}
なんか、volatile-lruが求めているものに近いなぁと思ったけど、これだとallkeys-lruの方が近い気がする。
でも、allkeys-lruだと、
http://doublefree.org:8080/source/xref/redis-2.4.13/src/redis.c#1604
1604 else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU || 1605 server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU) 1606 { 1607 for (k = 0; k < server.maxmemory_samples; k++) { 1608 sds thiskey; 1609 long thisval; 1610 robj *o; 1611 1612 de = dictGetRandomKey(dict); 1613 thiskey = dictGetEntryKey(de); 1614 /* When policy is volatile-lru we need an additonal lookup 1615 * to locate the real key, as dict is set to db->expires. */ 1616 if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU) 1617 de = dictFind(db->dict, thiskey); 1618 o = dictGetEntryVal(de); 1619 thisval = estimateObjectIdleTime(o); 1620 1621 /* Higher idle time is better candidate for deletion */ 1622 if (bestkey == NULL || thisval > bestval) { 1623 bestkey = thiskey; 1624 bestval = thisval; 1625 } 1626 } 1627 } 1628
maxmemory_samplesの数(defaultで3)だけrandomに抽出してそのなかでLRUが一番古い(大きい)keyを消去対象として選出している
これだと運が良くないと、結構新しいkeyがどんどんevictされていっちゃうので、memcached的なLRUと結構違う。keyの管理方法の問題だけど、これだとなかなか悩ましい。maxmemory_samplesの値を調整するともうちょっとまともにならないか、試してみようと思う。
Job Queue
Job Queueが欲しくて、いろいろ調べた。
要件としては、
- persistent queue (not on memory, but storage)
- low cost of maintenance
- programming language independent
こんな感じのシンプルなJob Queue
FIFOでqueueを取り出せればいい。
という要件でいろいろ探したけれど、なかなかフィットするものがない。
その中でも一番良さそうだったのがMysqlのストレージエンジンであるQ4M。
プロトタイプを作って実験してみても、なんだか良さそう。
ただ、困ったことが一つ。
Q4Mはreplicationに対応していない、、、
完全なミラーリングがしたい訳じゃない(処理中のqueueくらい取りこぼしてもいい)んだけど、DBサーバすっとんですべてのqueueを失うような状況だけは避けたい。
https://groups.google.com/group/q4m-general/browse_thread/thread/32fa1c16bd149277/a4f12c3e269f3069?hl=ja&lnk=gst&q=replication#a4f12c3e269f3069
これを見ても、replicationするにはDRBDとかを使う必要があるらしい。
でも、サーバクラッシュするような状況で論理的に正しいファイルがDRBDのミラーリング先にできているのか不安っていうのと、heartbeatとか使ってMysqlのデータを動的に復旧させようって言うのはもろもろ無理があるというか大変すぎる気がする。
そもそものシンプルなJob Queueが欲しいというスタートから大きく逸脱している気がする。
DeNA, mixi等、大規模システムで導入されているのだから、何か運用のコツ等があるのだろうか。。
困った、、、
(続) Erlangでチャットを書いてみた
前回の日記 [ Erlangでチャットを書いてみた ( http://d.hatena.ne.jp/doublefree/20110718/1310970177 ) ]
で書いていた、Erlang チャットのやり残しのうちのひとつである
- いなくなったユーザのログアウト処理
の部分を書き足したので
https://github.com/doublefree/brwchat/commit/2637ddfc024e51df43cea443787ba479e28eb04e
pushしておいた。
明示的なログアウト機能はつけてないけど、まぁこれで最低限のことはできたかな、とおもった。
一段落したのでOS X Lionにでも作業用Macをアップデートしてみようかと思ってる
Erlangでチャットを書いてみた
最近Erlangの入門書を読んだので、Erlangで何か書きたいなとおもっていました。
特に何も思いつかなかったので、ありきたりですが、チャットを書いてみました。
- やりたかったこと
- ブラウザだけでチャットしたいよね
- 超高速ポーリングとか、やだよね(昔学生時代にそんなチャットを作ったな、、)
- 基本的な構成
- 動作の仕組み
- ログイン、メッセージの書き込みは同期的(javascriptだから実際は非同期だけど、イメージの問題)にCGIに投げつけている
- メッセージ取得部分は、ロングポーリングなイメージで、jsonpのリクエストをCGIサーバに投げて、CGIサーバが新着メッセージがあるとそのリクエストに返事する、という仕組み。Cometになっているか分からないけどそういうのをイメージして作った
- http://ja.wikipedia.org/wiki/Comet <-- Comet
- やり残していること
- ロングポーリングっていってるけど完全に投げっぱなしなので、ある程度でタイムアウトもうけて再度リクエストを投げなおすように、クライアント/サーバの双方にそういう仕組みを入れたい
- ログアウトとかないのでしばらく応答がなかったクライアントは強制的に退出扱いにするとか、そういう仕組みは入れないとサーバが死ぬ
- 今回作ったものは1つのチャットルームでみんながしゃべる、というものだけど、erlang的にはおいしいところを味わいきれていない。
- 多くのチャットルームがあって、それぞれにerlangのノードが割り当てられている
- ノードの管理をするスーパーノードがいて、そいつに行き先のノードを聞いて、、、、っていうすてきなことができたらいいな、、、あはw
- 奥さんのすてきな記事( http://labs.cybozu.co.jp/blog/kazuho/archives/2007/02/keeping_comet_alive.php )にあるような諸処の問題の検証と対策
- 動作サンプル
- http://doublefree.org:8081/
- 誰でも適当な名前でログインできます
- ソース
Erlang + yawsでSession(Cookie)ハンドリング
Erlangのhttpサーバであるyawsをいじって遊んでいます
おきまりのHttp関連の操作もどうやったらいいか分からずに
http://yaws.hyber.org/yman.yaws?page=yaws_api
この辺を見ながら試行錯誤。
とりあえずcookieを使用したsession操作をしたくて組んでみました。
out(Arg) ->
SessionName = "hogehoge",
% Session処理
C = (Arg#arg.headers)#headers.cookie,
{SessionCookie, SetCookie} = case yaws_api:find_cookie_val(SessionName, C) of
[] ->
Cookie = yaws_api:new_cookie_session({}),
CO = yaws_api:setcookie(SessionName, Cookie, "/"),
{Cookie, CO};
Cookie ->
case yaws_api:cookieval_to_opaque(Cookie) of
{ok, OP} ->
Cookie,
{Cookie, undefined};
{error, no_session} ->
CO = yaws_api:setcookie(SessionName, Cookie, "/"),
{Cookie, CO}
end
end,case SetCookie of
undefined -> {html, "found cookie"};
_Else -> [SetCookie, {html, "cookie may be created"}]
end.
肝なのは最後の数行の
case SetCookie of
undefined -> {html, "found cookie"};
_Else -> [SetCookie, {html, "cookie may be created"}]
end.
この辺。つまりはSetCookieの変数部分の戻り値が、そのままheaderになっていて、html本文返す前にSet-Cookieヘッダ返さないといけないよ、という至極当たり前なことなのですが、この辺が隠蔽されていないフレームワークを使用するのが久々なのでちょっとはまった。
だって、このへん ( http://yaws.hyber.org/yman.yaws?page=yaws_api ) のマニュアルを見ても
setcookie(Name, Value, [Path, [ Expire, [Domain , [Secure]]]])
Sets a cookie to the browser.
って書いてあるだけで、ヘッダー相当の内容がこの関数から返ってくるとは思わなかった。
ちゃんと
case SetCookie of
undefined -> {html, "found cookie"};
_Else -> [SetCookie, {html, "cookie may be created"}]
end.
こう書いたら、
200 OK. .Server: Yaws 1. 90..Date: Sun, 1 9 Jun 20 11 14:53 :25 GMT.
.Content -Length: 22..Con tent-Type: text/ html
Set-Cookie : hogehoge=nonod e@nohost-3786894 771773034497;
path=/
こんな感じでSet-Cookieヘッダ返ってきて、めでたしめでたし。
うーーーん、2時間近くはまったなぁ
(続) apacheとnginxのベンチーマーク
apacheとnginxでベンチマーク
このエントリの続きとして、
apacheとnginxのベンチマークをさくらVPSでもやってみた。(静的ファイルのみ)
自宅からサーバにたいしてabしても結果が変わらなかったので、自宅マシンがしょぼい、もしくはサーバの上流でNWしぼられてるんだと解釈して、サーバにログインして、localhostにむけてabした結果
- 静的ファイル
Concurrency Level: 1000
Time taken for tests: 0.848 seconds
Complete requests: 3000
Failed requests: 0
Write errors: 0
Total transferred: 1030900 bytes
HTML transferred: 31720 bytes
Requests per second: 3536.44 [#/sec] (mean)
Time per request: 282.770 [ms] (mean)
Time per request: 0.283 [ms] (mean, across all concurrent requests)
Transfer rate: 1186.76 [Kbytes/sec] received
-
- nginx
Concurrency Level: 1000
Time taken for tests: 0.297 seconds
Complete requests: 3000
Failed requests: 0
Write errors: 0
Total transferred: 666000 bytes
HTML transferred: 36000 bytes
Requests per second: 10100.43 [#/sec] (mean)
Time per request: 99.006 [ms] (mean)
Time per request: 0.099 [ms] (mean, across all concurrent requests)
Transfer rate: 2189.74 [Kbytes/sec] received
- 軽いphp
echoするだけのphp
Concurrency Level: 1000
Time taken for tests: 1.243 seconds
Complete requests: 3000
Failed requests: 0
Write errors: 0
Total transferred: 783360 bytes
HTML transferred: 12288 bytes
Requests per second: 2413.52 [#/sec] (mean)
Time per request: 414.332 [ms] (mean)
Time per request: 0.414 [ms] (mean, across all concurrent requests)
Transfer rate: 615.45 [Kbytes/sec] received
静的ファイルと比べて2/3ほどのレスポンス
-
- nginx
Concurrency Level: 1000
Time taken for tests: 1.421 seconds
Complete requests: 3000
Failed requests: 0
Write errors: 0
Total transferred: 489000 bytes
HTML transferred: 12000 bytes
Requests per second: 2110.57 [#/sec] (mean)
Time per request: 473.806 [ms] (mean)
Time per request: 0.474 [ms] (mean, across all concurrent requests)
Transfer rate: 335.96 [Kbytes/sec] received
apacheと遜色ないレベル
- 遅いphp
400ミリsec sleepしたあとechoするphp
Concurrency Level: 1000
Time taken for tests: 8.999 seconds
Complete requests: 3000
Failed requests: 0
Write errors: 0
Total transferred: 960000 bytes
HTML transferred: 204000 bytes
Requests per second: 333.36 [#/sec] (mean)
Time per request: 2999.746 [ms] (mean)
Time per request: 3.000 [ms] (mean, across all concurrent requests)
Transfer rate: 104.18 [Kbytes/sec] received
-
- nginx
Concurrency Level: 1000
Time taken for tests: 3.555 seconds
Complete requests: 3000
Failed requests: 801
(Connect: 0, Receive: 0, Length: 801, Exceptions: 0)
Write errors: 0
Non-2xx responses: 2199
Total transferred: 962472 bytes
HTML transferred: 478875 bytes
Requests per second: 843.87 [#/sec] (mean)
Time per request: 1185.013 [ms] (mean)
Time per request: 1.185 [ms] (mean, across all concurrent requests)
Transfer rate: 264.39 [Kbytes/sec] received
nginxがnon-blockingだからか、apacheと比べると数字がいい
failed requestが多いのが気になる