こんばんは、kyontanです。
前回再起動試験で落ちたのでチーム「再起動非対応」で参加しました。ベストスコアは4410点でした。再起動試験までたどり着けなかった……悔しい……
チーム「再起動非対応」は同期3人 (自分 @kyontan, @hogashi, @h-otter) での参加でした。学生は2人いたはずですがエントリーをミスったので学生は1人ということになっています。
使用した言語
私はRubyが一番得意だと思っていますが、非同期処理が書きやすいとか型が着いていると嬉しい、みたいなことはあり今回もGo言語です。
でも今回はN+1潰すときの型マッピングで無駄に時間を溶かしてしまったので、Rubyの方が良かったかもしれない……つらい……
開始前の準備
Prometheus/Grafana等のサーバの設定を @h-otter にお願いしました。前回のコードを読み返しつつ、何をしたかを思い出すなどした。
時間がなくて完全に忘れてしまいましたが、せめて練習はやっておくべきでしたね…
あと、 Alibaba Cloud の UI が色々不思議な感じで良かったですね。
本番
完全に正解していますが、今回はマイクロサービス問だということに気が付いたのは開始してからだいぶ経ってからでした。
開始30分前、チームメイトとの雑談「今日は何がでるかなー」「フリマアプリかオークションかそういうやつでしょ」「マイクロサービスとかね」「わかる〜」 #isucon
— kyontan@新刊はまだ (@sukukyon) September 8, 2019
今回できたことが異常に少ない気がする。kataribeのログを眺めつつ、チームでやったことは以下のことです。比較的時系列だけどそうでない部分もあるかも。
- 開始スコアは 2310
- 10:30 メモリ多いな〜って思っていたら2, 3台目のインスタンスタイプを間違えていたことに気がつく。インスタンス立て直し
- コードは既にリポジトリへ追加していたのでチームメイトはルール/コードリーディングを進める
- 10:50ごろ: 作業開始
- 11:30ごろ?: MySQL 5.7 を 8.0 へ (@h-otter)
- スコア変化なし
- 11:45 DBにインデックスを張る(@kyontan)
- 多分間違えていて、これらにインデックスを貼りましたがあまり効いていなかったようにみえました
- BigQuery の触りすぎでRDBのインデックスの挙動をほぼ忘れたのも敗北ポイント
config (name) items (created_at, id) items (id) items (buyer_id) items (seller_id) shippings (transaction_evidence_id) transaction_evidences (id) transaction_evidences (item_id) users (account_name) users (id)
- 12:20: カテゴリをオンメモリへ (@hogas, @h-otter)
- 2510点ぐらい
- 12:30: http2 対応, 静的ファイルをキャッシュ, アップロードされた画像を nginx で返すなど
- スコア変化なし
- 13:30: config もキャッシュするように
- 14:00: 新着商品には発売中の商品だけを返すようにする (@hogas)
- 14:45 – 16:00:
GET /users/transactions.json
の N+1 を潰す (@kyontan)- 3500点ぐらい
- なぜか色々ハマって2時間半ぐらい溶かしていた。これは敗北ポイント
- 多分ここらへんでMySQLがサチらなくなっていた気がする
- CPUがサチっていたことに気が付いていたが原因が分からず (pprofをなぜ見なかったのか……)
- 15:30:
UserSimple
が持つ情報は全てオンメモリでキャッシュする- ユーザの存在チェックだけするような部分 (権限チェック) も基本的にこれで対応できるのでそうした
- 4210点
- ここからスコア上がらず……
- 16:00: 同じクエリを何度か叩いているエンドポイントがあったので潰す (@hogas, @kyontan)
- 16:00ごろ: transactions 以外詰まってないしCPUに余裕があるので campaign (負荷レベル)を弄ったところ外部APIで 403エラー {“error”:”IP address is not allowed”} が連発しだす
- 主に
GET /users/transactions.json
での外部 shipment サービスへのリクエストで連発 - 外部APIへの403はアプリケーション的には500を返す仕様だったので、これが連発してベンチマーカが途中終了し完走しない
- ここらへんで手詰まりに
- 主に
- 17:00: 明らかに不要な
SELECT FOR UPDATE
を削除 (4箇所+ぐらい)- これかこれの次辺りで4410点。これがベストスコア
- 17:10: 外部APIを叩く部分で exponential backoff を実装 (@h-otter)
- 変化なし / 403 が連発する
- 17:30: 外部APIをリクエストするエンドポイントだけ2台へ分散する
- 403 の連発は解決せず、なぜ……
- 18:10: 最終スコア0点 (failed) で終了
色々施策を打つもスコアの変化要因が今ひとつ分からず頭が回っていなかったですね。
特に外部API が undocumented な 403 を返す (しかもメッセージは 「許可されていないIPアドレス」) というのは全然分かりませんでした。
マイクロサービス問題なら単一IPから連続でアクセスすると弾くようになっているのかな、などと思い、後半で外部APIへリクエストをするエンドポイントだけ複数台へ分散するなどしていましたが全く解決せず泥沼へ。
これベンチマーカと外部API側に問題あるのでは? みたいな気持ちになりつつ、マイクロサービス問だしちゃんとbackoffとか実行すればいけるでしょ! と思っていましたが、意図したエラーだったのかは未だに分からず……
他の方の感想を見た限りでは、ポータルサイトへ登録されていないIPからのアクセスは弾かれるみたいですが、これについてはUI上で正しく登録されていることを確認したので問題なさそうでした。
11:00 ごろにインスタンスを立ち上げ直してIPを再登録したのがポータルサイトの何らかのエッジケースを踏んだのでは? などと勝手に邪推しています。謎ですね。
そのほか、 campaign の値を変えると何が変わるのか (POST系アクセスが増える?)、みたいなことも終了後にログを分析してようやくなんとなく傾向がわかり悔しい気持ちに。
感想
終わってみれば途中重大な気付き (「外部APIのレスポンスキャッシュできるじゃん」「ユーザの嗜好に合わせて商品出し分けたら?」など) があり、見逃しの多い回でした。
bcrypt の件は全く気づいておらず。前回は初手でやった(があまり意味がなかった) pprof を今回なぜ見なかったのか……
短い時間で追い詰められている環境の中でどれだけ集中できる / 気付けるか、というのが重要になると改めて感じました。
コンテストとしては今回も設定が面白く、運営の手の込みようが分かり、かつ短期間で取り組みがいのあるとても良い問題でした。
運営の皆様、大変お疲れさまでした。