orangain flavor

じっくりコトコト煮込んだみかん2。知らないことを知りたい。

#isucon 2014予選の延長戦をやってみた

予選の時間内では足りてないことばかりだったので、もう少し試行錯誤することにしました。

#isucon 2014の予選をほぼ一人で戦うハメになった話 - orangain flavor

目標は50000点、できれば60000点出したい。

予選終了時

  • Python実装
  • DBはRedisのみを使う
  • Cookieがないときだけnginxで静的ファイルを返す
  • Gunicornを使ったマルチプロセスモデル
  • ワーカー数10、ワークロード10

最終提出スコア: 32710

細々とした改良

  • nginxの設定を追加。
  • redis-pyのパーサーをhiredisに置き換え。
  • テンプレートエンジンを使わないよう変更。
  • アプリを見なおして、RedisのRead/Write数を削減。

スコアはあまり上がらず 32912

Gunicornのワーカーをmeinheldに置き換え

前回のエントリのコメントで id:methane 氏に教えていただいたmeinheldに置き換え、nginx―Gunicorn間でKeepAliveを有効にしました。PyPI最新の0.5.6です。

スコアは5000ほど上がって、 37223

副作用として、nginxでCookieがないときだけ静的なindex.htmlを返すことができなくなりました。

nginxでの良い設定方法がわからず、こんな感じで無理矢理やっていたのですが、meinheldを使うと400 Bad Requestが返ってくるようになりました。

    location = / {
      if ($http_cookie !~* "session") {
        root /home/isucon/webapp/public/;
        rewrite ^.* index.html break;
      }
      proxy_pass http://app;
      proxy_set_header Host $http_host;
      proxy_http_version 1.1;
      proxy_set_header Connection "";
    }
    location / {
      proxy_pass http://app;
      proxy_set_header Host $host;
      proxy_http_version 1.1;
      proxy_set_header Connection "";
    }

原因不明ですが、Varnishを使うともっと簡単に設定できるようになると聞いていたので、この時点では深追いしませんでした。

Varnish導入

Varnishを導入しました。yumで4.0.xをインストールしたつもりが、Amazon Linuxだと3.0.xしかインストールできずしばらくハマりました。

また、daemonの起動に失敗してもどこにもログが吐かれないのでだいぶ悩みました。 varnishd -C -f FILEvclを検証すれば良いことを知りました。

バックエンドとしてUnix Domain Socketに対応していないようなので、元の通りGunicornをTCP 8080で動かすようにしました。

静的ファイルはnginx、アプリはgunicornと振り分けるよう設定しました。 

スコアはさほど変わらず 37632

エラーページを静的に返すように

Varnishで簡単にキャッシュできるようになったので、ログイン失敗時に /?err=*** のページにリダイレクトするようにしました。

これは結構効いて、スコアは 43715 に。Varnish便利。

データをインメモリに持つように

ユーザー情報とBAN/Lockの情報をメモリに保持するようにしました。マルチプロセスなので、結局Redisにもアクセスする必要があり、苦労した割に効果は薄かったです。

スコアは上がらず、というかむしろ下がり 42533

FlaskからBottleに書き換え

プロファイリング結果を見るとFlaskで時間を食っていたので、さらにmicroなフレームワーク Bottle に置き換えました。

これは結構効果があり、スコアは 50386

Sessionを暗号化されていないCookieに置き換え

正直どうなのかという話ですが、Session Cookieの暗号化と検証を捨て、ユーザー名と最終ログイン情報をCookieに直接格納しました。

このとき、Cookieの値に空白が含まれるとベンチマーカーが意図したとおりに返してくれない問題に若干苦しみました。

スコアは若干上がり 51076

RedisからのReadをパイプライン化

attempt_loginの条件分岐の中で、細切れでRedisからReadしていたのを、パイプラインで一括取得するようにしました。

これによりRedisへのアクセスは、1リクエストあたり最大Read1回、Write1回の計2回で済むようになりました。

スコア: 52705

1回休み

Redisの設定を見ると、いつの間にかRedisのappendonlyが無効になっており、永続化されてない状態になっていました。

有効にしてスコアを測り直すと 51246 でした。

PyPyにしてみる

PyPyを使ったら速くなったりしないかなーと思って試してみますが、スコアは 46607 に下がったので却下。

PyPyに単純に置き換えただけで、標準以外のライブラリも普通に動くのだなと感動しました。

2014-10-05 23:34追記: 後ほどちゃんとPyPyに合わせてチューニングしたらCPythonよりも高いスコアがでました。詳しくは文末の追記を参照。

Varnishのチューニング

VarnishのCPU利用率が高いのでチューニングしてみました。

  • VARNISH_MIN_THREADS=200 → 50
  • -p thread_pools=4
  • -p session_linger=100

いまいちよくわからないもののスコアは若干上がり 53016

最新のmeinheldに置き換え

GitHubの最新版を使うと良いと教わったので、置き換えてみました。

スコアは若干上がり、 53872

まとめ

最終的なスコアは 53872 となりました。これ以上はどうすればスコアが上がるか思いつきません。

とりあえずの目標としてた50000は超えましたが、60000には届きませんでした。Pythonのマルチプロセスモデルだとこの辺が限界なんでしょうか。。

ちなみにワークロードやGunicornのワーカー数はいじってもスコアが上がらなかったので、変えてません。 

ソースコードと設定はGitHubで公開しています。

クーポンのおかげで、あまりお金をかけずに勉強できました。AWS様ありがとうございます。

f:id:mi_kattun:20141005123906p:plain

参考サイト

おまけ

GOGC=offでベンチマーク実行

$ GOGC=off ~/benchmarker bench --workload 10

スコア: 79324

静的ファイルを返さないように

<link>タグと<img>タグをdocument.writeで書きだすようにしました。

$ GOGC=off ~/benchmarker bench --workload 10

スコア: 180084

orangain/isucon2014-qualifier-python at no-static

2014-10-05 23:34追記:PyPyをちゃんと導入してみた

例によって id:methane 氏に教えて頂きました。ほんとうに感謝です。

PyPyへの最適化

Tornado 3.2をインストールし、GunicornのワーカーをTornadoWorkerに置き換えました。またC拡張であるhiredisもアンインストールしました。

さらにinitスクリプトの中で、JITのウォームアップとして25秒間ベンチマークを走らせました。

ベンチマーク実行中は、GunicornのCPU消費がCPythonに比べて格段に減り、ワーカー数を減らすほどスコアが上がるようになりました。ワーカー数1、ワークロード14で 54736 まで上がりました。

1プロセスへの最適化

1プロセスを前提とすれば、メモリにRedisと同じ内容を保持し、RedisへのReadアクセスを完全に無くすことができます。Readしないように書き換えたところ、同じくワーカー数1、ワークロード14で 55955 まで上がりました。

orangain/isucon2014-qualifier-python at pypy

PyPyを使ってみて

PyPyは今回初めてまともに使いましたが、サードパーティのライブラリを含めてエラーなどは全く無く、単純に置き換えただけ*1で正常に動作し、正直驚きました。

Amazon Linuxへのインストールこそ非公式配布のバイナリを使いましたが、非常に安定している印象を受けました*2

PyPyを使えば、Pythonでも1プロセスで高速に処理するGo言語のような戦い方ができることを確認できて良かったです。それでも来年はGoで出られる程度にはGo言語力を高めたいですが。

*1:もちろんサードパーティライブラリのインストールは必要ですが。

*2:ちなみに使用したPyPyのバージョンはCPython 2.7互換のPyPy 2.4です。