私が歌川です

@utgwkk が書いている

ISUCON10 予選突破した #isucon

😇😇😇 (:innocent::innocent::innocent:) というチームで、 id:nonylene id:wass80 と参加して、24位でめでたく予選突破できました。学生枠を使わずに予選突破できたのは初めてです。チームメンバーのブログも読んでください。

nonylene.hatenablog.jp

wass80.hateblo.jp

記録に残ってる最終スコアは2204点でした。

やったこと

New Relic導入

まずNew Relicを入れて計測できるようにしよう、というのをやりました。echo向けにはechorelicっていうのがいいのか、と調べて導入しました。

github.com

エンドポイントにかかった時間は計測できたけど、分散トレーシングとか、SQLにかかった時間が取れてない!!! なんでなの!! と言いながらechorelicのコードを読みに行きました。どうもcontextが渡ってない予感がする? echoのミドルウェアを書くのか? と思いつつ echo.WrapMiddleware っていうのを使うとなんか書けそう、ということでガッと試しました。

github.com

最終的にこれでSQLの計測もできるようになりました。これを書いてたときがいちばん輝いてた気がする。

後述するように、 /api/.+/search が重たいけどどういうクエリで重くなるのだろうか、と思ってNew Relicのtrace IDとリクエストの対応をログから取れるようにしたりもしていました。結局分析には使わなかったですが……。

github.com

インデックス貼る

明らかに足りないインデックスがあるよね、ということで貼りまくりました。

椅子におすすめの物件ロジックちょっとよくする

椅子を直方体に見たててドアを通せるか書いてあるけど、短い2辺だけ見ればよいよね、ということになって実装しました。Goでのsortのやり方を調べるより先に、3つのint64のうち小さい2つを返す関数を自前で実装してて野蛮な感じがあります。sort.Ints()を使うと楽に書けるよね、というのを聞いて、たしかに、と思いました。

github.com

うまくインデックスを効かせられないので、せめてスキャンする行数は減らしたい、ということで、EXPLAINを見ながらFORCE INDEXするインデックスを決める、というのもやってました。

github.com

自前アクセスログ解析

/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つにまとめられないか、ということで実装してみましたが、アプリケーションの互換性チェックに落ちたのでマージしませんでした。

github.com

MaxOpenConnsを大きくする

なんか小さいので大きくしたい、という話をしたのですが、大きくしたらFAILするようになったので結局もとに戻しました。

所感

Ruby → Python → Goと利用言語が変化していきました。ついにGoで参加したのですが、コンパイルが通るからひとまずマージできるでしょう、というふうに進められたのはスムーズでよかったです。VSCodeのGo拡張がだいぶ体験がリッチというのもありました。

New Relicを初手で導入して、このエンドポイントが明らかに重いし、分散トレーシングを見るとN+1なので直しましょう、というのがコマンドなしで分析できたのは便利でした。

今回はDBの負荷がどうしても高くなるようなアプリケーションだった、と思っていて、ひととおり自分ができることをこなしたあとは、id:wass80id:nonylene の2人にMySQL複数台構成の実装をやってもらっていて、後半はずっと手があいてました。たとえばもうちょっと実装をちゃんと見て、 priceRangeId とかをなんとかする、とかやったほうがよかったかもしれません。

去年の反省を活かして、VSCode Remoteは重いので手元で開発しましょう、という方向にシフトしたのは、結果的に各自が並列で作業できるようになって効率が上がったのでよかったと思います。

今回のダッシュボードは、ときどき高負荷で見られなくなることがあったとはいえ、体験がかなりよかったと思います。ベンチマークを走らせてると、どんどん点数が上がってる様子が見れるのはおもしろかったです。とてもおもしろい問題だったと思います。SUUMOはときどき引っ越しする予定が無でもブラウジングしていて、なぞって検索機能なんかあった気がする!! と言っていました。

今年こそ優勝したいですね。

追記

旅行っていうタグを間違えてつけたのでアイキャッチ画像がおもしろい感じになりました。