前回書いたように、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への移植
とりあえず、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キャッシュの実装
投票結果画面は、新しい投票があるまでは同じ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の排除
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キャッシュの改善
もはや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でもまだそこそこ伸ばせることがわかってよかったです。