😇😇😇 (:innocent::innocent::innocent:
) というチームで、 id:nonylene id:wass80 と参加して、24位でめでたく予選突破できました。学生枠を使わずに予選突破できたのは初めてです。チームメンバーのブログも読んでください。
記録に残ってる最終スコアは2204点でした。
やったこと
New Relic導入
まずNew Relicを入れて計測できるようにしよう、というのをやりました。echo向けにはechorelicっていうのがいいのか、と調べて導入しました。
エンドポイントにかかった時間は計測できたけど、分散トレーシングとか、SQLにかかった時間が取れてない!!! なんでなの!! と言いながらechorelicのコードを読みに行きました。どうもcontextが渡ってない予感がする? echoのミドルウェアを書くのか? と思いつつ echo.WrapMiddleware
っていうのを使うとなんか書けそう、ということでガッと試しました。
最終的にこれでSQLの計測もできるようになりました。これを書いてたときがいちばん輝いてた気がする。
後述するように、 /api/.+/search
が重たいけどどういうクエリで重くなるのだろうか、と思ってNew Relicのtrace IDとリクエストの対応をログから取れるようにしたりもしていました。結局分析には使わなかったですが……。
インデックス貼る
明らかに足りないインデックスがあるよね、ということで貼りまくりました。
- インデックス2つ追加 · innocent-team/isucon10q@cb15bb7 · GitHub
- CREATE INDEX estate_latitude_longitude ON isuumo.estate(latitude, lon… · innocent-team/isucon10q@1a48169 · GitHub
- CREATE INDEX estate_door_width_door_height · innocent-team/isucon10q@c38ad11 · GitHub
- /api/{chair,estate}/low_priced に効くインデックス · innocent-team/isucon10q@06be78f · GitHub
椅子におすすめの物件ロジックちょっとよくする
椅子を直方体に見たててドアを通せるか書いてあるけど、短い2辺だけ見ればよいよね、ということになって実装しました。Goでのsortのやり方を調べるより先に、3つのint64のうち小さい2つを返す関数を自前で実装してて野蛮な感じがあります。sort.Ints()
を使うと楽に書けるよね、というのを聞いて、たしかに、と思いました。
うまくインデックスを効かせられないので、せめてスキャンする行数は減らしたい、ということで、EXPLAINを見ながらFORCE INDEXするインデックスを決める、というのもやってました。
自前アクセスログ解析
/api/.+/search
が重いのでインデックスを貼りたいけどなんかめっちゃクエリあるね、どうしようかな、と id:wass80 と会話してて、searchはGETリクエストなのでクエリパラメータの種類でどんな検索が多いか傾向がつかめそう、という話をしました。分析スクリプトを即興で用意して回した結果、大半のリクエストはクエリ1個なのでひとまずそこにインデックスを貼るとよさそう?? とか、 features
はLIKE検索やばそうだけどそんなにリクエストされてないから後でいいか、という判断ができたのはよかったです。
use strict; use warnings; use feature 'say'; use URI; while (my $line = <STDIN>) { my ($req) = $line =~ qr{"GET (.+) HTTP/1\.1"}; my $uri = URI->new("http://example.com/$req"); my %query = $uri->query_form; my @keys = grep { $_ ne 'page' && $_ ne 'perPage' } sort keys %query; say join ',', @keys; }
perl nukidasu.pl < search.access.log | sort | uniq -c | sort -n
のようにして使うとよい情報が得られました。
(snip) 11 color,priceRangeId 11 heightRangeId,priceRangeId 17 features 441 widthRangeId 449 color 491 kind 521 depthRangeId 1027 heightRangeId 1461 priceRangeId
あとになって、alpとかkataribeとかを使うとこういう解析はすぐできるのかなと思いつつも、小さなスクリプトで大きな効果が得られたな、と思います。
やったけど導入しなかったこと
/api/.+/search
のSELECTを1つにする
SELECT COUNT(*)
したあとに同じ条件でSELECTしてるのを1つにまとめられないか、ということで実装してみましたが、アプリケーションの互換性チェックに落ちたのでマージしませんでした。
MaxOpenConnsを大きくする
なんか小さいので大きくしたい、という話をしたのですが、大きくしたらFAILするようになったので結局もとに戻しました。
- db.SetMaxOpenConns(50) · innocent-team/isucon10q@84526bf · GitHub
- db.SetMaxOpenConns(10) · innocent-team/isucon10q@5b20ad2 · GitHub
所感
Ruby → Python → Goと利用言語が変化していきました。ついにGoで参加したのですが、コンパイルが通るからひとまずマージできるでしょう、というふうに進められたのはスムーズでよかったです。VSCodeのGo拡張がだいぶ体験がリッチというのもありました。
New Relicを初手で導入して、このエンドポイントが明らかに重いし、分散トレーシングを見るとN+1なので直しましょう、というのがコマンドなしで分析できたのは便利でした。
今回はDBの負荷がどうしても高くなるようなアプリケーションだった、と思っていて、ひととおり自分ができることをこなしたあとは、id:wass80 と id:nonylene の2人にMySQL複数台構成の実装をやってもらっていて、後半はずっと手があいてました。たとえばもうちょっと実装をちゃんと見て、 priceRangeId
とかをなんとかする、とかやったほうがよかったかもしれません。
去年の反省を活かして、VSCode Remoteは重いので手元で開発しましょう、という方向にシフトしたのは、結果的に各自が並列で作業できるようになって効率が上がったのでよかったと思います。
今回のダッシュボードは、ときどき高負荷で見られなくなることがあったとはいえ、体験がかなりよかったと思います。ベンチマークを走らせてると、どんどん点数が上がってる様子が見れるのはおもしろかったです。とてもおもしろい問題だったと思います。SUUMOはときどき引っ越しする予定が無でもブラウジングしていて、なぞって検索機能なんかあった気がする!! と言っていました。
今年こそ優勝したいですね。
今年は言い忘れてた。百万円ドリブン倒す https://t.co/DDSYK32Lgb
— うたがわきき (@utgwkk) 2020年9月13日
追記
旅行っていうタグを間違えてつけたのでアイキャッチ画像がおもしろい感じになりました。