ISUCON9予選問題に挑戦する
GW中にちょいちょいISUCON9の予選問題に取り組んでみました。
パフォーマンスチューニングとしてやったことを残しておきたいと思います。
環境
https://github.com/matsuu/vagrant-isucon
こちらを利用してvagrantでstandaloneな構成を構築しました。
実際の予選はマシン3台の構成で、おそらくスペックも異なると思うのですが、今回は1台でできる限りのことをやったという感じになります。
使用した言語はGoになります。
成果物
https://github.com/daleksprinter/isucon9-qualifier-golang
一応自分のリポジトリを貼っておきます。
やったこと(かなり他の人の記事とかを参考にしました。)
1. getTransactionsのN+1潰し
pprofを用いてプロファイリングしたところ、getTransactionsが遅いようでした。
コードを読んでみると、4つくらいN+1が発生しているのがわかりました。
とりあえずItemとCategoryとUserをjoinしておこうと思って、SQLを修正しました。
書いている途中に嫌になりました。
2. Categoryを埋め込む
Categoryの更新が発生しないことに気がつき、メモリにキャッシュしようとしました。
けど解説なんかを読んでいると、コードに埋め込んだ方がいいことに気がつきます。
それに伴い、1の修正をきれいに改善します。
3. UNION句を用いた高速化
getTransactionsのWHERE(`seller_id` = ? OR `buyer_id` = ?)という書き方だと、indexが効かないらしくて、修正しました。
SELECT * FROM items WHERE seller_id = ?
UNION
SELECT * FROM items WHERE buyer_id = ?
というように修正したら良いそうです。
4. Userをインメモリ化
sync.RWMutexを用いてMapを安全に処理します。
それに伴ってまたSQLも修正
参考 https://qiita.com/TsuyoshiUshio@github/items/c3234f3705949d8cf413
5. getTransactoins内の残りのN+1をなくす
6. 外部APIへの通信を並列、非同期処理
これがgetTransaction内の最後の修正です。
一番点が上がった改善でした。
マジでGoの並列処理勉強しないといけないなと思いました。
↑欲しい
7. postBuyの外部APIアクセスも非同期に
8. その他
Index貼ったり、インメモリ化した部分のSQLをしました。
UserやCategoryをインメモリ化したので、全体のSQLも修正しようとしたのですが、
SELECT ~ FOR UPDATEになっているクエリを消して、syncでメモリ参照しようとするとベンチが失敗してしまいました。ロック周りの問題らしいのですが、よくわからず...
結果
最終的に7500点くらいになりました。
学び
・N+1の改善はjoinで結合して持ってくるだけでなくIN句を用いるとわりとすっきりするかも
・WHERE(`seller_id` = ? OR `buyer_id` = ?)はIndexが効かない。(Indexについて要復習)
・並列、非同期処理の勉強が必要
感想
3台構成で続きやりたい。