Runner in the High

技術のことをかくこころみ

isucon10に出て予選敗退した

iguchi1124hayashikunとisucon10に出た。一番伸びたスコアは1300くらい。

f:id:IzumiSy:20200913175854p:plain

やったこと

覚えていることベースでやったことを書いてみる。

自分が覚えているのは基本アプリケーションサイドの変更ばっかり。

マシン構成の変更

hayashikunとiguchi1124がAP2台+DB1台の構成に変更した。

index張り

使われてそうなとこにいくつか張った

create index char_stock on isuumo.chair (stock);
create index estate_latitude_longitude on isuumo.estate (latitude, longitude);
create index estate_height_width on isuumo.estate (door_height, door_width);
create index estate_rent on isuumo.estate (rent);

nginxでクライアントキャッシュ

hayashikunが椅子と物件の詳細情報をクライアントサイドキャッシュするようにした。効果は不明。

よく考えたら椅子は在庫数みたいなデータがあるのでキャッシュさせたらまずそうだがスコアには影響はなかった。

なぞって検索の処理をAPサーバでやらせる

ポリゴンの内外判定系をSQLでN+1してやっているということがわかったのでpolyclip-goというライブラリを使ってST系関数の呼び出し部分をアプリケーションサイドに寄せた。

スコアは10くらい伸びた気がする。

LowPriced系のオンメモリキャッシュ

APサーバで結果をキャッシュするようにした。これはめちゃくちゃ効いて400くらい伸びた。

DBのアプリケーションの最大コネクション数を変更

10→50にしてみた。効果があったのかは不明。

botリクエストの排除

UAを見てbotだったら503返すようにするあれ。効果は不明。

ランキング処理

任意の椅子にあう部屋を取得するエンドポイントで使われているSQLがorを5つ使っているのでそのぶぶんのorをひとつに。

椅子の縦横奥行きのうち最長辺を除いた二辺でドアの縦横幅と比較すればorをひとつにできた。

   var estates []Estate
    w := chair.Width
    h := chair.Height
    d := chair.Depth

    // 短い短辺ふたつをとりだす
    lengths := []int64{w, h, d}
    sort.Slice(lengths, func(i, j int) bool { return lengths[i] < lengths[j] })
    len1 := lengths[0]
    len2 := lengths[1]

    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.Select(&estates, query, len1, len2, len2, len1, Limit)

featureテーブル

これはやろうとしてできなかった。

LIKEを発行してchairとestateのfeaturesカラムを舐めているクエリがいくつかあったので、それに目星をつけてchairとestate用のfeatureテーブルを別で用意してみることに。これがうまくいけば検索クエリが改善するので購入ボリュームが底上げされると思っていた。

しかし、実際にはLIKEを発行するようなクエリは全体でみるとほんの数パーくらいしか占めておらず、改善する意味はほとんどがほとんどないということが終わってから分かる。また、意味がない割にfeatureテーブルを作ろうとするとめちゃくちゃ難しく、後半はここで時間をかなり使ってしまった。

思ったこと

用意されているDBテーブルがふたつだけ、そしてアプリケーション実装コードも短く、いままで以上にシンプルな構成のアプリケーション、という感じだった。

ベンチを動かしてみると一番負荷が集中するのは結局DBで、いかにキャッシュを効かせてDBへのアクセスを減らすか、いかにDB設計を考えて負荷を分割するか、という部分がポイントだったのかなという印象。しかし我々のチームはDBの分割などの戦略までは到達できなかったので、上位のチームがどのようなDB構成の戦略をとっていたのかが気になる。

また、今回のアプリケーションisuumoの特徴として

  • レコードの入稿がCSVを用いたバルクで行われる&頻度少なめ
  • 物件テーブルには更新がない(ユーザーの行動も資料請求で終わる)
  • 検索クエリが極めて複雑

など、実際のsuumoのようなプロダクトにものすごく近い状況が再現されており、おそらくベンチマーカーの挙動も 検索→商品詳細閲覧→資料請求or購入 という流れをたどるように作られていたように思える。つまり、パーチェスファネルのようにまずは検索部分のボリュームを稼がないとスコアに直接響く資料請求や購入につながらない、という感じ。

なので、早い段階で検索より先の部分(商品詳細とか購入とか)を改善しても、結局検索部分で詰まってトラフィックが増えないため結局スコアが伸びなかったのは極めて自然に思える。実際、トップページの商品表示で使われているLowPriced系APIをキャッシュした結果スコアがかなり伸びたりもしたので、アプリケーションの仕様からユーザーストーリー的な部分を把握して改善優先度を決定する、というのがISUCONで最も必要な考え方なんだろうなと感じた。

前回だか前々回だかのisucariでは「購入されやすい椅子」や「購入が多いユーザー」などのように、ベンチによるユーザーごとに特性を再現するギミックが仕込まれていたので今回も購買傾向を分析したりしたいと考えていたが、購入者の情報がUA程度でしか識別できないなどの理由がありやらできなかった。が、もしかしたらそこにもヒントがあったかもしれない。

isuconのアプリケーションチューニングにおいて優先度を決定するには、やはりまずアプリケーションの仕様や傾向を把握することから始めないといけない。gistの資料を読んだり、ログをとったり実装を読んだり。これは仕事でソフトウェア開発をするうえでも同じだし、こういう部分を無視してスコアが稼げないように問題設計がされているのはすごくおもしろい。