ISUCON10の予選で惨敗した話

2020-09-12

ISUCON10の予選に出場しました。

今までずっと一緒に出場してきたチームは解散し会社の同僚と参加しました。 物理で集まりました。同僚同士なのに物理で会うのは初めましてだったよう

今年はチームで3時間くらい準備をした。同僚は初参加だったので自分のISUCON熱を伝えるところから

結果的にはやり残したことはたくさんあるんですが、アプリケーション的にはそれなりにやれたのかと思いました(チームメンバーみんなでアプリケーション書いていた)

https://github.com/yudppp/isucon10-qualifier

やったこと

search遅い問題

featuresカラムがカンマ区切りで入っていて遅かったよう

CREATE TABLE isuumo.chair_features
(
    chair_id    INTEGER         NOT NULL,
    feature_id  INTEGER         NOT NULL
);
CREATE INDEX chair_features_fc ON isuumo.chair_features (feature_id, chair_id);

CREATE TABLE isuumo.estate_features
(
    estate_id   INTEGER         NOT NULL,
    feature_id  INTEGER         NOT NULL
);
CREATE INDEX estate_features_fe ON isuumo.estate_features (feature_id, estate_id);

テーブルを作ってidでsearchがかけれるように変更 自分は既存のDummyのテーブルにALTERをかけるsqlを作るツールを作っていた。 https://github.com/yudppp/isucon10-qualifier/blob/master/tools/create_04sql/main.go

botのBAN対応

nginxでさっとやってもらった

https://github.com/yudppp/isucon10-qualifier/pull/2/files

if ( $http_user_agent ~ /crawler \(https:\/\/isucon\.invalid\/(support\/faq\/|help\/jp\/)/ ) {
        return 503;
}

だけうまくいかなかったのでそこだけけして放置した。

椅子がドアを通るかのやつ

椅子から家をオススメする際にドアが通るものをオススメしているようだった。

query = `SELECT * FROM estate WHERE (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) ORDER BY popularity DESC, id ASC LIMIT ?`
err = db.SelectContext(ctx, &estates, query, w, h, w, d, h, w, h, d, d, w, d, h, Limit)

椅子がドアを通るかなので(Not三平方)なので一番大きい長さは無視し狭いものを元に通れば良いので

query = `SELECT * FROM estate WHERE (door_width >= ? AND door_height >= ?) OR (door_width >= ? AND door_height >= ?) ORDER BY popularity DESC, id ASC LIMIT ?`
err = db.SelectContext(ctx, &estates, query, size[0], size[1], size[1], size[0], Limit)

とシンプルになった。

https://github.com/yudppp/isucon10-qualifier/commit/efcbd57916821cc6935bd9dc2d3b842acb350535

nazotte問題

nazotteが重かった。色々調べてみるとMySQLにGEOMETRYという型があることを発見。これと別に空間Indexを貼ればIndex効くかもみたいな触れ込みをカラムを追加して試してみた。

ALTER TABLE isuumo.estate ADD geo GEOMETRY;
UPDATE isuumo.estate SET estate.`geo` = GeomFromText(Concat('POINT(',estate.latitude,' ',estate.longitude,')'));
ALTER TABLE isuumo.estate MODIFY geo GEOMETRY NOT NULL;
CREATE SPATIAL INDEX estate_door ON isuumo.estate (geo);

アプリもよしなに変えた。

low priceのcache

low priceがあまり変更がかからないのに重いqueryを叩いていた。 low priceの結果をオンメモリに持たせることによって解決

https://github.com/yudppp/isucon10-qualifier/pull/13/files

本当はlow priceの中で一番高い値段よりも安いアイテムを買った時だけinvalidationするようにしたかったが時間がなかった

nazotte N+1問題

GEOMETRY使って満足していたがN+1のままだった。

query = fmt.Sprintf(
	`SELECT * FROM estate WHERE id IN (%s) AND ST_Contains(ST_PolygonFromText(%s), geo) ORDER BY popularity DESC, id ASC LIMIT 50`,
	strings.TrimSuffix(strings.Repeat("?,", len(estateIDs)), ","),
	coordinates.coordinatesToText(),
)
err = db.SelectContext(ctx, &estatesInPolygon, query, estateIDs...)

https://github.com/yudppp/isucon10-qualifier/commit/c1e0c2a0c20f509f4257f836194916eb02b2d810

RIP FOR UPDATE

for updateが重いのが明らかになってきたのでsync.Mutexで実装。 効果がありそうだったので途中からMultiでできるように変更

https://github.com/yudppp/isucon10-qualifier/commit/676dcc4d837e540414e84fb48c1142b60bd1ce1e

https://github.com/yudppp/isucon10-qualifier/pull/27/files

感想

すごく楽しかったし、みんなでアプリケーションさわれて良かった。割とアプリケーションは自分のできる限りの力を振り絞ってできた。

心残りとしてはベンチマーカーが15:58~19:33までの3時間半くらいの間でエラーログのFailedのスコア0点が続き何が悪いのかわからず、ベンチマーカーがちゃんと動いていないのかなーとひたすら思っていたが、多分何かしらでアプリケーションの修正で些細なバグを埋め込んでしまっていたようでした。

isucon_10_0

あとは本当に全然点数が伸びなかったがどこがボトルネックなのか検証する時間が取れずタイムアップになってしまった。

NginxやMySQLの設定ファイルなどがほぼ変更されないままだったので、そのあたりで大きなブレイクポイントがあったのでは思った。

また今回初めてNewRelicを導入したがどのエンドポイントが遅いのかがさっとわかってよかった。提供ありがとうございました。

来年もあるようであれば必ずリベンジします。