AWS Lambdaを使ってベンチサーバー無しで社内ISUCONを運営したはなし

新人研修担当の長田です。

今年も新人研修の締めとして社内ISUCONを行いました。

昨年はプログラミング基礎の講師をやったのですが、 今年はその実績を買われて(?)社内ISUCONの出題を担当することになりました。

過去のISUCON準備の様子を傍から見ていた身としては、 準備を始める前から「とにかく大変そうだ・・・」というイメージを持っていました。 問題を作りこむ以上、どうしてもISUCON当日ぎりぎりまでかかってしまうのでしょう。

ぎりぎりになるのはまあ準備する人が頑張ればいいとして、 ぎりぎりになった結果競技自体の進行が危ぶまれるのは避けたい! ということで、いくつか効率化という名の妥協策をとることにしました。

効率化できるところは?

毎回新規に出題するのはしんどい!

社内ISUCONは過去2回実施していますが、 どちらも新規に高速化対象のWebアプリケーションを作成していました。 アプリケーションが異なるということは難易度調整も毎回行わなければいけません。 ベンチマーカーの動作確認も毎回行わなければいけません。

確認事項が増えるということは確認漏れが発生する可能性も増えるわけで、 重大な漏れがあった場合には最悪競技続行不可能になってしまうかもしれません。

社内ISUCONは新人研修の一環として行います。 新人・・・ということは毎回別の人が競技を行うということです。 つまり問題は変えなくてもみんな新鮮な気持ちでトライできる! ということで問題の作りこみは昨年のものに多少手を加えるにとどめました

競技にゲスト参加している先輩社員が有利にはなってしまいますが、 まー1年も経てば内容なんて忘れているでしょう。 大事なのは「研修を受けた新人が成果を確認できる」ことです。

できるだけ楽にセットアップしたい!

ISUCONを実施する上で準備しなければならないものはざっくり以下のとおりです。

  • チーム数分の競技サーバー
  • ベンチマーク実行サーバー
  • ベンチマーク結果集計サーバー

3つのアプリケーションを同時に用意しななければならないわけです。 これは手慣れていないとなかなか厳しい。

「競技サーバー」は減らすことはできません。 競技の性質からそこは仕方がありません。 減らせるとすれば運用用である「ベンチマーク実行サーバー」と「ベンチマーク結果集計サーバー」です。

と、いうわけで今回は以下のように各サービスで置き換えることにしました。

  • ベンチマーク実行サーバー -> AWS Lambda
  • ベンチマーク結果集計サーバー -> Mackerel

この置換えを行うことで、競技運用に必要なサーバーが必要なくなりました

ベンチマーク渋滞を無くしたい!

複数のチームが同時にベンチマーク開始をリクエストした場合、 どちらかのチームの処理を先に行い、もう一方のチームの処理は順番待ち状態になってしまいます。 かと言ってベンチマーク処理を並列実行すると、 ベンチマーカーのリソース管理をうまくやらないとベンチマークサーバーの状態によってスコアが変動してしまいます。 チーム数分のベンチマークサーバーを用意するのが理想ですが、 ベンチマークを実行するサーバーはそれなりのスペックが必要で、それなりのスペックのサーバーをチーム数分用意するのはそれなりにお金がかかります。 第一そんな数のサーバーをセットアップ&動作確認するのはしんどいのでやりたくありません。

今回ベンチマークの実行をLambdaで行うことにしたため、 副作用としてベンチマークをいくらでも並列実行できるようになりました。

なんだか夢の様な仕組みですが、デメリットがなかったわけではありません。 改善点についてはこの後書きます。

ベンチマーカーについて

ベンチマークの実行

競技対象のWebアプリケーションが昨年のものと同じなので、 ベンチマーカーもおおよそ昨年と同じものを使いまわしています。 代わりにLambda上で動かすために手を加えています。

f:id:sfujiwara:20170523182306p:plain

ベンチマーカーはGoで書き、 Lambda functionの管理にはapexを利用しました。 apexを使うとGo以外の言語を一切書かずにLambdaでGoバイナリを動かすことができます。

ひとつのworkerは20並列で競技サーバーにベンチマークをかけるようになっています。 問題アプリケーションの初期状態では20並列のリクエストをさばくだけでCPUを使い切るようになっていますが、 チューニングを加えていくことで20並列ではマシンパワーが余るようになってきます。 より高い負荷が必要な場合はkickerからベンチリクエストを送る際に-workloadというパラメータを指定すると、 指定した数だけのworkerが実行されるようになっています。

得点配分

スコアは以下の式で計算しました。


スコア = pv / (0.3 + 通信量(GB) * 0.14)

式中に出てくるマジックナンバーの出自はというと、

  • 0.3
    • もろもろの固定費(USD)
  • 0.14
    • Amazon EC2の通信料(USD)を丸めたもの
    • 東京リージョンでひと月内に1GB〜10TBの範囲で通信した場合の料金

つまり、スコアは「$1でさばけるpv数」を表していました。 アプリケーションのチューニングによって運用コストがいかに下げられるのかを示した計算方法となっています。

社内ISUCONの結果

f:id:sfujiwara:20170523182310p:plain

初期スコアは1700程度、優勝チームは50355(グラフ中の水色)、模範解答は103379(グラフ中の抹茶)でした。 以下各チームのスコア


          boko :   62793 (fail)
  yokohama-zoo :   50963
  moulin-rouge :   42336
chatzmers-mini :   33126
 yokohama-rare :   23579
      sukiyaki :   16106
       ninjari*:   14823
           nil :   14309 (インターン+アドバイザーの先輩混合チーム)
          neck*:    3469
   haagen-dazs*:    3296
    takeiteasy*:    1679
      yurufuwa*:       0 (fail)
           333*:       0 (fail)

     acidlemon :  103379 (模範解答)

*が付いているのは新卒チーム

今年も先輩チームは面目を保ったようです。 スコアだけで見ると1位のチーム「boko」ですが、最後に攻めすぎたせいでfailしてしまったようです。

基本的な改善点(ループ内でのクエリ発行、適切なインデックス作成)を潰していけば、 スコア10000程度は余裕で超えられるようになっていたので、新卒諸氏にはもうちょっと頑張ってもらいたかったな〜という感想です。

改善点

-workloadの値で試行錯誤しちゃう問題

過去のベンチマーカーの実装に倣って、ベンチマーク実行時に-workloadというパラメータを設定できるようにしていました。 下手にいじれる要素があることで、そこをいじってスコアを上げようとしてしまうチームが出てきてしまいました。 残念ながらというか必然というか、この方法を試みたチームはことごとくfailしていたようです。

本来-workloadはアプリケーションのチューニングが充分にされた状態で、 負荷を上げる以外にスコアを上げる方法がない場合の最後の手段として指定するものです。 これが競技者に充分伝わっていなかったために余計な試行錯誤の時間をとらせてしまった感があります。

理想としては-workloadパラメータなしで、常に対象アプリケーションに限界の負荷がかかり、 かつ負荷のかけ過ぎでfailしないベンチマーク方法になっていればいいのですが・・・。

なお、-workloadは本家ISUCO3で導入されたパラメータですが、 これは負荷を上げすぎてしまうと初期状態の対象アプリケーションがfailしてしまう、という問題を回避する苦肉の策とのことです。 (by 当時の出題者のfujiwaraさん)

スコアが伸び悩んでいるチームへの手助け

2週間の研修を受けたとはいえ、新卒チームはサーバーでの操作に慣れていない初心者です。 各チームのスコアは随時確認していたので、これを見ながら伸び悩んでいるチームには助言をしてもよかったように思いました。 何をすればいいのかわからず競技時間が過ぎてしまうのはもったいないですし、なにより楽しくありません。

ベンチマーカーにスコアに応じてヒントを出す機能をつけたり、 予めチェックするべき要素リストを作って渡しておくなど、 新人研修用にアレンジを加える必要があるように思いました。

Lambda function実行ホストのTIME_WAIT

仮想化されているとはいえ、コードを実行するのは実在するサーバーです。 ベンチマーク対象のアプリケーションがkeep aliveを有効にしていない場合、 ベンチマーカー側でTIME_WAITが大量に溜まってしまう、という問題がありました。 実サーバー上でベンチマークを行なっている場合は調整のしようがあるのですが、 managedなサービスであるLambdaでは手が出せません。

最終計測はTIME_WAITが無くなった頃を見計らって、ひとチームずつ順に行いました。 Lambdaだから全チーム同時に最終計測ができるぞー!と妄想していた頃がぼくにもありました。

名前解決の失敗

ベンチマークを繰り返すうちに、ベンチマーカー上で以下のようなエラーが発生することが有りました。


lookup {競技ホスト名} on 10.153.0.2:53: dial udp 10.153.0.2:53: socket: too many open files

どうやら名前解決に失敗しているようだということで、 ベンチマーク開始時に一度だけ名前解決を行い以降はIPアドレス+Hostヘッダーでリクエストを行うよう手を加えました。

当日は出題者もヒマじゃない!

競技の準備は前日までに(といっても深夜までかかりましたが・・・)無事完了したので、 当日はゆうゆうと観戦できるんじゃないかと思っていたのですがそんなことはありませんでした。 誰かがfailするたびに「ベンチマーカーが原因なんじゃないか・・・」と原因を探り、 「どこでfailしたのかが知りたいんだけどー」というリクエストに答えてログ表示機能を追加し、 あるいは前項のようなエラーに対応し・・・、 と常時ベンチマーカーの改善を行っていたので全然暇にはなりませんでした。

社内ISUCONでこれだけベンチマーカーの挙動にいちいちヒヤヒヤするのだから、 本家ISUCONの出題者の方々には頭がさがる思いでいっぱいです。

来年へ

Amazon Lambdaでベンチマークを実行するという方法は、 セットアップなど諸々の作業や問題をスキップできるナイスな選択だったと思います。 次回はベンチマーカーのチューニングは程々に、対象アプリケーションの作り込みに時間をかけられるようになる・・・のかな? まだ出題者が誰になるかはわかりませんが、今回作った仕組みが生かせれば幸いです。

今年の社内ISUCONで悔しい思いをした新卒たちが、 1年後に入ってくる2017年の新卒チームをバッタバッタとなぎ倒してくれることでしょう。

関連エントリ

カヤックではISUCONに興味があるエンジニアも募集中です!