サバフェスに参加してきました。

サバフェスに参加して1週間でやった事などをまとめて報告させて頂きます。
いきなりですが、レギュレーションについていくつか質問があったのですが、
質問受付期間中に聞きそびれるというミスをしたので後半はかなり自由な解釈のもと、
作業をしました。
途中のスコア等は全然残してなかったので書いてません。

やったこと

月曜日

寝てましたね。

火曜日

寝てました。

水曜日

やっと頂いたpdfを読んでインスタンスを起動させました。
1台起動させて残り4台の起動は一緒にやってたメンバーに任せて寝ました。

木曜日

nginx(openresty-1.4.3.3) + php-fpm(5.5.6) + MySQL5.6で環境構築
nginxのproxy_cacheで頑張ればって思ってたので上記の環境にしました。
ページが見れるようになったところで寝ました。

金曜日

とりあえず分散させるために、全台にnginx + php-fpmをインストールして、
1台だけ前段にproxyの設定をいれたサーバを用意しました。
ちなみにphp5.5.6はapcがないのでZend OPcacheを使用。
nginxのproxy_cacheで頑張ればって思いでしたがあまりスコアがでず、
とりあえず疲れたので寝ました。

土曜日

apcとの性能を比べるために、php5.4.22をインストールるして切り替えてみる。
Zend OPcacheの方が少し早かったのでphp5.5.6に戻す。
ngx_pagespeedも試してみましたがあまり結果が変わらず。

この時点で優勝する為にはもっと工夫しないと思い試行錯誤を始めます。
勝手な解釈のもとproxyサーバとしてならhaproxyやvarnishを使ってもいいのでは?
とか思いこみ、haproxyを入れる。スコアの伸びがいまいちだったので、
やっぱりvarnishにする。

varnishにした時点で5位以内にはいったりするようになったのですが、
やっぱりやるからには優勝したい!!!って事でここでとっておきの秘密兵器投入
postのスコアの方がgetよりも高かったので、postで好成績を出せば、
勝てるって思いました。

nginx + lua + MySQLで、wp-comments-post.phpを実装。
(コメントの投稿以外はできませんし、コメント投稿についても動作の保証はしませんので使う方は、
ご自身の責任のもとお願いします。)
レギュレーション的にnginxで実装するぶんにはグレーなはずっ!
というかどれくらい早くなるか試してみたかったんです。
この為に、nginxをコンパイルして入れてました。

夜中に実装が終わって朝のベンチを待とうと思って寝ました。

日曜日

朝起きたら、最終兵器を投入したはずがpostが0件になってたので絶望。
とりあえず何故か調べるためにベンチでポストされたデータをみてたら、
1度のベンチで100万件を超えるデータは入っており、wpのコメントのページが
タイムアウトしてました。

100万件も処理しなくていいので、処理を減らす方向に。

5台つかってサーバを3台に。
1台目はnginx + php-fpm
2台目はmemcache
3台目はmysql

これでも80万件ちかくpostされてて、タイムアウト。
ってことでmemcacheを使ってpostされた数をカウントして、
mysqlに入るデータを抑制するように修正。
(いくつかバグがあったのでそれを合わせて修正しました。)
タイムアウトをのばしたり、phpのmemory_limitを-1にしたりして、
出来るだけタイムアウトしない設定にして、自前でベンチをはしらせてたところ、
7万件くらいまでなら処理できる事がわかったのでとりあえず最後のベンチ前に
5万7千くらいに閾値を設定してました。
そしてついに最後のベンチで、 暫定1位に。

f:id:yokoninaritai:20131125183606p:plain

あとは寝る前に、cacheのexpireの時間を10分→5分にして閾値を6万5千くらいにしました。
5分にしたのは最後に走ったベンチマークのpostの処理が5分以内で終わってので、
本番の測定際もちゃんと動くだろうと想定して変更しました。
ちゃんと想定通りの動作をしていればいいのですが。

ちなみに、luaの処理の部分は下記のような形で実装しました。
(HOST,USER,PASSWORDについてはblogを書くにあたって変更しました。)

        location /wp-comments-post.php {
            content_by_lua '
                ngx.req.read_body()
                local args    = ngx.req.get_post_args()
                local id      = args["comment_post_ID"]
                local author  = ngx.quote_sql_str(args.author)
                local email   = ngx.quote_sql_str(args.email)
                local url     = ngx.quote_sql_str(args.url)
                local comment = ngx.quote_sql_str(args.comment)
                local agent   = ngx.quote_sql_str(ngx.req.get_headers()["User-Agent"])

                local memcached = require "resty.memcached"
                local memc, err = memcached:new()
                if not memc then
                    ngx.log(ngx.ERR,"failed to instantiate memc: ", err)
                    ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
                    return
                end

                memc:set_timeout(1000) -- 0.1 sec
                local ok, err = memc:connect("<MEMD_HOST>", "11211")
                if not ok then
                    ngx.log(ngx.ERR, "failed to connect: ", err)
                    ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
                    return
                end

                local max_id = 56591
                local rid, err = memc:incr("count",1)
                if not rid then
                   rid, err = memc:set("count", 1, 300)
                   -- ngx.log(ngx.ERR,":",rid,":",max_id,":",err)
                end
                -- ngx.log(ngx.ERR,":",rid,":",max_id)

                if tonumber(rid) > tonumber(max_id) then
                    return ngx.redirect("http://"..ngx.var.host.."/?p="..id.."#comment-1", ngx.HTTP_MOVED_TEMPORARILY)
                end
                memc:set_keepalive(0, 100)

                local mysql = require "resty.mysql"
                local db, err = mysql:new()
                local ok, err, errno, sqlstate = db:connect{
                    host = "<DB_HOST>",
                    port = 3306,
                    database = "wordpress",
                    user = "<USER>",
                    password = "<PASSWOED>"
                }

                local res, err, errno, sqlstate = db:query(
                    "INSERT INTO wp_comments (comment_post_ID,comment_author,comment_author_email,comment_author_url,comment_content,comment_date,comment_date_gmt,comment_agent) VALUES ("..id..","..author..","..email..","..url..","..comment..",NOW(),UTC_TIMESTAMP(),"..agent..")"
                )
                -- ngx.say(res.affected_rows, " rows inserted into table cats ","(last insert id: ", res.insert_id, ")")

                local result, err, errno, sqlstate = db:query("SELECT comment_count FROM wp_posts WHERE ID = "..id.."")
                for idx, records in pairs(result) do
                    for key, record in pairs(records) do
                        -- ngx.log(ngx.ERR, record)
                        record = record + 1
                        -- ngx.log(ngx.ERR, record)
                        local bytesend, err = db:send_query("UPDATE wp_posts SET comment_count="..record.." WHERE ID = "..id.."")
                        -- ngx.log(ngx.ERR, bytesend,":",rid,":",id,":",err)
                    end
                end
                local ok, err = db:set_keepalive(0, 100)

                ngx.redirect("http://"..ngx.var.host.."/?p="..id.."#comment-"..res.insert_id.."", ngx.HTTP_MOVED_TEMPORARILY)
            ';
        }

まとめと反省

後で思ったのですが最初の認識として、クラウドでkeepalivedが使えないという勝手な思い込みにより、lvsを使わなかったのは本当に反省です。
プロファイリングや、可視化する対応をいれた方がもっとボトルネックがはっきりしたのではないかと思います。
phpは遅い。(postのハイスコアはすべてluaのおかげです。)
今回初めてlua入門しましたが、これだけ早ければ別の機会でも使えそうだなと思いました。
かなり自由にやりましたので楽しかったです。
ちなみに一緒にやってたメンバーはなにしてたかと言うと、ここではあまり言えませんが何もしてません。

最後に書いておきますが今回のnginxのレギュレーションに違反する可能性は考えました。
禁止事項に「 WordPressショートカット 」というのがあった為です。
最初にも書いた通り最初にきちんと質問してできればよかったと思っております。

今回のような機会を作って頂いたidcフロンティアの方々ありがとうございました。
運営の方々、参加されたみなさま、お疲れさまでした!