[Dd]enzow(ill)? with DB and Python

DBとか資格とかPythonとかの話をつらつらと

ISHOCON2を意地でPythonで20万点だした

前回書いたように、ISHOCON2に参加してきました。Pythonで9万点で3位ではありましたが、GoやCrystalといったコンパイル言語勢が20万以上出していて悔しいでのPythonで20万点出るように試行錯誤しました。

結果

Flaskはどうやっても9万が限界なので、Sanicに書き直して13満点、さらにRedisを廃止して17万、いろいろ絞り出して20万って感じでぎりぎり届きました・・・

Sanicでの試行錯誤はこんな感じでした。

Sanic Implementation by denzow · Pull Request #1 · denzow/ishocon2-practice · GitHub

Sanic?

SanicはPython 3.5以上で動作するFlaskライクなハイパフォーマンスなWEBフレームワークです。uvloopを利用しており、非同期I/Oを行うことでPythonらしからぬパフォーマンスが出ます。

公式がFlask Likeというだけあり、インターフェースなんかは結構似てますので書き換えはそこまで難しくありませんでした。結構面白いライブラリなので時間を作って今度まとめたいと思っています。

ざっくりうと、Node.jsみたいなアーキテクチャっぽいです。

やったこと

Sanicへの移植

github.com

とりあえず、Flaskでの自分の実装を愚直にSanicに移植しました。Flaskに似ているとはいってもサードパーティとかはそのままとはいかないので、ちょっと試行錯誤もありました。

  • templateエンジンが標準で載ってない
    • jinja2_sanicを追加
  • 2つのRedisDBに接続を保つ方法がわからない
    • sanic_redisは接続先を1つしか持たない設計っぽい
    • aioredisを直接つかって解決

あとはひたすらメソッドにasyncをつけて、非同期I/Oの処理はawait書いて、という感じになりました。

メモってなかったですがこれで9万点が12,3万くらいに伸びてます。ちなみに、Flaskでworkerを8にしてたので同じようにSanicでも8にしていましたが、これは良くなかった気がします。

HTMLキャッシュの実装

github.com

投票結果画面は、新しい投票があるまでは同じHTMLを戻せばいいのでその部分のキャッシュをRedisで実装しました。投票ごとに全キャッシュを破棄して、ページアクセス時にHTMLをRedisにしまう実装です。

また、workerの調整をした結果実は1が一番伸びたのでworker=1で固定しました。スコアはこんな感じ

ubuntu@ip-172-31-23-113:~$ ./benchmark --ip 18.179.197.192 --workload 30
2018/08/26 15:18:51 Start GET /initialize
2018/08/26 15:18:51 期日前投票を開始します
2018/08/26 15:18:52 期日前投票が終了しました
2018/08/26 15:18:52 投票を開始します  Workload: 30
2018/08/26 15:19:37 投票が終了しました
2018/08/26 15:19:37 投票者が結果を確認しています
2018/08/26 15:19:52 投票者の感心がなくなりました
2018/08/26 15:19:52 {"score": 145595, "success": 125147, "failure": 0}

Redisの排除

github.com

Workerが1でいいということは、ローカルのメモリにキャッシュしても問題ないので、Redisではなくグローバル変数をキャッシュとして使う実装にしました。usersだけはメモリに保持するには大きすぎるのでそのままにしています。

ubuntu@ip-172-31-23-113:~$ ./benchmark --ip 54.238.255.213 --workload 60
2018/08/28 14:09:09 Start GET /initialize
2018/08/28 14:09:09 期日前投票を開始します
2018/08/28 14:09:10 期日前投票が終了しました
2018/08/28 14:09:10 投票を開始します  Workload: 60
2018/08/28 14:09:56 投票が終了しました
2018/08/28 14:09:56 投票者が結果を確認しています
2018/08/28 14:10:11 投票者の感心がなくなりました
2018/08/28 14:10:11 {"score": 189211, "success": 168155, "failure": 0}

これで19万近くまで伸びました。このあたりからベンチマーカの負荷を上げるとベンチマーカが異常をきたしてきました。

HTMLキャッシュの改善

github.com

もはや20万に届けるためだけの試行錯誤です。投票ごとにHTMLの全キャッシュを破棄していましたが、投票ページアクセス数というVOTE_COUNTERという変数を設けて、キャッシュのキーに含めるようにしました。これにより、毎回キャッシュをクリアしなくても、投票が1件でも行われればキーが変わるため自動的にキャッシュが参照されなくなります。

このダメ押しのおかげで

ubuntu@ip-172-31-23-113:~$ ./benchmark --ip 54.238.255.213 --workload 75
2018/08/28 14:32:16 Start GET /initialize
2018/08/28 14:32:16 期日前投票を開始します
2018/08/28 14:32:17 期日前投票が終了しました
2018/08/28 14:32:17 投票を開始します  Workload: 75
2018/08/28 14:33:02 投票が終了しました
2018/08/28 14:33:02 投票者が結果を確認しています
2018/08/28 14:33:18 投票者の感心がなくなりました
2018/08/28 14:33:18 {"score": 202920, "success": 181960, "failure": 0}

なんとか20万点までは届きました。

まとめ

Pythonでもどうにか20万までは届きました。Flaskと違ってプロセスを増やさなくてもいいので、結果としてグローバル変数に頼れてスコアが結構伸びました。毎回こんなにグローバル変数使えることもないとは思いますが、とりあえずPythonでもまだそこそこ伸ばせることがわかってよかったです。