Isucon12 参加記
2022年7月24日,ISUCON 12に参加しました.結果は予選敗退でした.本記事はその参加記です.
はじめに
本番前の練習を含め本コンテストを通して,Webアプリケーションのボトルネックの発見や様々なチューニング方法を知ることができたほか,Go言語を楽しく勉強することができました. 本コンテストを開催してくださったISUCON運営関係者の方々に感謝いたします.また,一ヶ月以上前から練習に付き合ってくれたチームメイトである@_u017 ,@donkooo00 に感謝いたします.そして,過去のISUCON参加記を書かれた方々含めコミュニティに感謝いたします.
チームメンバーと役割
チームメンバー全員がISUCON初参加,また,自分含めチームメンバー2人がGo初心者でした. そこで,役割分担としては各々勉強したい分野を担当しました.また,役割分担は厳密ではなく,当日は各々空いた人が必要なことをやっていました.
僕
普段は強化学習の研究をしています. コードのGit管理のための準備,アプリケーションを担当していました.
@_u017
普段はデータマイニングの研究としてプログラムの識別子名推薦をしています. nginx周りや複数台構成への拡張.アプリケーションを担当していました.
@donkooo00
普段はスパコン関連の研究をしています.唯一のGo経験者. 本番前は解析ツールやdeployスクリプトを用意するためのalpの準備,本番中はアプリケーションを担当していました.
本番当日までにやったこと
本番一週間前までは,毎週4時間ほど過去問をみんなで解いていました.一週間前からは本番時と同じく8時間という制限時間のもと本番を想定して練習していました.それ以外は各自が各々勉強していました.
本番当日
使用した解析ツール
- htop: メモリがどれぐらい使われているか,どのプロセスが重いかを見る時に使っていました.
- netdat: htopではわからない情報(ネットワーク周りなど)を見る時に使っていました.
- alp: どのエンドポイントが重いかを見る時に使っていました.基本的にここを見ながらボトルネックを発見していました.
- pt-query-digest: どのSQLクエリが重いかを見る時に使っていました.
- pprof: エンドポイントの中でどこが重いかを見るときに使っていました.しかし,本番は諸々の事情で使うことができませんでした.
開発方法
各々のブランチ上で各自作業をし,本番環境でpullしてベンチをとり,ボトルネックが解消されればmainにマージ,という流れを取りました. 基本的にローカル環境ではアプリケーションは動かさず,SQLクエリ関連のデバッグをするために,MySQLだけはローカルに持ってきていました.
本番当日の流れ
開始直後
開始直後は@u017がインスタンス作成,@donkooo00と僕がマニュアル読みをしていました.インスタンスが立ち上がったあとは,@u017はalpの流し込み,僕はGIt管理のための諸々の設定をしていました.
10:20
各々上記の作業が終わったあとは,@_u017はnginx confの設定,@donkooo00がdeployスクリプトの書き換え,僕はMySQL関連のボトルネックの洗い出しやINSERTやUPDATEが行われているテーブルの確認,ローカル環境でMySQLを実行できるようにしていました.
11:00
開始直後に行うことの一通りの作業を終え,ベンチの結果,整形後のalpの出力が出ました.この時,テナントごとにsqliteを毎回connectするのは遅そうという話をしていました.そこで,sqliteからMySQLへ移植するという話がでました.しかし,自分達はsqliteからMySQLへの移植をしたことがなく,また,どのやり方が良いかを考える必要もあり,競技時間中に終わらない気がしたため,@donkooo00がsqliteからMySQLへの移植作業,自分は移植作業が失敗した時を考え,N+1などの自明なボトルネックの解消をすることにしました.
competitionRankingHandlerのN+1の解消
@_u017がcompetitionRankingHandlerに存在するN+1の解消をしました. スコアは3857.
indexの追加
indexの追加をしました.このとき,mysqと同様の方法でindexを追加しようとしたため,エラーが出続けていました.SQLはDBによって微妙に構文が違うことは知っていたのですが,それに気づくことができず余計に時間を費やしてしまったのは勿体なかったです.スコアは4000.
playerHandlerのN+1の解消
@_u017のコードを引き継ぎ,playerHandlerのN+1の解消をしました.JOINを使用してN回呼ばれるクエリの結果を結合したものを1回のクエリでとってくることが理想でしたが,練習時にサブクエリが必要になってくるようなJOINに多くの時間を費やしてしまったことを考え,ひとまずin memory cacheを使用して解消しました.スコアは4418.
competitionScoreHandlerのbulk insert化
competitionScoreHandlerにて,INSERTを複数回呼ぶようになっていたので,bulk insertするように変更しました.スコアは6886.ここら辺はテナントごとのqueueを使うことでさらに高速化ができそうで,書こうとしたのですが時間が足りずできませんでした.
14:00
ここら辺でsqliteをMySQLへ移植することが難しそうということから@donkooo00も自明なボトルネックの解消をすることになりました.
playersAddHandlerの改善
playersAddHandlerのinsertも複数回呼ばれていたため,まとめて行うようにしました.その流れでN+1は自然に解消されました.スコアは8242.
2台構成
@_u017がapp x1,db x1の2台構成を試しました.スコアは10674.ここまでが15時までにできた作業でこの時点では割といい順位にいた気がします.ただし,この作業は現段階ではmergeせず,ひとまず1台構成で開発を進め,最後に複数台構成に変更しました.
competitionScoreHandlerのN+1の解消
competitoinScoreHandlerのN+1を解消しました.スコアは8601 .
billing内のクエリにLIMITをつける
@donkooo00がbilling内のクエリにLIMITをつけました.元の実装では,billing関数では上位11件のデータしか使われていないにもかかわらず,全ての行を取り出していました.スコアは計測忘れ
competitionRankingHandlerのin memory cache化
終了したcompetitionのランキングは変わらないと思ったため,終了したコンペティションのみメモリ上にキャッシュしました.しかし,別の箇所でcriticalなエラーが出て途中でベンチが終了しスコアが少し下がったため,mergeしませんでした.
playerHandlerのin memory cache化
tenantからdisqualifiedされたユーザー情報をメモリ上にキャッシュしました.スコアは8888.
終了作業
ログを切ったり,netdatを止めたり,複数台構成化をしたのちに再起動試験をしました.ここら辺から特にコードを変えていないのにベンチのスコアが安定しなくなるという問題が発生し,かなり焦りました.本来はApp x1,DB x2の構成をしたかったのですが,DBを複数サーバーに分けるのが難しく,最終的にApp x1,DB x1の構成にしました.最終スコアが10795
良かった点と反省点
良かった点
まず最初に良かった点としては,Goの構文周りの実装に詰まったらメンバーにすぐに聞くようにしたため,余計な沼にはまるということをなくすことができました. また,開始作業や終了作業などは特に大きな問題はなく,練習の成果が出せたと思います.
反省点
次に反省点としては,自分の実装が遅く,比較的自明なボトルネックの改善が全て終わらなかったことです. 特にN+1の解消に関しては,コードをある程度よく理解しないと効率よく解消することが難しく,コードリーディング力不足が出てしまっていたと感じました. ただし,自明な改善ができれば予選突破ラインぐらいには到達しそうだったため,かなり悔しいです.
また,チームメンバーがtenantsBillingHandlerのN+1の解消にずっと詰まってしまっていた時に手伝えなかったことです. 後から確認したところ自分が他の箇所で解消したN+1と同じような感じで解消することができることがわかり,ここら辺の情報共有をもっとしっかり行うべきでした.
終わりに
結果は予選敗退と悔しい結果になってしまいました. これは単純に自分の実力不足が原因であることを表しているため,今後はさらに精進していきたいと思います. また,今回はISUCON周りにGoにしか触れなかったため,今後はもっと広くGoを触っていきたいと思います.
PDF Translator 使い方
DeepL Translator for PDF 使い方
7月頃にPDF向けのDeepLベースの翻訳Chrome拡張を作りました.
実装はGitHubにあげています.
今回は,このChrome拡張で出来ることと使い方の説明をしていきます.
PDF Translator ができること
このChrome拡張ではPDFファイルから直接選択したテキストを自動で修正し,翻訳結果を直接画面に表示することができます.
以下は実際に使っている動画です.
このChrome拡張は以下の言語に対応しています.
また,このChrome拡張は以下の文章の整形を行います.
-
文章の途中の改行を削除します.
-
単語から「-」を削除します.
-
「.」の直後に「大文字のアルファベット」がある場合,その間に「スペース」を挿入する.
-
「;」"と「:」の直後に改行を挿入します.
使い方
ローカルのファイルに対して使いたい場合,このChrome拡張をインストール後に以下の設定を行う必要があります.この設定をしないと,ローカル(PC内)のファイルをこのChrome拡張で開くことができません.
まず,アイコンを右クリックします.
次に,拡張機能を管理を選択します.
このChrome拡張の使い方は非常にシンプルで翻訳したい文章を選択するだけです.
翻訳結果のテキストは選択してコピーすることもできます.
この時の注意点としてPDFファイルを開いた時のURLが「chrome-extension://」で始まっているかどうかを確認してください.そうでない場合,このChrome拡張は機能しません.
もし,URLが「chrome-extension://」で始まらない場合,このChrome拡張が有効になっていないことが考えられます.Chrome拡張の管理ページでこの拡張が有効になっているか確認してください.
また,一時的にこのChrome拡張の使用をやめたい場合,アイコンを左クリックすると一時的に機能を無効化することができます.
オプション
このChrome拡張のオプション機能として,翻訳元・翻訳先の言語の変更や翻訳結果の出力タイプの変更をすることができます.
この変更はアイコンを右クリックした時に出てくる「オプション」を選択すると,この画面に飛ぶためそこで言語の変更をしてください.
このようにすると,中国語で画面の下側に翻訳結果が出るようになります.
また何か不具合や質問点があればお気軽にご連絡ください.
C++で複数個の値に対する最大公約数の求め方
ある数とある数の最大公約数を求めるときにユークリッドの互除法を使い再帰的に求める方法は有名ですが,入力される数が3個以上になったときにどのように求めるのか書いていきます.
続きを読む