データアナリストが社内ISUCONで得たこと

はじめに

2019年4月にWantedly, Inc. に入社した奥山です. 

私は現在社会人4年目で, 大学では物理化学を, 卒業してからは住友電工に入社し光ファイバの研究を行ってきました(プロフィール)

IT関係のバックグラウンドとしては, 実験データを解析するPythonアプリケーションや機械学習を使った材料開発などがあります. Wantedlyでは現在データ分析を主に行っています.

私はWeb未経験と特殊な経歴ですが, 弊社が新人研修として行っているISUCONに参加した際にとても得られることが多く, データアナリスト(DA)やデータサイエンティスト(DS)にこそ経験してほしいものだったのでどんなところが良かったかまとめます.

ISUCONとは

ISUCON(Iikanjini Speed Up Contest)の略で, Webアプリケーションの高速化を競う大会です. LINE株式会社が主催で, 毎年いろんな会社が問題(あえてスピードを落としたWebアプリ)を事前に作成し, 3人1組のチームでそれを高速化してスコアを競います.  f:id:spring1018:20190520101451p:plain サーバをたててアクセスするとこんな感じの画面が表示されます. 今回の新人研修ではYahoo! JAPAN株式会社が公開しているY!SUCONという問題(↑の画像)を使いました.

研修内容

研修は実際とは異なる二人一組で行いました. 二人で行う理由は新卒研修で行うISUCONの魅力にあるように, 一人が担当する範囲を広げて普段触れられない分野にも挑戦できるようするためです. 今回ペアを組んだ@euglena1215はISUCON経験者で, ISUCONに必要な事前準備は何か, どういう思考プロセスで高速化するかなどを下記のブログでまとめています. Web開発経験者の視点はこちらを参考にしてください. 

euglena1215.hatenablog.jp

Wantedlyの開発ではrailsを使用しているので今回はRubyを使用しました(ISUCONはいろいろな言語で取り組めるよう準備されています)

ISUCONスタート!

ISUCONを進める大きな流れとしては次になります.

  1. アプリケーション環境のセットアップ
  2. 起動確認後, ベンチマーク走行(ここで初期スコアが得られる)
  3. データベースの確認
  4. モニタリング環境のセットアップとボトルネックの特定
  5. ソースコードボトルネックとなっている部分を修正
  6. ベンチマーク走行しスコアを確認

ボトルネックの特定→コードの修正→スコア測定の流れ(4→5→6)を繰り返していきます. 

1. 環境セットアップ

AWS EC2などでインスタンスを作成し, ssh接続・参照実装の切り替えを行いアプリの画面が表示できることを確認します. このあたりの話はレギュレーションに書いてあるのでそれに従うと良いです. 環境構築はつまづくことが多いので弊社では研修の事前説明時に全体で確認しました(実際予期せぬトラブルも発生していたので, これについてはメンターが事前に確認した問題に取り組むのが良いかもしれません)

2. ベンチマーク走行

起動が確認できたら次はベンチマークを走らせて初期スコアを計測します. ベンチマークは, ベンチマークサーバから各チームのサーバにリクエストし, それをどれくらい処理できたかによってスコアが決まります. 算出方法はレギュレーションに記載されており, 例えば成功レスポンス数(GET) x 1 + 成功レスポンス数(POST) x 2 - (サーバエラー(error)レスポンス数 x 10 + リクエスト失敗(exception)数 x 20 + 遅延POSTレスポンス数 x 100 のようにスコアが算出されます. ここまではレギュレーションに従うだけでよいので, 次からがISUCONのスタートです.

3. データベースの確認

使用されているDB・Table・カラム・indexの有無・レコード数を全て確認します. 特にindexに関しては, 実行数の多いクエリが参照しているカラムにindexを貼るだけでスコアが大きく改善する場合があります. またレコード数についても, 今回は10万行を超えるようなものがあり, そういったテーブルに対してどういうクエリが実行されているかは要チェックです. これらのDBの情報は全てホワイトボードに書き出し, 競技中はいつでも確認できるようにしました.

4. モニタリング環境のセットアップ

今回使用したmyprofilerとkataribeについて紹介します. 

myprofiler

今回のISUCONではDBにMySQLが使用されていたので, myprofilerによりどのクエリに時間がかかっているかを調べることができます. ISUCONは初期状態ではあえて効率の悪いコードを書いているので, 本来1回のクエリで取得できる内容をソースコードのループ内で何度も実行していたりします(ループクエリ, N+1問題). そういったクエリはmyprofilerにより検出できるので, ソースコードを直す際にどのクエリを修正すべきかが簡単にわかります.

32 SELECT * FROM tweets ORDER BY created_at DESC
15 SELECT * FROM tweets WHERE created_at < S ORDER BY created_at DESC
3 SELECT * FROM tweets WHERE user_id = S ORDER BY created_at DESC
3 SELECT * FROM users WHERE id = S

kataribe

kataribeは時間がかかっているリクエストを調べることができます.

Count   Total      Mean    Stddev    Min   Max   2xx  3xx  Request
 1466  62.976  0.042958  0.025205  0.106  0.132  1466    0  GET / HTTP/1.1
  740  42.094  0.056884  0.018947  0.104  0.137   740    0  GET /?append=1&until=*
  293  13.969  0.047676  0.018845  0.102  0.119   293    0  GET /hashtag/*

競技中はソースコード全てを直す時間はないので, これらのモニタリングツールを使って分かったボトルネックを修正→再計測による次のボトルネックの特定, の流れを繰り返していきます.

5. ソースコードの修正

まずは上で特定したスロークエリを修正します. 下の例はループ内で無駄にクエリを実行しています.

# ループの中でクエリを実行している
user_ids.each do |user_id|
  statement = db.prepare('SELECT * FROM tweet_log WHERE user_id = ?')
  rows = statement.execute(user_id)
# ループの外で一回だけ実行する
statement = db.prepare(
<<~SQL
  SELECT
    *
  FROM 
    tweet_log
  WHERE
    user_id = ?
SQL
)
rows = statement.execute(user_id)
statement.close
hoge = rows.map do |row|

次にコストの高いクエリがないかを確認します. よくある例として, 使用しないカラムも全て取得している場合です.

-- 変更前: *で全てのカラムを取得している
SELECT * FROM TABLE;
-- 変更後: ソースコードをよく見ると, 実はnameしか使わないのでnameだけでよい
SELECT name FROM TABLE;

またレコード数レベルでも同じようなケースがあります. 例えばアプリケーションとしては最新データ50件だけ表示する処理になっているのに, クエリは全レコードを取得するようになっていることがあります.

-- 変更前: 全てのレコードを取得している
SELECT name FROM TABLE ORDER BY created_at DESC;
-- 変更後: クエリの段階で50件に絞っておく
SELECT name FROM TABLE ORDER BY created_at DESC LIMIT 50;

他にも, 重い処理であるJOINを回避できるのに実行していることもあります. この場合は非正規化を行ってJOINを回避するのも手です.

普段の開発ではこういうひっかけ問題のようなコードには意外と出会わないかもしれないので, ISUCONではそういう目でコードを見るのが重要かもしれないです.

6. ベンチマーク走行

コードを修正したらまずはベンチマークを走らせます. コードのバグやベンチマークの条件を満たしていない場合はエラーとなるのでこの確認作業は必須です. 修正によって逆にスコアが下がることもあるので, 現在のスコアと比較しながら進めます. 実際のISUCONでは最終的なスコアはサーバ再起動後に測定するので, 再起動してもベンチマークがエラーにならないかを確認しておくとよいです. 無事走行できたら今度はmyprofilerやkataribeを起動した状態でベンチマークを実行し, 次のボトルネックを発見, 改善のサイクルを回していきます.

ここまでが大まかなISUCONの流れです.

その他必要なこと

事前に準備しておくとよいものを挙げておきます.

git管理

セットアップにやや時間がかかりますが, git管理は行った方が良いです. メリットとして,

  • コード修正作業は各自で行うので, どの変更がスコアに影響したか, またエラーの原因になったかが分かる.
  • masterを常にエラーのない状態に保つ運用ができるので, 一定のスコアが出る状態を確保できる(競技終了時の最終スコア計測でスコア0となる悲しい事態を回避できる)

の二点があり, 時間がかかってもセットアップする方がよいです.

systemd

起動処理やログ管理を行う上でsystemdのコマンドを使う必要があります. 再起動時に自動起動するサービスの設定はルール上必須ですし, コード変更した場合のrestartも頻繁に行うので事前に把握しておくとよいです. 今回は事前にrestart時に使用する.shファイルを作成してコマンド一発でできるようにしていました.

Nginx

正直私はまだ細かい設定は理解できていないです笑. ペアの@euglena1215が事前に設定ファイルを用意し, 細かい設定も行ってくれたのでとてもスムーズでした. 今回のISUCONでは, 他チームのブログにあるようにエラーログを確認したり, ある程度ソースコードの修正が終わった後にworker数を変えてベンチマークを走らせたりする運用は誰でもできた方が良いとは思います.

sinatra

今回はRubyを選択していたので, フレームワークsinatraでした.

redis

ログを残す必要のない部分はredisに置き換えられる可能性があります. 事前練習では導入した際にスコアが大きく上がったので本番でも導入する予定でしたが, 時間の都合上導入はやめました.

複数台構成

複数台にすれば必ず上がるとも限らないのですが, 状況によっては1→3台構成にすると2倍以上スコアが上がります(事前練習で使用した問題はこのケースでした). 実際のISUCONでも各チームに複数台与えられるので, 予選通過には必須の知識だと思います.

まとめ

ここまででISUCONで取り組む内容と, 使用するミドルウェアやモニタリングツールを紹介しました. ここからはデータアナリスト(DA)として今回のISUCONを通じて得られたものをまとめます.

  • 実際のWeb開発の流れがわかる

私はスキル・経歴的には冒頭に紹介した通りで, Web開発の実際の流れがわからないと常々感じていましたが, ISUCONではWebアプリケーションのソースコードを実際に修正する過程でWeb開発を手早く経験できると感じました.

  • OS・ミドルウェア・モニタリングと様々な領域に触れることができる

これも実際に手を動かせるので学習効率が高いです. 広く浅く経験できるのはDAにとってメリットが大きいと思います.

  • 他言語の勉強になる

自分の場合はRUbyの知識がなかったのでISUCONをきっかけにRubyの勉強ができたのは有益でした.

  • スコアが出て楽しい

ISUCONを研修でやるとスコアが出るので達成感が得られますし, 成長を感じられます. DSはkaggleをやっている人が多いと思いますが順位が上がると嬉しいですよね!

また, しいてデメリットもあげるなら問題を準備するのが大変というのはあると思います. 今回はYahoo! JAPAN株式会社の問題を使ってはいますが, レギュレーション作成やスコア監視体制など一定の準備はメンターに行ってもらっています.

DAやDSがISUCONに一人で取り組むのはなかなか骨が折れるので, 可能なら詳しい人とチームを組むのが一番良いと思います. ぜひISUCONに挑戦して, Web開発を広く経験してみてください!