ISUCON3 を開催しました

参加者の皆様、共催で運営となった LINE, DataHotel, カヤック各社の皆様、本当にありがとうございました。いくつかトラブルがあったものの、本選もなんとか無事に終えることができました。

まずは優勝した LINE 選抜チームの皆様、おめでとうございます!なかなか初期スコアから上がってこないので内心ものすごく心配していましたが、ポイントを見極めて作業が終わったところで一気にスコアを上げてきたのは感服しました。

本選終了から48時間経過したいまでも頭の疲労が回復しきっていない感じで、整理できていないので思うままにつらつら書きます。

以下長文になってしまうので最初に告知です。

ISUCON3反省会 というイベントを 11/15(金) に行います。ISUCON参加者でなくてもどなたでも無料でプレミアムモルツ飲み放題ですので、日時が迫っていますが是非お越しください。

出題内容について

お題は以下のような感じです。

  • 画像版のTwitterみたいなリアルタイム投稿アプリ
  • 投稿の公開レベルを、パブリック、フォロワーのみ、プライベートで指定可能。閲覧権限がないユーザの画像ファイルへのアクセスには 404 を返す
  • HTTP long polling でのタイムライン取得機能あり

『予選とがらっと変えてきた』的な反応が多かったのですが、これは実は順序が逆でして。ISUCON3出題の裏側 | tech.kayac.com - KAYAC engineers' blog で書かれているとおり、最初に出題内容を考えたときには本選のお題を決めたんですね。

画像投稿で、リアルタイム通知があって、複数台ちゃんと分散しないと勝てないようにしよう、という方向でした。

予選をやるとなると、本選のネタバレに繋がる内容を盛り込むわけにはいかず、必然的に全然違うタイプの出題になった、ということなのです。

リアルタイム通知によって同時に画像変換にリクエストが殺到する、というネタは、弊社の提供しているチャットサービス Lobi で実際にあった現象を元にしています。

実運用を考えるとリサイズ済みの画像をあらかじめ持つのは容量とファイル数の問題で避けたい、けれども参照時に変換すると殺到問題がある、というあるあるネタですね。

『罠』について

初回の ISUCON 1 での通称「kazeburoの罠」の印象があまりに鮮烈なため、ISUCONといえば『罠』的な印象があるのですが、実は ISUCON 2 には特に罠らしい罠はないのですよね。

今回の予選では意図的な罠ネタを入れすぎてしまったのですが、これはおそらく出題したことのある人にしか分からない心理がありまして…

出題しているとお題についてあまりに熟知してしまうために、この程度の問題ではあっさり解かれてしまうのではないか、という恐怖心が時間を追うごとにどんどん増してしまうのです。その恐怖心に負けるとああなってしまう。

…というのを予選後かなり反省しまして、予選を通ってきた強者たちにはまっすぐな出題で応えるべきと思い直したため、今回の本選には特に意図して仕掛けた『罠』はありません。

意図せず罠的な挙動をしていた事象としては、

  • PerlでImagerを使うと画像差分が大きくなりがち (特に小さいサイズやアイコン)
    • これは出題中に image diff の閾値を決めるときに気がついていました
    • 実際に変換した画像をリファレンス実装のものと並べてみると、デフォルトだと1ピクセルずつ右下にずれたように見えます

というのがあります。画像変換のライブラリによって結果に差分がでるのは当然のことなので、差分を検出するツールを提供すれば問題なかろう、という判断でした。

あとは1MBを超えるサイズのデータで nginx で 413 がでるとか、memcachedに入らないとか、そういう現象もありました。これも出題時に当然気がついていたのですが、昨今のスマフォは1MB以上のJPEG画像を平気で撮影できますし、仕方ないですよね。
(ただし、全画像を大きくすると100Mbpsの帯域に対するインパクトが大きすぎるため、1MBを超えるのは全体の数%程度です)

配点について

POST /entry とタイムラインへの反映については、レスポンス速度によって加点があります。

score = log(10 / $response_time) / log(2)

なぜ log(2) で割っているかというと、ベンチマーククライアントのタイムアウトが5秒なので、5秒ちょうどで1点になるようにしたかったためです。

10ms で返せれば約10点になるのですが、1秒で処理した場合との差分が7点弱。通常のGETが1リクエスト1点なので、あまりそこで速度を上げるのを頑張らずにGETを捌くので高得点が狙えるのは優勝チームが示したとおりです。

これは出題の罠といえば罠でしょうか。事前検証ではPOST時には保存のみ、初回GETで変換という方策でもスコアを出すことはできているので、どちらを取るかで直接の有利不利はないと思います。

初回GET時に変換という戦略をとった場合、タイムラインが流れた瞬間に複数のクライアントが同一画像を GET に来るため、そこですべてに変換処理を走らせるとCPUの無駄遣いになりますし、workload を上げれば上げるほど効率が落ちるのでスコアを出すのが難しくなります。

これを回避するためには同一画像に対する変換が同時に複数走らないように、なんらかの方法で排他制御 (事前検証ではRedisを使用) をする必要があり、本選のように時間が限られた状況では実装しきるのが難しい、ということはありそうです。

ベンチマークツールについて

予選では同一ホストで動作させる関係上、ソースコードが読めてしまうと対応が容易になってしまうので Go を使用しましたが、本選では参加者の触れないサーバからベンチマークをするためその心配はない、ということで、自分が一番実装速度が出せる Perl で書かれています。
(ソースコード提供は数日中にはしますのでお待ちください)

リクエストを順次実行するタイプのプロセスには Furl、タイムラインを監視して同時アクセスを飛ばすタイプのプロセスには AnyEvent::HTTP を使用し、複数のタイプのプロセスを拙作の Parallel::Benchmark で束ねて実行するような作りになっています。

帯域制限は tc を wrap してくれる大変便利な qos-control で1ベンチマークホストに対する設定を吐き出してから、設定ファイルを加工して125ホスト分定義しました。

複数ホストに対するベンチマーク実行は、LVS で NAT 構成を取っています。参加チームごとに 8000 + チームID のポートを割り当てて ipvsadm で設定、そのポートに対してクライアントが接続する形を取りました。(そのため一部のエラーメッセージで謎のURLが見えてしまうことになり、混乱をしたかたもいたようで、申し訳ないです)

このあたりの詳細は、ISUCON3反省会 で紹介する予定ですので、ISUCON参加者に限らずぜひ無限プレモルしに来てください。お待ちしております。

さいごに

まだこれから結果まとめエントリを書いたり、ソースコードやAMI(?)公開をしたり、いろいろ作業は残っていますが、とりあえず今日の所はこれぐらいで。

来年のISUCONがどうなるのかはまだ誰も分かりません。が、是非誰か出題に名乗り出ていただきたいなあ、というのが正直な気持ちです。

いきなり言語実装を大量に揃えて、となると準備も大変ですから、まずは社内や身内で1言語でやってみるといろいろ面白いのではないかと思います。身内だけなら実装に不備があってもごめんなさいで済みますし。実際出題すると相当勉強になりますし。

今回は100万円の賞金が掛かっているがために、出題で下手を打ったら切腹もの、というのが最大のプレッシャーでした。でも当日競技を見ていて、最後のほうは100万円のことを忘れるぐらい面白かったですね!

ISUCON3 予選を開催しました

出題担当なのですが正式名称が ISUCON3 なのか ISUCON 2013 なのか未だによく分かってない今日この頃です。

それはともかくとして、200名以上の皆様に参加していただいて ISUCON の予選を盛況のうちになんとか終えることができました。

  • スコアの算出方法が公表されていなかったり
    • 基本的に静的ファイル以外の1リクエストが1点 (ただしPOST→リダイレクト→GETは1点)、静的ファイルは0.02点、css扱いで で読まれるのは 0点、でした
    • Failについては合計3まで減点なし、それ以上は (Fails - 3)^2 * % を減点するので合計 Fails 13 で (10*10)% = 100% 減点でスコア0になります
  • 想定していない /recent/* 荒稼ぎポイントがあったり
    • 初日の競技中に気がついて、何チームぐらいここで稼いでくるかと思いましたがやはり60チーム以上いるとありますね

と、不備がいくつかありまして参加者の皆様には申し訳ないです。本選ではそのあたりしっかり準備します。

感想

予選を開催してみての感想をいくつか。

今回はフロントキャッシュ組と再実装オンメモリ組がどうしても強かったですね。

キャッシュ組が強かったのはチェッカーが甘かったからなのですが、オンメモリ実装組が強いのはある1インスタンスで予選をする以上ある程度仕方ないかな、という気がしています。

前回の ISUCON2 では4台のリソースをほぼすべて使い切った fujiwara 組とリバースプロクシのホスト1台にアプリ乗せて捌いた山形組が (1分計測では) 僅差でしたし、1台かつデータセットがメモリに保持できるという出題ではこれを覆すのはなかなか大変かなと。

本選では用意した複数台を使い切ることで性能が上がるような出題にしようと構想しています。

準備とか

準備はまあ、分かっていたことですが大変でした。

9/18〜20 の YAPC::Asia 前に出題のプロトタイプと Go でのベンチクライアントの仮実装ぐらいは何となくできていた状態で、YAPC 後の1週間でひとまず最初の版を仕上げて @acidlemon に解いてもらいはじめたのが 9/25(水)。

最初は /recent/* がなかったのですが、ちょっと簡単すぎるかも、ということで追加したのが 9/30(月)の昼。後付けだったから意図しない穴があったんですね。

そこから二人で Ruby, Python, Go, Node, PHP の移植をせっせとやって目処がついたのが10/2(水)〜3(木)。

開催前日10/4(金)にAMIの元になるインスタンスを用意して、結果登録管理サーバのAPI作成、ベンチツールもインターフェースを整備して管理サーバへの送信部分を作ったり……

ベンチツールにちょっと変更を入れると6実装分でチェックする必要があったのでなかなか時間は食われるし、想定外のFailを起こすことがあるとその原因を追及しないとリリースできないし、というのが面倒でしたね。

結局予選1日目用のAMIが仕上がったのが土曜日の午前3時過ぎでした。

直前の週は、(1日だけ子供を寝かしつけて寝落ちしましたが) ほぼ毎日3時ぐらいまでコードを書いていて、もうそろそろ不惑の身体にはなかなか辛かったです。

運営当日

当日は IRC で櫛井さんと @acidlemon と連絡を取りつつ、自宅でほぼ張り付き対応。土日を潰してしまって家族には申し訳ないことをしました。

AMI-id を提出してもらうことは決まっていたのですが、そのインターフェースがなかったので14時頃からせっせと管理画面を実装して18時にdeployしたり。

--workload の存在は気がつかない人多かったですね。途中でもうちょっとREADMEに親切に「負荷を変えることでスコアが伸びることがあります」とか書こうと思ったのですが、1日目と2日目で有利不利ができるとまずいのでそのままにしました。

1日目終了後、細かい不備が見つかったのでそれを直した AMI を作り直して、2日目用には別に用意することに。

2日目は終了後、とりあえず速報で暫定スコアと順位を出して終了。

予選後

週末を潰してしまったので、月曜日はさすがに有休を取って家族と買い物にいってました。

10/8(火) には公開用AMIの整備とか。

火曜日夜に提出 AMI が出そろったので、awscli を使って

  • チームごとにAMI-idを指定してインスタンス起動
  • user-data で指定されたベンチコマンドを実行する shell script を食わせて、結果を IRC チャンネルに通知

というスクリプトをぶん回してスコア確認をしました。中も見てみたかったので、2,3チームずつ run-instance して確認して terminate して、というかんじで合計3時間ぐらい。

user-dataから起動したからか、環境変数かカレントディレクトリの影響か何かでうまく動かないチームもいくつかあったので、その場合は SSH ログインして手動でベンチコマンド実行して確認しています。

10/9(水) に結果発表を行いまして、この記事を書いてなんとか予選の一連の作業をすべて終えられます。


……といってるまもなく一ヶ月後に本選がやってくるので、来週からはその準備ですね。

また本選で、よろしくお願いいたします。

YAPC::Asia 2013 で「社内ISUCONのつくりかた」を発表しました

blogを書くまでがYAPCということなので…

YAPC::Asia 2013 にて「社内ISUCONのつくりかた」を発表しました。朝一の同時間帯に魅力的なトークがひしめくなか、聴きにきていただいた皆様ありがとうございます。

毎回40分のトークで早口になるので今年はゆるふわにするつもりだったのですが、蓋を開けてみたら結局いつもの感じになってしまいました。ゆるふわ難しい。

資料はこちらです

技術評論社さんのレポートも書いていただきました。ありがとうございます。http://gihyo.jp/news/report/01/yapcasia2013/0002

YAPC::Asia は2006年の初回から8年連続参加していて、うち本編トークは4年連続でやらせていただいていて、あらためてこう数えてみると結構な歴史を感じますね。

来年からはまた新しい歴史が始まることに期待して、一年間自分のできることをやっていきたいなと思います。

【告知】
優勝賞金100万円の ISUCON 3、予選エントリー絶賛募集中ですのでぜひよろしくお願いします!

http://isucon.net/

[perl] Perl徹底攻略という本が出ました

本日、7月23日発売です。

Perl徹底攻略 (WEB+DB PRESS plus)

Perl徹底攻略 (WEB+DB PRESS plus)

昨年10月に発売された Web+DB Press vol.72 に寄稿した「Webアプリケーションのパフォーマンス改善」が収録されています。

内容はほぼ初出時のままです。再録にあたってコード部分の変更はなしでいけると思い込んでいてそのまま校了してしまった後に、Devel::KYTProf に執筆当時のバージョンと非互換な変更が入っているのを見つけてしまい、あわてて互換性を確保するpull reqを送って 発売までにリリースされて助かった、という裏話があったりなかったり。

PerlWebサービスを作るために必要な基礎から運用、娯楽まで幅広くカバーしている本ですので、是非お手にとっていただければと思います。

nginxでメソッドごとにリクエスト数制限を掛けたい

アプリケーションでどうしても捌けない量のリクエストが一時的に押し寄せてしまう場合、アプリケーションサーバが死ぬのを避けるために GET は制限を掛けたいが、POST はリトライが面倒なのでなるべく通してあげたい、というような要求を nginx で処理できるかどうか。

実装として一番望ましいのは

  • GET は 100 req/sec で制限 (超えたら503)
  • POST は無制限

のようにメソッドごとに別々の制限を掛けることだったのですが、とりあえず HttpLimitReqModule を使うことで、メソッドごとに同一の上限を設定することはできました。

http {
    limit_req_zone $request_method zone=method:1m rate=100r/s;
    server {
        listen 80;
        location / {
            limit_req zone=method;
            proxy_pass http://127.0.0.1:5000/;
        }
    }
}

これで $request_method をキーにして、100req/sec を超えた分を 503 にします。

この設定では GET も POST も同じ上限になりますが、たとえば仮に

  • リクエストの比率が GET : POST = 9 : 1
  • アプリケーションが処理できる上限が 150req/sec
  • そこに 300req/sec が到達する

という場合を考えると GET=270, POST=30 req/sec になるので、

  • GET は 100 req/sec を超えた分 (170 req/sec) が 503
  • POSTは 30 req/sec なのですべてアプリケーションの処理に回る
  • アプリケーションに到達するリクエストは合計 100 + 30 = 130 req/sec

となって、POSTを全部通しつつ限界を超えないような制限ができます。

chef-soloがcookbookから実行するscriptの無限ループで大量にメモリを食って死んだ件

とあるホストで初期設定をしようと思って chef-solo を実行していたところ、メモリを全部食い尽くして chef-solo (11.4.4) が死亡するという事案が発生。

追ってみたところ、どうやら原因はこんなかんじ。

  • cookbook から shell script を実行していて、その中で perl Makefile.PL && make && make install していた
  • CPAN.pm が初期設定を終えていない場合、対話モードに入る
  • 対話モードで標準入力が閉じられていると途中まではデフォルトの入力で進むが、地域を選択するところはデフォルトがないためここでメッセージを表示しながら無限ループする
  • 無限ループで大量に出力されたメッセージを、(詳細は確認していないですが) chef がメモリに乗せ続けて太る

ということで、cookbook 内で実行する script には注意しましょう。というか chef 自体そのあたりの防御機構を持っていた方がいい気もしますね。

# echo -n "" | perl Makefile.PL
*** Module::AutoInstall version 1.03
*** Checking for Perl dependencies...
We have to reconfigure CPAN.pm due to following uninitialized parameters:

cpan_home, keep_source_where, build_dir, build_cache, scan_cache, index_expire, gzip, tar, unzip, make, pager, makepl_arg, make_arg, make_install_arg, urllist, inhibit_startup_message, ftp_proxy, http_proxy, no_proxy, prerequisites_policy, cache_metadata

/usr/lib/perl5/5.8.8/CPAN/Config.pm initialized.

(略)

I'd use that as a database of CPAN sites. If that is OK for you,
please answer 'y', but if you want me to get a new database now,
please answer 'n' to the following question.

Shall I use the local database in /root/.cpan/sources/MIRRORED.BY? [y] y

(ここまではデフォルトがあるので進む)

Now we need to know where your favorite CPAN sites are located. Push
a few sites onto the array (just in case the first on the array won't
work). If you are mirroring CPAN to your local workstation, specify a
file: URL.

First, pick a nearby continent and country (you can pick several of
each, separated by spaces, or none if you just want to keep your
existing selections). Then, you will be presented with a list of URLs
of CPAN mirrors in the countries you selected, along with previously
selected URLs. Select some of those URLs, or just keep the old list.
Finally, you will be prompted for any extra URLs -- file:, ftp:, or
http: -- that host a CPAN mirror.

(1) Africa
(2) Asia
(3) Europe
(4) North America
(5) Oceania
(6) South America
Select your continent (or several nearby continents) []
Sorry! since you don't have any existing picks, you must make a
geographic selection.

(1) Africa
(2) Asia
(3) Europe

(以下無限ループ)

Provisioning Frameworks Casual Talks vol.1 で「新卒研修でserverspecとChefを使った話」を発表しました

主催の @studio3104 さん、登壇された方々、参加者の皆さんありがとうございました。
Provisioning Frameworks Casual Talks vol.1 にて、カヤックの今年の新卒研修でserverspecとChefを使った話、をしてきました。

スライドはこちら

このblogには書いていなかったので、2013年のカヤック技術部新卒研修について、参考資料のリンクも張っておきます。

Chefの実行結果に対するテストは書かないとなあ、とずっと思っていながら結局あまり有効な手を付けられていない状態だったのですが、ちょうどいいタイミングで serverspec が出てきたので試しがてら研修で取り入れてみたという状態です。

プロダクション環境のテストはこれから随時書いていきますが、なにしろ対象が大量に(台数的な意味ではなく cookbook のバリエーション的な意味で……)あるのでまず cookbook に変更を加えるところから順番ですかね。

先着順参加方式について

MySQL casual vol4 の時も今回も、人数がちょうどよく埋まっているのであまり不満は聞かれないようですが、定員に達した場合の受付締切のアナウンスは(どこでアナウンスするのかも含めて)しっかり告知する必要があるな、と思いました。

当日登録のatndを併用するのは、先に確保してしまって忘れてしまうような人をださない手としてよさそうですね。