taka_rock’s blog

エンジニアの勉強記録

ISUCON11予選に参加して失格になってきた

概要

去年(予選, 本戦)に引き続きISUCON11 予選に参加し、失格になってきた
その振り返り。

f:id:taka_rock:20210901000004p:plain
最終スコア

また、去年参加したチームは、今年社会人になった僕以外今年も学生ということで、学生枠での本戦出場を目指して解散した。
ということで会社の同期に声を掛けて、次のチームで出場した。

事前準備したこと

利用言語は、メンバー全員が普段業務でKotlin, ScalaといったJVM系の言語を書いており、実装が無いので可読性の高さなどからGoでやることにした。
ミドルウェアはNginx, MySQLが使用されると想定して、設定や計測の練習や準備を事前に行った。

準備した計測ツールは下記

  • プロファイラー
    • pprof
  • アクセスログ解析
    • alp
  • スロークエリ解析
    • pt-query-digest
  • リソース確認
    • htop

毎年ツールの導入方法をISUCON前に思い出しているので、自分のメモのために導入方法も書いておく。

pprof

net/http/pprofに書いてあるように、こんな感じで追記すればOK。便利。

import _ "net/http/pprof"

func main() {
  go func() {
      log.Println(http.ListenAndServe("localhost:8080", nil))
  }()
}

プロファイルする際はローカルから

$ go tool pprof -http=":8888" "http://example.com:8080/debug/pprof/profile?secondos=60"

みたいな感じで、計測をした。

alp

下記コマンドでインストール

$ curl -OL https://github.com/tkuchiki/alp/releases/download/v1.0.7/alp_linux_amd64.zip
$ unzip alp_linux_amd64.zip
$ sudo install alp /usr/local/bin/alp

Nginxのログ出力をLTSV形式に設定して、こんな感じのコマンドでアクセス合計時間で降順にソートしてログを解析できる。

$ alp --sort=sum -r --file=/var/log/nginx/access.log ltsv | head -n 10

pt-query-digest

下記コマンドでインストール

$ wget https://www.percona.com/downloads/percona-toolkit/3.0.3/binary/debian/jessie/x86_64/percona-toolkit_3.0.3-1.jessie_amd64.deb
$ sudo apt-get install -y gdebi
$ sudo gdebi -n percona-toolkit_3.0.3-1.jessie_amd64.deb

MySQLの設定でslow_queryを出力するようにして、下記コマンドで解析できる。

$ pt-query-digest /var/log/mysql/mysql-slow.log

htop

topコマンドで もリソース確認できるが、特に何もしなくても見やすいのでhtopを使用することにした。

$ sudo apt install -y htop

でインストールして

$ htop

で使用する。

セットアップスクリプト

当日、各サーバーにセットアップする手間を減らすために簡単なスクリプトを書いた。

github.com

デプロイスクリプトも書きたかったが、準備する時間が無く、準備できなかった。絶対にあった方が便利なので、来年は頑張りすぎない程度に事前に用意する。

当日やったこと

当日のチームメンバーの動きは、下記のように予め大まかに決めておいた。

  • takahiro
    • インフラ
  • tomonori
    • 問題の読み込み+計測+App改善
  • shutarou
    • App改善

当日行ったことを、自分が行ったことと記憶を元におおまかな時系列順にまとめていく。

10:00 競技開始と初回ベンチ2396点

競技開始され、AWS CloudFormationのテンプレートが配布されたので適用した。
競技用サーバーのIPが確定したら、ssh用のconfigファイルを用意してメンバーに共有した。
ここで実行した初回ベンチマークのスコアは2396点だった。

10:10 諸々のセットアップ

事前に用意しておいたセットアップスクリプトを実行し、tomonoriに問題の読み込み、shutarouに競技のコードをGitHubへのpushをお願いした。
自分は、サーバーのスペックと実行環境を確認した。
サーバーのスペックはAWSコンソールから確認し

$ sudo systemctl status nginx

でnginxが動作してることを安心して

$ sudo systemctl status mysql

MariaDBだったことに少し驚いたが、MySQLと互換性あるからなんとかなるだろうと思い、alpとpt-query-digestで計測できるようにNginxとMatiaDB側の設定を書き換えた。

10:30 AppとDBの分離

AppとDBを別のサーバーに分けたほうが計測しやすいと考え、最初に下記の様にAppとDBを別サーバーに分離した。

  • サーバー1
    • Nginx
    • App(Go)
  • サーバー2

(限界まで1サーバーでチューニングした方が良い気もするので、来年は事前の準備段階でそのことについてもうちょっと考えておいた方が良さそう。)

ここで、ローカルからもDBへアクセスできるように、AWSのセキュリティーグループのインバウンドへ3306を特に何も考えずに追加した。

11:00 MariaDBのチューニングを少しして6825点

MariaDBのチューニングを少しした。 git管理していなかったので微かな記憶だが、下記の値を設定した気がする。
(来年はこの辺の設定ファイルもちゃんとgit管理しておきたい)

innodb_buffer_pool_size = 3GB
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT

サーバーのメモリが大きかったので、雰囲気でquery_cache_sizeも大きくしといた気がする。

この時点で、スコアは6825点

12:00 DB使用率が100%

ベンチマークを実行した際のサーバーリソースを計測すると、DB使用率が100%だった。
alpの実行結果と f:id:taka_rock:20210901002957p:plain pt-query-digestの実行結果 f:id:taka_rock:20210901003437p:plain を確認して、isu_conditionテーブルのreadとwriteが原因でCPU利用率が100%になっていると考え、POST /api/condition/[0-9a-z\\-]+があまりにも多かったので、write側のチューニングをしようとチームで決めた。
(pt-query-digestの結果から、INDEXを貼ることによりreadの改善が期待されたが、write優先で後回しにした。パッとできる改善ではあったので、パッとやっちゃえば良かったと後から思った。)

12:30 isu_conditionのBulkInsert化

他のメンバーがisu_conditionのwriteを減らすために複数回分のリクエストをまとめてInsertしようとしたりしてくれていたが、非同期周りのハンドリングが難しくて、1リクエスト内のBulkInsertだけ実装した。この辺はチームメンバーに任せていたので、詳しいことは分かっていない。
その間に自分は、Appのソースコードを読んだり、DBの中身を確認してた。

13:30 GoからDBへのコネクションの制限を無くす

ソースコードを読んでいたら

db.SetMaxOpenConns(10)

と、最大コネクション数が10に制限されていたので、コメントアウトした。
スコアは全然上がらなかった。

14:00 jia_service_urlを変数で持つように

isu_association_configに保存されているjia_service_urlが、初期化処理時以外に変化して無さそうだったので初期化処理時に変数で持つようにしてDBから取得しないようにした。
これもスコアは全然上がらなかった。

14:30 DBを2台にしようと妄想する

相変わらず、DBサーバーがCPU100%のままなのでなんとかしようと考える。 水平分割すると良いのかな?とか妄想しながらとりあえず、ボトルネックになっているisu_conditionテーブルだけ別サーバーのDBにしてなんとかしようとGoとDBのセッティングをしてた。
↓が起きたので、PRだけ作成して反映はしなかった

15:00 isu_conditionに貼り忘れてたINDEXを貼る 23000点

isu_conditionにINDEX貼ると良さそうって話をしていたもののINDEXを貼っていなかったので、tomonoriがINDEXを貼ってくれた。
ベンチマークを実行すると唐突に23000点になって衝撃を受けた。

サーバーのリソースを確認するとDBのCPU使用率が低下し、App側が100%になっていたので、サーバー構成を

  • サーバー1
    • Nginx
    • App(Go)
  • サーバー2
    • App(Go)
  • サーバー3

にしようと考える。

16:30 App2台構成で30000点

Nginxでロードバランシングすれば、App2台に簡単に構成にできると思ってたら、少しハマってベンチマークが失敗し、時間を取られてしまった。

ハマった点としては下記

  • 初期化に必要なSQLファイルが追加したサーバーに配置されていなかった
    • 初期化に必要な/sql/1_InitData.sqlをgitignoreしており、元々存在していたファイルを削除してしまっていたため、初期化に失敗していた。全然意識していなかったので、気づくのに遅れてしまった。
  • jia_service_urlを取得できない
    • jia_service_urlを初期化時に変数で持つようにしたことにより、初期化をしていないサーバーだと失敗するようになっていた。

上記を改善して、App2台構成で30000

17:00 trend APIの改善をしようとする

App2台構成で負荷分散すると、DBのCPU使用率が100%になった。
alpの結果が

f:id:taka_rock:20210901013538p:plain

とtrend APIの呼び出し回数が、増えていたので他のチームメンバーに改善をお願いした。
任せてしまったので、詳細な変更内容は把握できてないが、処理の高速化を行ったらAPI呼び出しが増えてスコアが下がってしまい、revertした。

17:10 Nginxの設定をチューニングしたりした

nginxのworker_connectionsを増やしてみたり、ロードバランシングをラウンドロビンとLeast Connectedで比較してみたりした。 スコアはあまり変わらなかった。

この辺で、運営側に障害が発生し、ベンチマークが実行できなくなった。

17:30 撤退準備してたら51865点

ベンチマークが実行できなくなってる間に、撤退準備をした。
行ったこととしては、nginx,mysqlのログ出力停止、goのpprofを削除したりした。

その間にベンチマークが復活し、実行すると51865点。
pprofの削除が効いたっぽい?

18:30 dropProbabilityに願いをかける

ベンチマークの障害により延長された時間でdropProbabilityに願いをかけた。
0, 0.5, 0.9の値で検証してみたものの、デフォルトの0.9が最もスコアが高かったため、0.9を採用。

18:45 競技終了 43797点

51865点を取った際と特に差分はないはずだが、何故か再現することができず43797点でフィニッシュ。

結果

失格になりました。

f:id:taka_rock:20210901000004p:plain
最終スコア

DB等をローカルからアクセスできるようにするために、AWSのセキュリティーグループのインバウンドを追加した事が原因です。 レギュレーションをちゃんと読んでおらず、禁止されていることを認識せずに何も考えずに行いました、、、

振り返り

良かったこと

  • チームメンバーの役割分担を予め決めておいたこと
  • 計測ツールの導入、実行方法を予め確認できていたこと
  • サーバーリソースの確認、ログの解析を行いながらチューニングが行えたこと

悪かったこと

  • レギュレーション、アプリケーションマニュアルをちゃんと読んでいなかったこと
  • INDEX追加等、とりあえずできることをすぐにやらなかったこと
  • ミドルウェアのチューニングをなんとなくで進めてしまっていたこと

今年できなかったけど来年はやるとよさそうなこと

まとめ

チューニングを愚直にやることで、着実に点数が上がる問題だった。
結果として失格にはなってしまったが、ちゃんと計測し、チームで色々議論しながらパフォーマンスチューニングできて楽しかった。
来年も出場して本戦出場したい。

appendix

shutarouの参加ブログ chouxcreams.hatenablog.com

使用したGitHubレポジトリ github.com

2020年を振り返る

毎年、年末に1年を振り返りたいなと思いながら振り返れていなかったので今年こそは振り返る。
1年間の休学の終盤+復学した大学4年という1年間。
月ごとに振り返っていきたいと思います。

1月

前年の12月に、働いていたスタートアップを退職して(学生インターンだけど休学して週5くらいで働いていた)時間ができたので次は何をしようか考えていた。
プロダクト作るの手伝って欲しいと以前から声を掛けてくれていた友人を思い出し、プロダクトの内容を聞いたり、壁打ちを手伝ったりもした。次はここにフルコミットしようかなとも考えたが、自分的にピンと来なかったので違うかなと思ったりもしていた。
とりあえずどこかの組織でコードを書きたいと思い、内定先や以前のインターン先に聞いてみたがタイミングの問題もあり、厳しそうだったので新しく探すことにした。
技術的には、Golang書けるようになりたいと思っていたのでGolang書いて勉強をしていた。

2月

Golangの学習を続けており、TechTrainにてGolangを題材にした教材があったためそれに取り組んだtechbowl.co.jp

そのツイートをTechTrain代表のおざまささんに拾って頂き、TechTrainで面談することになった。

その面談でインターン先探してる等の会話もし、TechTrainの開発に声を掛けて頂いてTechTrainの開発に加わることになった。この頃はまだコロナの影響も大きくなく、僕が茨城に住んでいるため、出社しやすいようにGAOGAOさんのシェアハウスを用意して頂いた。(金曜日に面談して開発に加わることになり、月曜日には出社していた。圧倒的スピード感)

エンジニア系以外の話だと祖父が亡くなり、初めて身近な人の死を経験した。(葬儀の準備とか諸々見て、非本質的な手間が多すぎてしんどいな。とも思った) 葬儀と好きなバンドのフェスが重なり、行けなかったのは2020年トップレベルの心残り。coldrain、またフェスを開催してくれ。欲を言えばPTPも見たい。

3月

コロナの影響も大きくなってきて、TechTrainでの開発がリモートになり、茨城に帰ってきた。(なんだかんだ物理出社したのは1週間未満になってしまった。) 今振り返ると、中々早い時期からリモートで働いていたなと思う。 TechTrainのバックエンドでは、PHP,Laravelが使用されており、それをゴリゴリ書いていた。PHPさんは中々手が掛かる。 他にはハッカソンのメンター等色々やらせて貰った。

友人と海外に行く話もしていたが、コロナの影響で取りやめにした。コロナの影響が収まってきたら海外に行きたい。 KNOTFESTのチケットも持っていたが延期になった。チケットはまだ持っている。

4月

大学に復学した。1年間休学して大学の事は何もしていなかったが、意外と専門分野の事や数学も覚えていてなんとかなるなと思った。 がっつり研究しようかなとも思ったけど、コロナで研究室に中々行けないこともあり、引き続きがっつり働いてコードを書いていた。
後は、GAOGAOさんのハッカソンに参加し、Next.js x Firebaseで開発した。 入賞できなくて悔しかった。フロントエンドもっと書けるようになりたい。
note.com

5月

やぱったreudハッカソンに参加し、入賞した。 qiita.com

2人の開発力が凄くて、あーだこーだ言っていると、どんどんプロダクトが形になっていて楽しかった。3人でまた何か開発したい。
後は、仕事&研究室のゼミ&暇な時にDockerとかの勉強してた気がする。

大学時代最後のロッキンや色々なフェスが中止になって、しんどいな〜と思っていた気がする。

6月

引き続き、TechTrainで働きつつ研究室のゼミをこなしていた。 プレゼン力を上げたいということもあり、LTで登壇したりもした。 note.com

7月

6月と同じ感じの生活をしていて、またLTで登壇した。 devrel.connpass.com 知り合いが全然いないということもあり、かなり緊張した。もっと上手くなれるよう精進したい。
大学では、研究テーマが決まり、やりたいと言っていた機械学習使う系のテーマに決まって良かった。

8月

研究室のゼミが夏休みで時間ができたということもあり、筋トレしてた。そしたら、アナフィラキシーショックになった。死ぬかと思った。運動は適度にやることis大切。 後は、初キャンプに行った。楽しかった。また行きたい。
技術の事だと、友人とClean Architectureの輪読を始めた。良い本なのかもしれないけど、筆者の主張的なとこが多かったり、今でも使えんのか?って思うとこは結構あった。
今でもプログラム書くときはアーキテクチャどうしようかな〜って結構悩む時間が多い。

9月

ISUCON10予選にやぱったreudと参加した。
棚からぼたもちにより学生枠で本戦出場してしまった。めっちゃ嬉しかった。 taka-rock.hatenablog.com

サポーターズ社の技育展にも登壇した。入賞はできなかったけど、周りのプロダクトが凄くてそれはそうって感じがした。オンラインでの登壇には慣れてきたのでオフラインでも同様のパフォーマンスを発揮したい()

仕事では、インターン生のメンターをやったり、インフラのDocker化、AWS ECSへの移行+TerraformでのIACを設計から1人でやったりしてた。

zenn.dev

今までその場しのぎでインフラを触っていた感があったが、ISUCONと仕事でのインフラ移行タスクにより、この辺から自分のインフラスキルの圧倒的向上を感じた。

10月

ISUCON10本戦に出場した。正直、全然歯が立たなかった。来年は、社会人枠で本戦出場し、優勝して100万円欲しい。 taka-rock.hatenablog.com

応用情報にも申し込んだが、全然勉強する気が出なく、落ちた。来年はちゃんと勉強して取りたい。

友人とのClean Architectureの輪読が終わったので実践Rustプログラミング入門の輪読を始めた。友人と輪読することによって、CSや他の言語での実装知識も深まってなかなか良い。来年はRustでプロダクト作りたい。

11月

研究の中間発表があったため、がっつり研究してた。
所属している研究室が無線通信の分野で、数式で理論を立てる→Pythonでシミュレーションという流れの研究室だが、とりあえずシミュレーションを実装する→もっと理論まとめなって教授や先輩から言われるのをひたすら繰り返してた気がする。(とりあえず手を動かす&足りないところは後からキャッチアップする派の人間なので自分にはこのやり方が合ってた。)
シミュレーション回してると、もっと効率化したり実行時間を短縮したいという気持ちが強くなり、シミュレーション終了時にslackに結果を通知するようにしたり、Pythonでの並列処理を勉強したりしてた。プログラミングスキルで研究ゴリ押ししてます。ニューラルネットもやっているので、機械学習のスキルもちょっとついて嬉しい。
研究が忙しくなってきたこともあり、この月でTechTrainでの開発は辞めさせてもらった。コロナが落ち着いてきたらまた遊びに行きたい。

12月

引き続き研究を進めつつ、研究室で機械学習用のPCを買って貰ったので研究しやすいようにそれのサーバー化など、研究してると見せかけて違うこともしていた。
折角研究するなら、学会発表したいと言い続け、年内最後の進捗報告で教授から年明けすぐ締め切りの学会発表申し込もっかとクリスマスプレゼントを頂いた。(年末年始潰れるかと思いましたが、無事提出してこの記事を書いています。)

まとめ

2020年はコロナの影響で、中々遊びに行けない&仕事と研究と趣味のプログラミングが全部同じ場所でしんどいって思う時がかなりありました。
ITは家で仕事できるから良いよねって言われることが結構あるけど、リモートではプロダクトを作っているってより業務を請け負っている感を強く感じてしまいました。
周りでも転職した話を今年は結構聞いたので、家で仕事できていて全てうまく行っている訳では無いかなと思います。
そんな中でも振り返ってみると、2020年はTechTrainの方々や、今までのインターン等で仲良くなった方々のおかげで結構色々挑戦し、スキルの幅が広がったのではないかと思います。
地方国立通っており、大学には自分と同じようなことをやっている人間は少ないので彼らの存在は大きかったです。 (大学や研究室等の周りの方々にもお世話になりました!)
(今日ちょうどこんなツイートを見つけ、同じことが博士以外の自分のような場合でも言えるな〜と思いました。)

2021年の抱負

4月から社会人として働くので、頑張っていきたいです。今まで複数社でインターンしてきましたが、同規模の開発には余り関わってこなかったので新たなスキルを身に着けたい。
また、2020年にAWSを結構勉強したので資格を取りたい。 (他の資格も取りたい。)
そしてその前に、ある程度の成果を研究で出したい。(多分もう大学は卒業できるけど、プロダクト思考寄りの人間でも、やれば理論もいけるってことを証明したいです。)

おわりに

一緒に開発や、近況報告など何でもいつでもお待ちしてるので、声掛けてください!
4月からは東京に行きます!多分!

ISUCON10本戦に参加してきた話

ISUCON10 本戦に参加してきた話

ISUCON10予選に参加してきた話の続き。 予選敗退、、、悔しい。って気持ちだったのですが、ISUCON本戦2週間前になんだかんだあって本戦出場できることになりました。歓喜

メンバー

予選と一緒

本戦まで

事前練習

reudがVultrでインスタンスを4台用意してくれました。
予選ではhtopとnew relicでほぼ雰囲気で計測していたため、計測ツールの導入等練習しました。
具体的には

の導入の練習をしました。

また、予選の環境をインスタンス上に構築する際に運営側がAnsibleで用意してくれていた為、Ansibleを初めて触りました。触った所感として、「Ansibleめっちゃ便利だな」とチームメンバー全員でAnsibleに感動し、計測ツールの導入もAnsibleで行おうという話になり、reudがAnsible周りの用意をしてくれました。神。

後、この記事を読んで

主にはベンチ実行後、alp + pt-query-digestの結果をNginxのドキュメントルート配下にhtml出力し、そのURLをSlack通知するという方法で確認できるようにしていました。 この形式だと結果を中途半端にSlack通知するより視認性も高く、後から見返す時もわかりやすいのでオススメです

とあり、便利そう!ということでalp + pt-query-digestの出力結果をNginxで配信できるように準備しておきました。
その内容としては 定期実行処理としてcrontabに

crontab -e

こんな感じでnginxのアクセスログをalpに通し、sedでmdのいー感じの形式にし、md形式にて出力しました。

*/1 * * * *  cat /var/log/nginx/access.log | alp ltsv -r | sed -e 's/\+/|/g' -e '1d' | head -n -1 > /home/isucon/logs/nginx.md

また、pt-query-digestの方も

*/1 * * * *  sudo pt-query-digest /var/log/mysql/mysql-slow.log > /home/isucon/logs/slow.txt

のように出力しました。

本戦始まってから気づいたのですが、cronでこんなに解析かけるとサーバーになかなか負荷がかかります。その為本戦ではcron外しました、、、良い子はめんどくさがらずにちゃんとベンチマークの実行に合わせて解析をかけるようにしましょう。

景品

当日前に本戦出場記念の景品が届きました! 物が届くと嬉しいですね!

f:id:taka_rock:20201007212936j:plain
f:id:taka_rock:20201007213047j:plainf:id:taka_rock:20201007213040j:plain

当日

やぱったの家にメンバー3人で集まり、オフラインで本戦に挑みました!

f:id:taka_rock:20201007213019j:plain

本戦開始

10:00の開始と共に問題とレギュレーションを読み始めました。文章量かなりあった、、、

概ね読み終えた所でインスタンスに接続し、まずはミドルウェア周りの確認を行いました。

mysqlは8系が入っていることはすぐに分かったのですが、Webサーバーにnginxが用いられていると思いきや入っていない、、、

sudo lsof -i:443

で確認してみるとenvoyが動いている、、、存在は知ってたけど触ったことは無かったのでconfigのymlを雰囲気で読みました。

確認後はmysqlの設定でslow-queryのログをオンにし、pt-query-digestで解析できるようにしたり、別インスタンスからのアクセスを許可したり、軽くindexを貼ったりしました。

メンバーの方もUbuntuのバージョンによる違いによりansibleが上手く動かない等、かなり手こずっていました

また、alpでアクセスログの解析を行おうとしたものの、envoyのログをalpで欲しい形式で出力するのに時間がかかってしまい、結局やめました。(ltsvで食わせて上げる必要があると思っていたのですがalpのREADMEをちゃんと読んでみると解析に必要な情報が書いてありますね、、、本番中にも読んだのですが、焦っていて読み飛ばしていました)

サーバー構成

reudがneofetchを用いてサーバー3台のスペックを確認してくれて、3台ともスペックが違う事が判明しました。(3台のスペックが違うなんて思ってもいなかった)

  • サーバー1
    • CPU 2コア
    • メモリ 1GB
  • サーバー2
    • CPU 2コア
    • メモリ 2GB
  • サーバー3
    • CPU 4コア
    • メモリ 1GB

その為、初期状態でベンチを回した場合もスコアはバラけてました

  • サーバー1
    • 4973
  • サーバー2
    • 5617
  • サーバー3
    • 6887

netdataで負荷を確認しながらベンチを何度か回して、サーバーの構成を入れ替え、DBのCPU負荷が高いため最終的に

  • サーバー1
    • envoy
  • サーバー2
  • サーバー3
    • DB

の構成にしました。 多分この辺でスコアが多分7152点。

TeamCapacity

サーバーを分散し、CPU負荷に余裕があることが確認できた為、何かしらのパラメータでアクセス数のlimitにあたっているのでは無いかと考えました。メンバーに話すとTeamCapacityなるパラメータがある事が判明。 10→50へ上げてもらう。これでスコアが多分10010点。 良さげなTeamCapacityを探すために一旦120にあげて、ベンチを回したりしてみるも通らず60にてfix(負荷がかかりすぎるとenvoyが死んでしまっていたっぽい。open files limitに引っかかってしまっていた??)

WebPush

やぱったとreudが実装してくれました。 途中で実装手伝おうと思いドキュメント読んだものの、しっかり読まないと何も分からなかった為何も手伝えず。実装感謝です。これで多分14314点

Goのコードをい〜かんじに

Goのコードをい〜かんじにしようとしたものの時間が足らずにほぼできませんでした。

ラスト1時間

諸々あって、ベンチが通らなくなってしまいかなり焦りました。

f:id:taka_rock:20201008005658p:plain

終了時間ぎりぎりでベンチが通ったためそこでfinish

結果

failしてました

最後のベンチが通ったのでいけると思ったのですが、ランダムでベンチが落ちる状態だったようです。 出ていたエラーとしては下記

  • あるべき通知を受信していないことが検知されました
  • 最終検証にて Clarification の検証に失敗しました

悔しいです。

振り返り

競技中はやらなきゃいけないことが多すぎて終了時間まであっという間でした。その振り返りとして、やることとやらないことをしっかりと判断するべきだったと感じています。

具体的には、計測ツールの導入で手間取る箇所やenvoyの理解など競技のスコアアップには本質的には繋がらない箇所はある程度割り切って自分たちのできる所で更にスコアアップを狙うべきだったと思います。

できることをやるの大切。

後、マニュアルにキャッシュ可と書いてあったものの最小工数での良い方法が思いつかなかったです。他チームではVarnishというものをenvoyとwebの間でキャッシュしていたそう。知らなかった為、勉強する。

来年は社会人枠で本戦出場目指して頑張ります。

P.S.

メンバーでオフラインで集まって参加するのはとても楽しかったです。 来年はオフラインで開催できてると良いな。

ISUCON10予選に参加してきた話

ISUCON10予選に参加してきた話

ISUCON10予選に参加してきました。 ブログを書くまでがISUCON予選?と言われているので今回はISUCON10予選について振り返っていきたいと思います。

ISUCONとは?

いい感じにスピードアップコンテスト、略してISUCONです。 その名の通り、サーバーの高速化をしたりしていい感じにスピードアップして戦います。

本選へ参加するための予選があり、下記の条件で30チーム選出されます。

  1. 一般枠 (25チーム):予選終了時スコアにおける上位25チーム
  2. 学生枠 (5チーム):学生チームの中で、予選終了時スコアにおける上位5チーム

結果は?

結果は、1122点でした。 Twitterで見た所、学生枠の5位だった方が1240くらいだったそうなので学生枠での本戦出場あと一歩でした、、、
下記が作業レポジトリです。(機密情報が含まれていた為、commitを1つに固めてます) https://github.com/reud/01NOCUSI

誰と出たの?

ワンランク上のジロリアンというチームで3人で出場しました。

メンバーの役割分担としてはそこまで明確には決めてなく、インフラとアプリケーション(Go)のコードを3人でい〜感じに分担して作業する感じでした。
Discordを使って常に通話しながらのフルオンラインで競技に参加しましたが、特に問題なく競技を行えました。

参加までの準備

事前にISUCON9の予選を用いて少し練習していました。
reudがConoHaでVPSを借りてくれたので、そこに諸々立てました。
3人ともNginx, MySQLのチューニング等したことが無かった為、皆で介抱し合いながら色々勉強していました。
VPS借りてくれたのめっちゃありがたかった、、、

  • 皆で勉強したこと
    • N+1の対策方法
    • インデックスの貼りどころ
    • New Relicを導入しての計測
    • Nginxの設定チューニング
    • Mysqlの設定チューニング
    • インスタンスを複数立てた際の設定方法
    • インメモリキャッシュ
    • Goでの非同期処理
    • etc

基本的な所の勉強&参加者のブログを読んで自分たちも実際にやってみたって感じですね。参加ブログ感謝。

当日

開始まで

10:00開始だったものの、朝起きたら12:00開始に変更になっていました。時間の余裕ができた為、10:00からDiscordに集まりながらSlackのワークスペースを作ったり、便利コマンドや困ったときの記事をSlackに貼りまくっていました。開始延期ありがたい。 そんなこんなで12:20開始に変更になり、ゆるゆると開始待機していました。

12:20 競技開始

開始と同時に、チームメンバー全員で予選マニュアルをしっかり読みました。

踏み台経由のSSHをしたこと無かったため、 ssh_configの例がマニュアルに書いてあったのめちゃくちゃ助かりました。

 Host isucon-bastion
   HostName <踏み台用IPアドレス>
   Port 20340
   User isucon

 Host isucon-server
   ProxyJump isucon-bastion
   User isucon
   HostName <自チームサーバのIPアドレス>

インスタンスに接続して最初に、ディレクトリ構成を確認しました。 Webアプリケーションは/home/isucon/isuumo/webapp以下を触れば良さそうだったのでチームメイトにGit管理の追加&Githubのプライベートレポジトリの連携等頼みました。Git管理は必須ですね。 次はNginx, MySQLのデフォルトの設定を確認したりしていました。

とりあえずベンチを走らせようと思ったもののまだ走らせる事ができなかったので、ソースコードの確認やreudがnew relicを導入してくれたりしていました。仕事が早い。

13:30

ベンチを動かせるようになったので動かしました。 493点。なるほど。

13:40

やぱったがインスタンスのメモリを確認してくれました。
2GB、、、?めっちゃ低スペじゃね??って話したりしていた記憶がある。

new relicの計測結果や、ソースコード見た感じでデータベースチューニングゲーか、、、?と強く感じてきた頃。SQLクエリとschemaとにらめっこし始めました。
インデックス貼ろうにもwhere句とORDER BYが多すぎて辛い。

14:00

マニュアルに下記の記述が書いてあったことを思い出しました。

bot からのアクセスはコンバージョンに繋がらないため、弾くことが仕様として決定しましたが、まだ実装されていません。 bot は User-Agent が以下の正規表現にマッチする形式であり、このリクエストに対して 503 Service Unavailable を返すことが許可されています。 これに対するベンチマーカーからの減点は発生しません。

やぱたが正規表現の神(正規表現エンジンとか作ってた)なので該当UAからのアクセスは503を返すようにNginxの設定をお願いしました。/etc/nginx/sites-enabled/isuumo.confに下記を追加していく感じ

if ($http_user_agent ~* "^ISUCONbot(-Mobile)?$") {
  return 503;
}

点数が上がった気がする。(確かこの頃ベンチマークのキューを積めなくてすぐに計測できなかった)

クエリ見てみるページング処理がとLIMIT OFFSETを用いて記述されている。これを良い感じにしようと思ったが、条件が複雑でどうやってページングすればよいのか分からず断念、、、(これどうやってページングするのがベストだったんですかね、、、?)

この辺で、スキーマをデータ構造に最適っぽやつに変更して、インデックスを追加してみた。(これもベンチで計測できなかった為お気持ち早くなった気がする)

Webからアプリを操作していたら、画像ファイルの配信が遅かったのでNginxで静的ファイルのキャッシュをすれば早くなるような気がしたのでキャッシュの設定を追加する。(マニュアルしっかり読んだら、「ベンチマークからのリクエストは、 API に対してのみ行われます。」と書かれているので静的ファイル周りの設定は今回は何も意味ないですね。最後にキャッシュ周りの設定を剥がしました。マニュアルしっかり読みましょう。)

16:00

reudがhtopコマンドを用いて負荷計測する事を提案してくれる。
CPU使用率が100%貼り付き、、、
処理の流れ的にもMySQLの負荷が凄い事は確実なのでMySQLインスタンスを増やそうって話になるものの17:00まではインスタンス1台でスコア上げていく方針で作業を進める。

17:00

MySQLインスタンス増やそうにも、構成どうするって話になる。

ここが今回1スコアが上がったターニングポイント

3人とも別の手法提案していてどれを採用するか結構話し合った

こんな感じでどれを採用するかなかなかまとまらない為、とりあえずGoで1台, MySQLで1台の2台構成にして計測してから考えようって話になる。

ここで、Goから別のインスタンスMySQLに接続切り替えるのに結構手間取ってしまった、、、
やったこととしては/etc/mysql/mysql.conf.d/mysqld.cnfへ他インスタンスのIPをbind-address=xx.xxx.xx.xxxで追加して、Goのインスタンスからmysql -u isucon --host=xx.xx.xx.xxx --port=3306 -pで接続できることを確認。
接続できたので問題ないだろうとWebからページの表示を確認した所500が返ってくる、、、みたいな状況になり、色々話しているうちにユーザー+ホストでMySQLユーザー権限の管理がされている事を思い出し、各インスタンスに紐づくユーザーを追加した所、問題なく接続できました。(複数インスタンスでの接続練習しておくべきだった、、、)

やっとGoで1台, MySQLで1台で動作成功し、ベンチを走らせる。
スコアは覚えていないが、あれこの構成でも結構スコア上がってね??ってなった

19:00

インスタンス構成は、色々話しているうちにreudの案を元にNginx+Goで1台,テーブル毎に分割したMySQL2台を用意して、Go側でDB2台の接続情報を持つのが一番楽&安全でスコア上がりそうじゃね??って話になった為その構成に変更。

reudが爆速でGoでの2台DB接続処理を書いてくれました。早すぎて震えた。

ベンチを走らせるとスコア881で学生順位15位くらいに食い込んで、「あれ、本戦出場目指せるんじゃね??」という雰囲気が出て、インスタンス構成はこれで確定。

MySQLinnodb_buffer_pool_sizeを2GBにしたりGo側の処理をいーかんじにしてもらい再度ベンチを走らせる。
スコア1075で学生順位7位で本戦出場が本当に見えてくる。

f:id:taka_rock:20200914035139p:plain
学生7位

20:00

ミドルウェアのログ出力をオフにしたり、new relicをGoのコードから剥がしたりと撤退作業を始めました。
諸々作業完了後に計測すると1122点。他のチームがそこまでスコア上がっていないことを願い、これ以上のスコアUPは諦めました。

21:00

終わり。

結果

学生枠でも本戦出場に食い込めませんでした、、、

振り返り

結果発表待ちの時に気づいたのですが、MySQLの設定のinnodb_buffer_pool_sizeってメモリ上に展開するデータサイズで、それ+query_cache_sizeでクエリの実行結果もどんどんメモリ上にキャッシュしていくことができるんですね、この事を知らずにメモリの空きがある事は確認できていたものの、query_cacheのチューニングを行いませんでした、、、、メモリ限界までキャッシュさせてればもっとスコアあがったと思うとかなり悔しいです。勉強不足です。(再起動テストを行っていなかったため、結局Failで落ちていたっちゃ落ちていたかも)

またスコアに繋がる打ち手を全然自分で出せておらず、あまりチームに貢献できなかったなと感じています。

細かく振り返ってみると次の点が改善できたと思っています。

  • 計測を更に行えるように
    • 計測の練習ほぼしておらず、当日ぶっつけで計測したので、更に良い計測方法があったはず、、、来年は推測せずに、計測します。
  • Goの練習を直前にもっとしておくべきだった
    • Goは書いたことがあるものの、複雑な処理を記述すのには時間がかかってしまう位のレベル感なのでGoの最適化をチームメイトに全て任せる形になってしまいました。問題点見つけて自分で爆速で修正できるレベルまで練習しておくべし。
  • ミドルウェアの設定内容を深く理解しておく
    • query_cacheの件など理解しきれていない点が存在した。細かいパラメータを完全に理解しなくとも頭の中の引き出しは増やしておくべし。

まとめ

ギリギリ、本戦出場できなかったのでかなり悔しいです。精進します。

来年の意気込み

社会人枠で本戦出場目指します。

P.S.

DISCORDに書いてあった他の人の打ち手

  • MySQL5.7 → 8に変更する
    • 8に変更すると早くなるんですね、、、普段5系ばかり使っているので知らなかった
  • generated columns
  • Special index

はじめてのKubernetes

About

Kubernetesをはじめて触ってみた。
その際の記録。

環境構築

Docker for MacではKubernetesがサポートされている。
Docker for MacのPreferenceを開き、KubernetesタブからEnable Kubernetesにチェックを入れればKubernetesのインストールが始まり、少し待つとKubernetesが使用できるようになる。とても簡単!
f:id:taka_rock:20191110183857p:plain kubectl cluster-infoコマンドを実行し、次のような結果が返って来れば導入成功!

Kubernetes master is running at https://kubernetes.docker.internal:6443
KubeDNS is running at https://kubernetes.docker.internal:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

やってみた

Kubernetsでは「マニフェスト」と呼ばれる定義ファイルに「リソース」を記述して登録することで、コンテナを実行する。

  • Word List
    • Pod
      • コンテナの管理に使用するリソース。1つ以上のコンテナのグループを表し、Kubenetsにおけるコンテナ管理の基本単位である。
    • Deployment
      • 実行するPodの数とそのPodの定義テンプレートを含むリソース。複数のPodを管理する為に使用する。

Podの定義例
nginxコンテナを実行するPodの定義

apiVersion: v1
    kind: Pod
    metadata: 
    name: nginx
spec:
    cantainers:
    - name: nginx
        image: nginx:1.15.7
    ports:
    - containerPort: 80

Deploymentの定義例
ngixコンテナを実行するPodを3個起動する定義

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

コンテナを起動してみる

上記Deploymentの定義を記述したマニュフェストファイルをdeployment.yamlとして保存する。 kubectl applyコマンドを実行してKubenetesにマニフェストファイルの内容を実行する。

kubectl apply -f deployment.yaml

kubectl get podsコマンドを実行してKubenetesのクラスタ上にPodが起動しているかを確認する。
下記のような結果が返って来れば実行成功!

NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-6dd86d77d-5sgtm   1/1     Running   0          100m
nginx-deployment-6dd86d77d-cphxc   1/1     Running   0          100m
nginx-deployment-6dd86d77d-k4kkd   1/1     Running   0          100m

Podと通信してみる

Deploymentを用いてPodを起動し、コンテナを建てられたので実際に通信してみる。

  • Word List
    • Service
      • 複数のPodへの負荷分散を実現する為のリソース
        • Serviceの定義を元にロードバランシングできる

Serviceの定義例
先程のngixコンテナを実行するPodへポート8080への通信をPodのポート80へ転送する定義

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 80

Deploymentの登録と同様に、上記Serviceの定義を記述したマニュフェストファイルをservice.yamlとして保存し、kubectl applyコマンドを実行してKubenetesにマニフェストファイルの内容を実行する。

kubectl apply -f service.yaml

kubectl get serviceコマンドを実行してServiceの状態を確認する。

NAME             TYPE          CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
kubernetes      ClusterIP      10.96.0.1      <none>        443/TCP    20h
nginx-service   LoadBalancer   10.105.91.94   localhost     8080:31494 TCP   102m

nginx-serviceがlocalhost:8080で待ち構えている事が分かる。
実際にhttp://localhost:8080へアクセスし、nginxの標準ページが表示されればService経由でのPodとの通信が成功! f:id:taka_rock:20191110193025p:plain

感想

Docker for MacKubernetesがサポートされている為、とても簡単にHello worldができ、コンテナの複数起動&ロードバランシングもとても簡単に行えた。
更に触れ、実用的なメリットも学んでいきたい。