Varnish を CouchDB 用のReverse Proxyに活用する。

ご無沙汰しております、生きています。

さて、CouchDB 1.0 がでて、incremental replication もだいぶ安定した感があるので、自宅の CouchDB でロードバランスしたくなりました。

Apache mod_proxy_balancer という手もあるのですが、Incremental (MapReduce + Replication) という環境では、High Performance な Caching Proxy になる Varnish という選択肢もおもしろそうなので試しています。

構成

twitter のタイムラインをバックアップしている http://www.yssk22.info/relax/_design/mytweets/_list/timeline/by_date?descending=true のデータを対象にします。1つのドキュメントは 1.5KB ぐらいです。

CouchDB の設定

init.d の起動スクリプトに Incremental Replication を仕込んでおきます。これ忘れると、うっかり再起動したときにレプリケーションが止まっている、とかそういう感じになるので注意。

#!/bin/sh

/opt/relax/master/couchdb/pro-1.0.0/etc/init.d/couchdb $@
/opt/relax/slave/couchdb/pro-1.0.0/etc/init.d/couchdb $@

start_replication () {
      sleep 1  # wait for slave waking up
      curl -X POST --data '{"source": "http://127.0.0.1:5984/relax", "target": "http://127.0.0.1:15984/relax", "continuous" : true }' http://localhost:5984/_replicate
} 

case "$1" in
   start)
      start_replication
      ;;
   restart)
      start_replication
      ;;
   *)
      ;;
esac

Varnish の設定

初心者なので、 vcl にこんな感じで とりあえず GETとHEAD をロードバランスしてくれればいいや、と。
まずは、効果測定のために、Varnish キャッシュを無効にしておきました。

backend default {
.host = "127.0.0.1";
.port = "5984";
}

backend slave {
.host = "127.0.0.1";
.port = "15984";
}

director couchdb round-robin {
   { .backend = default; }
   { .backend = slave; }
}

sub vcl_recv {
    if (req.request != "GET" && req.request != "HEAD") {
        return (pass);
    }
    set req.backend = couchdb;
    return (pass);
}

ab してみる

クライアントには iMac (Core2Duo 3.02GHz, 4GB Memory) を使います。

まずは、直接CouchDBにある 1 tweet を直接たたく感じで。

$ ab -n 1000 -c 30 "http://relax:5984/relax/fffdcaf2b6bafa2f6a39a513d48d5e45"

Concurrency Level:      30
Time taken for tests:   2.431 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      1687000 bytes
HTML transferred:       1452000 bytes
Requests per second:    411.42 [#/sec] (mean)
Time per request:       72.918 [ms] (mean)
Time per request:       2.431 [ms] (mean, across all concurrent requests)
Transfer rate:          677.80 [Kbytes/sec] received
  • c 30 が正しいかはともかく(もうちょっと多くてもいいかな...)、こんな感じです。

Varnish 経由でたたいてみます。

$ ab -n 1000 -c 30 "http://relax:6081/relax/fffdcaf2b6bafa2f6a39a513d48d5e45"

Concurrency Level:      30
Time taken for tests:   1.671 seconds
Complete requests:      1000
Failed requests:        7
   (Connect: 0, Receive: 0, Length: 7, Exceptions: 0)
Write errors:           0
Non-2xx responses:      7
Total transferred:      1747622 bytes
HTML transferred:       1445098 bytes
Requests per second:    598.52 [#/sec] (mean)
Time per request:       50.123 [ms] (mean)
Time per request:       1.671 [ms] (mean, across all concurrent requests)
Transfer rate:          1021.48 [Kbytes/sec] received

ちょっと効果がでたようです。beam.smp 自体は concurrent に動いてはいるんですけどね。いろんな作用のせいで(ちょっと詳しく見る必要ありそう)、単体時が 500 rec/sec, RB時が 600 rec/sec ぐらいになっていました。

ab してみる (2)

次に MapReduce View。これは更新しながらはからないと余り意味がないのですが、とりあえず、tweetを日付順に並び替えて、10件とるURIをたたきまくります。

CouchDB 直接。

$ ab -n 1000 -c 30 "http://relax:5984/relax/_design/mytweets/_view/by_date?limit=10&descending=true"

Concurrency Level:      30
Time taken for tests:   13.289 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      27286000 bytes
HTML transferred:       27082000 bytes
Requests per second:    75.25 [#/sec] (mean)
Time per request:       398.667 [ms] (mean)
Time per request:       13.289 [ms] (mean, across all concurrent requests)
Transfer rate:          2005.17 [Kbytes/sec] received

Varnish 経由。

$ ab -n 1000 -c 30 "http://relax:6081/relax/_design/mytweets/_view/by_date?limit=10&descending=true"

Concurrency Level:      30
Time taken for tests:   10.043 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      27377000 bytes
HTML transferred:       27082000 bytes
Requests per second:    99.57 [#/sec] (mean)
Time per request:       301.293 [ms] (mean)
Time per request:       10.043 [ms] (mean, across all concurrent requests)
Transfer rate:          2662.06 [Kbytes/sec] received

こちらも若干改善された感じ。全部メモリにのっちゃうのでそんな変わらないかなー、と思いきや。

ab してみる (3)

最後に、HTMLレンダリングURIもやってみます。これは毎回JSプロセスがCGI的に動くので、どうでしょう。

$ ab -n 1000 -c 30 "http://relax:5984/relax/_design/mytweets/_list/timeline/by_date?limit=10&descending=true"
Concurrency Level:      30
Time taken for tests:   40.317 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      7127000 bytes
HTML transferred:       6942000 bytes
Requests per second:    24.80 [#/sec] (mean)
Time per request:       1209.498 [ms] (mean)

割とがんばっている気がします。JSでHTMLをレンダリングするって結構重い処理なので。。。(pure JS実装のtemplate engineしかないもので。。。

Varnish版。

$ ab -n 1000 -c 30 "http://relax:6081/relax/_design/mytweets/_list/timeline/by_date?Concurrency Level:      30
Time taken for tests:   34.386 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      7217500 bytes
HTML transferred:       6942000 bytes
Requests per second:    29.08 [#/sec] (mean)
Time per request:       1031.594 [ms] (mean)
Time per request:       34.386 [ms] (mean, across all concurrent requests)
Transfer rate:          204.97 [Kbytes/sec] received

あんまりかわらなくなっちゃいました。

じゃあ、Varnish Cache を有効にしてみようか。

vcl_recv で最後に lookup します。尚、キャッシュはデフォルトの1GB。

sub vcl_recv {
    if (req.request != "GET" && req.request != "HEAD") {
        return (pass);
    }
    set req.backend = couchdb;
    return (lookup);
}

測定

$ ab -n 1000 -c 30 "http://relax:6081/relax/_design/mytweets/_list/timeline/by_date?descending=true"

Concurrency Level:      30
Time taken for tests:   1.673 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      196220832 bytes
HTML transferred:       195932256 bytes
Requests per second:    597.55 [#/sec] (mean)
Time per request:       50.205 [ms] (mean)
Time per request:       1.673 [ms] (mean, across all concurrent requests)
Transfer rate:          114504.36 [Kbytes/sec] received

20倍ぐらい高速になりました。 tweet 自体は cron で 10 分ごとにまとめてとってきてCouchDBにつっこんでいるので、そのタイミングで Varnish Cache を破棄させるもよし、 CouchDB の update notirifcation を使って Cache を破棄させるもよし。

なんかこの組み合わせはすばらしい気がする。

もう少し気になること。

master -> slave 構成ですが、逆向きのincremental replicationも起動時に仕込んでおけば、POST, PUT もVarnishにくわせることができるんじゃないかと。