開発^3

Web開発、宇宙開発、ゲーム開発の3種類についてつらつらと

ISUCON9 予選

ISUCON9お疲れ様でした。

去年に引き続き「RE: ゼロから始めるISUCON」として1人チームで参加して来ましたが、無事16位に入ることができ、来月の本選に出場できることになりました。 3回目の挑戦で初本選ですね。ばんざーい ∩( ・ω・)∩

予選の感想

出題がSAKURA internetさんと、mercariさんということで、椅子を販売するサイトisucariでした。
ベンチマーク」が「ベンチ」ジャンルに入っていたり、時間に追われながらもくすりとさせてもらいました。

ISUCON的にはRDBボトルネックから始まるものの、割とすぐに外部APIボトルネックが移った印象。 結局API呼び出しをある程度減らしつつ、とにかくWorkerを増やすことでAPI結果待ちの間に他Workerが処理するようにしてスコアを稼いでいました。
感想部屋見ているといろいろ足りていなそうなので、怖いながらも講評が楽しみ。

使ったもの

  • Python実装(Python本選組はうちだけだったらしい)
  • kataribe
  • pt-query-digest
  • netdata
  • 個人契約VPS上のtmux
  • 使い慣れたdotfiles

最終的な構成

1台目:

  • nginx(load balancer, /uploadの画像配信) 1
  • gunicorn (weight: 8)

2台目:

  • gunicorn (weight: 10)

3台目:

  • MySQL
  • gunicorn (weight: 5)

やったこと

オーソドックスに計測・一番重い場所を改良の繰り返し。

items.root_category_idを追加

重いクエリの改善

categoriesテーブルをオンメモリに

親カテゴリ名まで含めて辞書定義

index追加

Slowクエリを元として、主にitemsにindexを追加

外部APIを出来るだけ呼ばずに済むように

この辺りで目立つSlowクエリが無くなって、API呼び出しが支配的に。
正直、これが正しかったのかはわからないですが、netdataで見ているとCPU使用率が0%と100%を行き来していたのでAPIのレスポンス待ちでworkerを使い切っているのかなー、と。
この時はcompaign上げてなかったので、単純にベンチマーカーからリクエストが来ていなかったのかもしれません。

APIレスポンスの内容をJSONに含めて返す必要があったため、遅延処理もできず、workerをがっつり増やして対応。
改めて考えると裏でポーリングしておく手もあったかも?でもwait_shipping以降の物が増えると破綻しそうだなぁ。

APIドキュメント読む→QRコードの実装に迷い込む

APPLICATION_SPEC.md, EXTERNAL_SERVICE_SPEC.mdを読んで、Shipping statusがisucari側である程度把握できる(=APIレスポンスする必要が無いタイミングがある)ことを知る。
/acceptのタイミングがわかれば、大部分はわかるはず。でもこのURLは外部サービスのものだし……はっ!荷物を引き渡すときのQRコードを差し替えて、isucariを経由させれば……と実装。
1.5時間ぐらいかけて実装してみたものの、QRコードのvalidationに引っ掛かってしまってgit stash orz

確かにレスポンスの変更にあたるからルール的に変更不可か……

3台構成に

アプリ側がうまく行かなくて凹んだのでインフラ側へ。
RDB負荷はある程度減らせていたようなので、DBサーバーにもgunicorn入れて数で勝負。 画像ファイルの配置がバラけてしまったのでNFSで無理やり共有。2

/upload以下はnginxで提供。

結果

Job一覧を見ていると、こんな感じか。

  • 13:45まで:色々手は入れているものの初期スコアのまま変化なし
  • 14:00ぐらい: 3000点に上がって喜ぶ
  • 14:20ぐらい: API関連弄って6000点
  • 14:30ぐらい: worker数増やして9560点、一瞬だけ1位に
  • 16:00ぐらい: QRコード系をあきらめてインフラ側へ
  • 18:00ぐらい: ガチャが安定していたので数回走らせて12,960で終わり

最終スコアは12,960、最高スコアは13,560でした。

実はcampaignを上げるタイミングを逃し、後半で上げてみたら外部API呼び出しがエラりまくったので0のままです……(小声)


  1. 後半でワタワタしていたのもあって、デフォルトで/uploadが読めた理由がよくわかっていなかった。見直してみたらFlaskのstatic_folderで提供していたのか。

  2. /sellだけ1サーバーに寄せれば良かったかな、と終わったちょい後に気付いたり。