2020/09/20

ISUCON 10 にfujiwara組で参戦しました

タイトルに「予選突破しました!」って書いてないのは突破してないからです!

今年もISUCONの季節がやってきましたね〜。昨年はいろいろあって1人チームで出ていたのですが、さすがに1人は厳しい! ということがわかりまして(知ってた)、今年はコロナもあり社内チームが無難だろうということで久々のfujiwara組からの参加にしました。ちなみに1人は厳しいのは元々分かってるんだけど、でも作問中の事前解答とかは1人でやることが多いわけでして、まぁまぁ行けるんじゃないかとかちょっと思ってたんだけどな〜。世の中そんなに甘くないですね。

さて、この記事はISUCON終わった後の水曜日からコツコツと書いていてようやく日曜日に書き終わったみたいな感じの記事なので、まだ運営の問題の解説を読まずに書いております。まぁ結構好き勝手書いていますが、解説読んじゃうとまた感想にいろんな成分が加わってしまってピュアな感想にならない気がする……と思ってまだ読まないようにしていたので、早く解説読みたい一心でモリモリ書きました。

はじまるまで

技術的な事前の準備は組長がだいたいやってくれた(サーバに迅速に入れるようにするところとか)ので私の出番はなくて、主に社内の会議室確保したり自分のディスプレイ運んできたりみたいな物理系の準備をやりました。

ということで、開始を待ってたのですが、開始2時間遅延のアナウンスがあり、最終的には2時間20分遅延でスタートということに。それまでわりとヒマだったので、YouTubeの配信を眺めながらBGMを流して待っていました。大量のVMをプロビジョニングして用意することは昔のISUCON運営でやったことがあるので、いろいろ大変なのわかります…… 運営お疲れさまです……。

そういえば、なんか11時半すぎから連続で地震ありましたよね〜。会社の会議室でやってたので、さすがにこれデカイヤツきたら避難どうしようみたいなことをちょっと考えながら準備していました。

はじまったぞ

12時20分に始まって、ISUUMOという題材で「なるほどリクルート〜〜〜」となり、さっそくベンチマークを回そうと思ったのですがサーバーリストが空で回せなかったので、ひとまずレギュレーションとお題アプリをいろいろ眺めたりしました。サーバリストにデータが入るまで小一時間ほど時間があったので、サーバからとってきたソースを眺めたりしていましたが、やはり1回もベンチが回せない状態だとどういうURLパスが重いのかとか、どういう検索条件が飛んでくるのかがわからないためこれは正直わかんねーなーという感じでした。

一通り読んでもまだサーバリスト問題が解決してなかったので、サーバリストが空でもベンチマークをかけたいサーバのIPアドレスは分かっているということで、このポータルのWeb APIをcurlかなんかで叩けばEnqueueして一足お先にベンチマークかけられるのでは?? と思ってポータルのwebpackされたJSなどをpackされたまま読んだりして時間を潰していました。ちなみにポータルはTypeScriptで書かれててprotobufでやりとりしていたのでcurlでprotobufのペイロードを投げつけるのは大変そうだ! というところまではわかり、うーんどうしようかなコレ深追いするか悩むな〜とちょっと考えた結果、まぁお題を高速化する方策を考えた方がよいだろう(そりゃそうだ)ということで深追いするのはやめました。

ベンチ回せるようになるまでいろいろ読む

小ネタなどを含むいろいろ気付いた話をメモっておきます。

  • サーバは3台、1コア2GB
    • このご時世にこのスペックで3台……! まぁISUCONってそういうもんですけど
  • 入稿処理(というベンチマークをかけないとなにがくるのかよくわからない処理)では画像自体はアップロードされない
  • みた感じ同じ画像だけどファイル名が異なるやつ、画像全体のMD5は異なるけど画像を head -c 1000 したやつのmd5sumは一致している。ISUCON7予選にもあったやつだけど、バイナリの最後のほうのメタデータに違いがあるやつっぽい
    • 結局そこのところはあとからベンチ回してボトルネックに関係ないことが分かったので、スルーした

お題は検索だった

はじまる前、チーム内の雑談で「今回の問題はなんですかねぇ」みたいな話をしてました。さすがに10回やってて3回目以降は予選本戦形式になったので15問以上の問題があるということで、もうネタがないんじゃないかみたいなことを思ってましたが、たまたま最近ちょっと全文検索方面に思いを馳せていたので「検索系とかありますかねぇ、MySQLで検索してるけどElasticSearchにまるっと載せ替えるのが正解みたいなやつ」みたいな話をしていたら今回はまさに検索の問題。

ベンチマークが使えるようになるまでちょっとお題アプリを実際に動かして触ってみて、うーーんこれはめっちゃたくさん検索条件の種類あるから、これはもしかしてElasticSearchか? と思いましたが、だれもちゃんと使ったことがなかったのでさすがに初手でElasticSearchに載せ替えはできないよねということで、普通にMySQLでがんばる方向で解き始めました。

ベンチマークが使えるようになって実際の負荷をみてみると、MySQLが9割ということで、ほぼほぼ予想通り(というかISUCONって大体そういう感じからスタートしますよね)。重そうなクエリの出所を眺めてみても、やはりchairとestateのsearchだったので、まずはここから手をつけていくことにしました。

Index Condition Pushdownでいくぞ作戦

検索条件側に、Featureという「日当たり良好」みたいな希望条件をANDで入れるやつがあったのですが、それはチームメイトのmacopyさんがいい感じにしてくれるとのことだったので、自分はラジオボタン系の検索をどうにか軽くすることにしました。

この大量の検索条件をINDEX一発で捌くのはなかなか骨が折れる話なのですが、今回はMySQLが5.7だったので、マルチカラムインデックスでIndex Condition Pushdownが効くよねということで、以下の大がかりな改修に取りかかります。

  • Width / Height / Depth / Price(estateではRent) の検索条件はIDで飛んでくるので、IDで引けるようにカラム追加
  • 追加したカラムでマルチカラムインデックスを構成してINDEXを効かせるようにする

結局小一時間ちょいでコードは書き終わって実際に投入してみたのですが、どうも最初のVerificationフェーズで落ちることがあり、発生頻度が半分くらいだったためそのブランチは結局マージできず。あれは結局なにが悪かったのかよくわかってない……。

ちなみに、Index Condition Pushdownするにはそのマルチカラムインデックスの1カラム目がちゃんと条件に入ってないとオプティマイザがそのインデックスを使ってくれないので、chairはstockを最初のカラムに、estateはそもそも対象カラムが3個しかないので、順番変えたマルチカラムインデックスを3個張る形で対応しました。後半の時間にmacopyさんに実データ調べてもらったときに「最初は全部stockが存在する状態から始まる(つまり stock > 0の条件はほぼ全てのカラムに当てはまるので意味がない)」ということがわかったので、まぁまぁ筋の悪いINDEXだったため、捨てて正解でしたね。

終わったあとのDiscordとかで「とはいえ飛んでくる条件は1個か2個くらい」みたいな話だったので、それならFeatureの指定があるならそっちで絞り、それが無い時は素直に各条件のIDを示す追加カラムで1個だけ張ったINDEXで絞り込んでしまうだけでもだいぶ負荷が下がったんじゃないかなぁと思いました。

CSV入稿がBulk Insertされてないやつ

Index Condition Pushdown狙いの7カラムマルチカラムインデックスは、UPDATEクエリによりINDEX更新負荷がすごくて、CSV入稿で500件飛んできたときに、Bulk Insertに変えてない状態だと2.9秒ほどかかるようになってしまいました。で、その結果どうやらクライアントが2秒くらいのタイムアウトで打ち切っているようで、チェックエラーとなりVerificationフェーズが完走しないというのがありました。レギュレーションにはその辺のタイムアウトの許容時間とかが書かれてなかったのでタイムアウトなのかどうかが良くわからん(今考えれば普通にnginxログみて499かどうか見れば良かったんだけど)、ということで質問フォームを送ったりもしましたが「答えられません」というような回答でした。

アクセスログの流れをみると2秒くらいでレスポンスまつの打ち切って入稿成功しているかどうかのリクエストがきて死んでるような感じであることは分かったので、まぁこれ高速化するしかないやろということでBulk Insertに変えました。すると400msくらいで終わるようになり、そのタイムアウト起因によるVerificationのチェックエラーは出なくなったのですが、結局別のエラーで半分落ちるのでどうしようもなかった。

ということで、ブランチまるごと捨てましたが、初期状態だとVerification時の1回しか来なかったCSV入稿が後のオンメモリ化が出来た後にはそれなりの頻度でやってきてたので、このBulk Insert化だけは入れて置いた方がよかったな〜というのが一つ反省として残っています。

SELECT * を撲滅する地味作業

ISUCONでよく出てくる、この手の複雑怪奇でRow ScanがデカくなりがちのSELECT文は SELECT * になっているのを SELECT id に変えて、一発目のクエリをセカンダリインデックスだけで完結するようにして改めて WHERE id IN(?) でクラスタインデックスを引くようにすることで、ちょっと軽くするみたいな小技があります。

まぁ今回もこれやっといたほうがいいだろうということで、全てではないですがRow Scanの大きそうなクエリをゴリゴリと SELECT id に変えていく作業をやりました。

これ1回目ミスってマージ出来ずだったのですが、その後1箇所nazotteのところで手を抜いたところの心当たりがあって、まさにそこでベンチマークが落ちていたのでそこをちゃんとしたらベンチマークが通り、なんとかマージ。ようやくバリュー出た説ある。

オンメモリ化に舵を切る

とはいえ、残り2時間ちょいの時点でアプリ側でバリューが出たのは私の SELECT id のやつとmacopy側のFeature縦持ちブランチくらいしかなかったため、そろそろ予選突破には飛び道具的な大技を決めないとダメですねとなり、オンメモリにデータを持って検索をMySQLからゴリッと引き剥がすことに。

estateとchairをそれぞれmacopyと私で分担し、macopyのestateのほうが(検索に絡むカラムが少ないこともあり)早くおわったのですが、それがなかなかベンチマークが通らずという感じで残り1時間を切り、私のブランチは残り30分くらいで完成し、1個だけバグあったけどそれだけ直したらバッチリ通ってスコアがモリモリッとあがりました。

その時点でもうあと20分ちょいしかねーぞということで、組長に3台のCPU負荷を教えてもらってnginxのlocation振り分けを調整し、再起動をしてもう1回まわして2000ちょいのスコアがでたところで残り10分。まぁ予選通過ラインに10%ほど足りなかった今からすると「もうちょっと粘って回しておけば……」という感じでしたがもうその時は疲れ果てていて「これ以上ベンチ回してせっかく2000乗ったスコアを壊したくない……」みたいな気持ちになってしまい、そのまま終了しました。

その他感想など

いやー難しかった! なんというかISUCONは回が進むにつれてどんどん予選問題難しくなってきてて、でも実在するサービスがベースになっている問題が多くて「ふつーのWebサービスだったら高速化できるけど、こういうオリジナリティがあっていかにもユーザが便利に使いそうなサービスの裏側ってマジで大変なんだな〜これ!」という感じになりますね。

ということで、本戦はもう2週間先の10月3日(土)ですね! 本戦に出るみなさまがんばってください! 今年はコロナで一箇所に集まれない分ライブ配信とかに力を入れてるっぽいので、その辺を楽しみにしております〜〜〜

2020/06/28

シリーズ: 筆不精の旅行記

2018年末にシンガポールに行ってきた (1)

新型コロナウィルスの影響で旅行に行けないみなさんこんにちは! 私もそんな旅行好きの一人なのですが、まぁ旅行が出来なくてもやることはあるだろ! ほらアレだよアレ! 『旅行から帰ってきたら今度こそはちゃんとブログ書いて更新するぞ〜!』と帰りの飛行機で思ってたはずなのに、結局1行も書かなかった旅行記を完成させるという人生の残タスクだよ……!

……ということで、ぼちぼちそういうのを書いていくか~ということで、手はじめに2018年末に人生初の海外年越しをしたシンガポール旅行の話を書いていきます。

旅行の準備

今回の旅程は2018年12月29日(土)〜2019年1月1日(祝)の3泊4日です。シンガポール3泊4日は移動も考慮するとけっこうな強行軍になってあんまり滞在時間多くない感じになるわけですが、当時そんな遠くまで旅行したことなかったこともあり往復の移動時間を考えずに「3泊4日!!」と決めてしまっていたのでした。まぁあとは予算が「年末年始だけどとりあえず30万用意しておけば足りるよね?」くらいのノリだったみたいなのもあります。

結局、飛行機予約した後にざっくり旅のしおりを作り始めて滞在時間を計算したところ、「あれ、移動時間めっちゃ長いから実質の滞在時間思ったよりも短いな……」となった記憶があります。とはいえ年末年始なので飛行機が高くて高くて、帰りの便を1月2日以降にするとめっちゃ高くなるので、旅程側でいろいろ小細工をして安く済ませる方向でやりました。

当時調べた記憶だと、年末年始期間にANAでいくとエコノミーでも往復35万とかだっけな……? とにかく高くて飛行機だけで予算オーバーになるし、パックツアー調べても同じような値段なのでこれはもう飛行機とホテルバラしたほうがいいなとなって、シンガポール航空でいくことにしました。こっちだと見栄張ってビジネスクラスとか選ばなければ17万くらいで行けそうです。しかも帰りは同じ値段でプレエコとれたので帰りは寝てりゃいいだろみたいな感じにできたのでよかった。

最初予約したときはその時唯一空いてた29日(土)の夕方便を取ったのですが、それだとシンガポール着いたら深夜になってて3泊4日といいつつ実質丸2日しか行動できません。ということで予約完了直後からちょくちょくシンガポール航空のサイトの空席情報をチェックしてすかさず空きが出来たのをみつけて28日深夜便に繰り上げて滞在時間を捻出した、という感じです。最初の予約がたまたま変更可能な運賃でよかったわ〜。これで現地での滞在時間は29日朝〜1日昼まで一応3日半確保できました。

ちなみにシンガポールにはこんな国です。

  • 日本からのフライト時間7〜8時間
  • 日本との時差 +1時間 (日本の19時はシンガポールの18時)
  • 通貨はシンガポールドル(S$)、1S$=80円弱

時差がほぼないので「着いたら時差ぼけ」みたいなことがないのはよいですね〜。時差ぼけしたことないからわからないけど。

0〜1日目 (移動編)

12月28日(金)、この日は会社の年内最終営業日ということで納会があるのですが、適当に切り上げて深夜便に乗るべく羽田空港に向かいました。乗るのはSQ639便、深夜2時半に羽田を出て朝の9時すぎにシンガポールに到着するというフライトです。深夜2時半のフライトなので深夜0時半くらいまでに羽田に着いていればいいのですが、そもそもそんな深夜に到着する電車ないよということで、22時すぎに着くように早めに出発。

羽田の国際線ターミナル(今の名称は第3ターミナル)といえども22時過ぎるとだいたいのお店は閉まってしまうので、わりとやることねーなということで早々に出国してしまい出国後エリアでぶらぶらしたあとラウンジへ。一応まだ年末年始の出国ラッシュが始まる直前みたいなタイミングなのでそんなに混雑はしておらず、お菓子食べながら飲み物飲んで半分寝てました。

搭乗時間が近くなってきたので搭乗口に向かうと、なんかマカロンとかの菓子配ったりホットココアなどの飲み物を振る舞ったりしてて「えーこれなんなんだ」とおもったら、どうやら予約した便がその12月29日から増便になった便だったようで、就航記念みたいな感じでいろいろ配っていたようです。乗った後もなんか普段もらえない感じのプレゼントとしてネックピローがプレゼントされて「あ、オレネックピローもってないからちょうどよかった〜」みたいな感じになりました。

この投稿をInstagramで見る

たまたま適当に予約した飛行機が記念便だったパターン

acidlemon(@acidlemon_jp)がシェアした投稿 -

機内で何食べたかはもうすでに忘れてますが、2時半に乗ってなんかそれなりにヘビーなものを食べてお酒を飲んで、そこからぐっすり寝て出てきた朝ごはんをたべて一息ついたらシンガポール到着です。とりあえずビジター向けのSIMを買って市内観光へ。

ちなみに、チャンギ空港のトイレで当時買って2ヶ月のiPhone XSを落としたときにヒビが入ってしまいました。まぁ角がちょっとヒビいっただけなので別にいいかと思ったのですが、コレよく考えたら海外旅行中の携行品損害だからクレジットカードの保険とか使えるのでは! って気付いたのですが、帰ってきてから手続きするのめんどくさくて結局使わずじまい。今もiPhone XSは当時のヒビがはいったまま使っておりまして(最近また落としてヒビが増えた)、今年の9月のiPhoneが5G対応してたらそれに買い換えよう……と思いながら使ってます。

空港から市街地に向かうにはいくつか交通手段ありますが、行きは現地の公共交通機関に慣れるという意味でもやはりMRT(地下鉄)じゃろということで電車移動です。空港ってだいたいどこでも電車の終点なので、とりあえず来た電車にのっとけば大丈夫だろうと思って乗っていたのですが、シンガポールは空港行電車が盲腸的な路線だったパターンの街でした。2駅乗ってみんなどこかへ乗り換えていったあともそのまま座ってたら逆向きに走って空港に戻り始めて「ちょwww」みたいになりました。

ちなみにシンガポールで何度も公共交通機関をつかう場合EZ-link cardというICカードを使うと便利なので2日目以降はそれを利用しました。いまだとチャンギ空港に現地SIMとEZ-link cardが合体したhi!Tourist 2-in-1 SIM Cardが売られていて、カードからSIMカードを外したあとの抜け殻がそのままICカードとして使えるみたいなやつになってるらしいです。よく考えられてるな〜。

1日目 市内観光編

さてシンガポールといえばなんだ? という話なのですが、私はF1観る人なのでとにかくシンガポールといえばシンガポールGPです。ということで、Google MapとシンガポールGPのコース図を眺めながらベイエリアを散策です。

シンガポールGPはシティサーキットなので直角コーナー多いのですが、実際に歩いてみるとたしかにそりゃ公道だから直角だよなみたいな感じでした。とりあえず適当にマーライオン目指して歩いてる途中におしゃれな橋が出てきて、これなんかみたことあるような……って調べたらアンダーソンブリッジじゃんみたいな感じになりました。というかこんなに狭い橋F1カーで走るのん……? と思って調べてみた所、ESPN F1で同じような構図の写真があって「たしかにこれだ……」みたいになりました。

あと、マーライオンみたり美味しいアフタヌーンティー食べたり(スイーツだけでメシOKな身体でよかった)してるうちにちょっと日が暮れてきて、マリーナベイサンズにいく橋からベイエリアを一望する風景をぼーっと眺めたりしていました。

※補足: コレ「The Float @ Marina Bay」のグランドスタンドではあるのだけど、調べてみると「シンガポールGPのグランドスタンド」ではなかったです。シンガポールフライヤー(観覧車)側に別途あります

このあとマリーナベイサンズのショッピングエリアをうろうろして、チームラボがつくったインタラクティブな光る床(語彙力が無い…)を眺めたり、いろんなショッピングエリアをぶらぶらしたりして、最終的にちょっとローカル感のあるフードコート(と、食べたときは思っていたのだがもっとローカル感のあるフードコートはこの後たくさん見た)でご飯をたべて1日目を締めくくりました。

このうまそうなやつ、たしか3種頼んで10S$しないくらいだった気がするのでまぁまぁ安いな〜という感じでした。ちなみに2日目以降に食べた他のフードコートはもっと安かったので、フードコートの食べ物についてはそこまで高くないんだな〜という感じです。

ちなみにこのとき止まったホテルはベイエリアに歩いて行けるギリギリくらいの距離にあるペニンシュラエクセルシオールホテルという4つ星のホテルでした。ベイエリアの5つ星高すぎてのう……(12月31日夜にニューイヤーカウントダウンの花火やイベントがあるので、部屋からそれがみえるホテルは軒並み高い)。ちなみに東京とかにあるザ・ペニンシュラのホテルグループとは関係ない。

宿泊プランを見ていくと、限定された人しか入れないクラブフロアというやつがわりとお手軽な値段で泊まれるので、クラブフロアで朝食食べれるの便利そうだし眺めもよさそうだしということでそういうプランで泊まっています。結果、どんな部屋だったかというと、窓の向き海向きじゃなくてダウンタウンコア向きでしたが、部屋は広いので荷物散らかしても足の踏み場無くならないからまぁまぁよかったといったところです。

さて、出発から1日目の話はここまで。また気が向いたら2日目以降の記事を書きます。

うされもん @acidlemonについて

|'-')/ acidlemonです。鎌倉市在住で、鎌倉で働く普通のITエンジニアです。

30年弱住んだ北海道を離れ、鎌倉でまったりぽわぽわしています。

外部サイト情報

  • twitter
  • github
  • facebook
  • instagram
  • work on kayac