ISUCON8本戦出題記というよりぶっちゃけ工数感どうなの?って話

こんにちは!

サーバーサイドサイドエンジニアの荒賀です。 普段はソーシャルゲーム事業部でゲームの開発したり、Lobi事業部でコミュニティサービスの開発をしたりしています。

さて、この度ISUCON8の本戦問題の出題を担当させていただきました。 問題に関する解説は公式のブログを書きましたのでこちらを御覧ください。

ISUCON8 本選問題の解説と講評

こちらでは技術的なことは置いておいて、業務としてどのようにISUCON8に関わってきたかを書き残しておきたいと思います。
(※カヤックの出題はISUCON3でもしており今回が2回目です。)

はじまり (2017/12〜2018/2)

だいぶ曖昧な記憶ですが、ISUCON7で優勝させていただいた直後の打ち上げの場で、僕の耳元で941さんが「来年はカヤックさんどうですか〜(ニヤニヤ)」と囁いてきたので、とりあえずニヤニヤで返したことを覚えています。

その後、正式に藤原にお話をいただいたことで、マネージャー陣が社内調整をして私と藤原のリソースを確保して業務として取り組んで良いということを決めてくれたので「じゃあやります」となったのがはじまりでした。それがたしたか2017年の末

ただ、前回の出題経験などから一社では厳しいという話を藤原はしていて、

そして無事一緒に出題をするDeNAさんとサーバーを提供するGMOさんも決まり、さあやるぞ~となったのでした。

とはいえ、まだこの時期は担当していたゲームのリリースなどで通常業務が忙しく、ISUCONに関してはなんかネタ無いかなーと探す程度でした。 また、仮にいいネタがあったとしても、ネタバレを警戒して「このネタはどうかなあ?」ということを誰にも相談できないという状況だったため、それはもうとにかく苦痛でした。
(リリース時期だったりしたので結構トラップは思いついていた気がします。すべて忘れたけど...)

社内のメンバー集め (2018/3〜)

さて、開発リソースとしては藤原と私の2人は確保したのですが、2人だけではネタを決めるブレストもままならず、 仮に問題を作っても客観的に見れるメンバーがいないと困るので、社内で他のメンバーを募ることにしました。

2018/02/28 募集

f:id:ken39arg:20181029103024p:plain

2018/03/12 ついに

f:id:ken39arg:20181029103224p:plain

谷脇瀬戸が運営側での出題を決意してくれてネタ作りが始まりました。

最初のアイデア (2018/4〜)

まだこの頃は、打ち合わせの前日とか運営Slackに「進捗どうですか〜?」ときてしまった、などのギリギリのタイミングになって、 緊急集合してブレストして、形だけでもなんとかするというスタイルをとっていたため、

運営での打ち合わせには、具体的なネタは未定のままでボトルネックのみをもっていくということをしてしまいました。 DeNAチームはなんと具体的なネタを2本も持ってきていてあちゃーとなったのをよく覚えています。

最初の案出し打ち合わせに持っていったメモがこちら

コンセプト: job qeueu等を使った非同期処理がポイント
- Webアプリ(具体的なネタは未定)にアクセスがあるたびに、行動ログ記録APIを叩く必要がある
- 行動ログ記録APIは以下のようなもの
  - バイナリ提供するブラックボックスツール
  - 各自のサーバで動かす
  - レイテンシが大きい(海外に実体がある想定)
  - API rate limit がある
  - バルク送信も可能
  - 一定以上の量を送信する場合はバルク送信にしないといけない
- 初期実装ではリクエスト処理で同期的にHTTPSでリクエストしている
  - そこを非同期化するとレイテンシが改善するのでスコアが上がる
- ベンチマーク走行終了後、10秒後に行動ログを抽出し、ベンチマーカの結果と整合していたらOK
  - 非同期処理による遅れを一定時間許容する

実はもともと、カヤックも予選を希望していたのですが、このような感じで我々のネタは未定が多すぎたし、DeNAさんのアイデアは既にしっかりしていたので、 DeNAさん予選どうぞ!ということにサクッと決まり、我々の方は本戦ということで、それなりに振り切った問題にしていこうみたいな話だけをしていました。

ちなみに具体的なネタとしては、カヤックらしさとボトルネックとの相性を気にして下記のようなことを考えていました。

  • アバタージェネレーター
  • 簡易的な連打ゲーム
    • ゲーム事業部はカジュアルゲームやブラウザのクリックゲームもたくさん作ってきた
    • 昨年の本戦とかぶる
  • リアルイベントとの連動系
    • コミュニティサービスでは、ある時間になったら大量の負荷が来たりする。
    • いまいちログと絡むイメージがない

そして諸々が決まり出題チームキックオフが実施され、開発スケジュールとしては、最初のマイルストーンを8月上旬に開発合宿を行い、そこで双方の問題を交換して解き合うということに決まりました。 (つまり本戦であってもその合宿までに作るということが決まったのでした。)

めっちゃ楽しそう

社内ISUCON (2018/4)

さて、カヤックでは毎年4月になると、新卒で入社した技術部のエンジニアを対象に、先輩社員が作る技術部研修を行っています。

この技術部研修では、フロントエンドのHTMLやアプリ、サーバーサイドのRDB(MySQL)、NoSQL(Redis)、AWSなど一通りを座学と実習で行いながら、最終的に一人で簡易的なアプリを作ってもらうということをやっています。

今年は、FlappyBirdのアプリをUnityで作って、自分で作ったサーバーアプリにスコアを送って、HTMLページからJSON APIを叩いてランキングを表示するという内容でした。 そして、3週間の技術部研修の最終日に、その作ったランキングAPIを題材に先輩社員も参加して社内ISUCONを行うというものでした。(濃すぎw)

(※ちなみに上記リンク先の研修に関するブログは昨年のものです。今年は研修直後にGWなどがあり、一同ブログを書くのをすっかり失念しておりました...)

この最終日の社内ISUCONを、出題の練習を兼ねて私が担当しました。

実施した結果、私は下記のような反省をしました。

  • 悪かった点
    • 直前にベンチマーカーのシナリオにロクにテストをせずに凶悪なのを追加したため、強力なシナリオをやっつけない限りあらゆる施策がスコアに効かない状態になってしまった。 特に、技術部研修の一環であるはずのISUCONなので、楽しさを知ってもらうような簡単なものにすべきだった。
  • 良かった点
    • ベンチマーカーは比較的安定して動いていた
    • ISUCON7のポータルの使い方を完全に理解した。

※この社内ISUCONのスコアリングに関しては非常に反省していて、半期に一度の360度評価にある「この半期での失敗はなんですか」という項目にも記載していました。以下抜粋

具体的にはアプリ作成まではスケジュールできていたが、サーバー準備とチューニングをスケジュールとして多くとっていませんでした。そのため、余裕を持って準備ができていたにもかかわらず、すぐに次の手に進まなかったことが原因の一端かなあと思います。

通常業務でもつくるスケジュールはしっかり組めているがQAやサーバーセットアップのスケジュールが十分じゃなく突然炎上してしまうということはよくあります。 そうならないために、良いものをつくるには逆算したスケジュールをきちんと立てる必要性があるということです。

今回の反省を生かして9月に予選10月に本戦のISUCONではきちんとスケジュールしていい問題を作れるようにしようと思います。(現状ゼロ進捗ですが...

ネタの決定とモック版 (2018/7〜)

そうして、運営キックオフでは英気を養って、社内ISUCONで出題の感覚を掴んで、一気に作るぞエイエイオー!と行きたいところだったのですが、 まだまだISUCONは遠い未来の話なんてついつい思ってしまったものでして、、燃え上がった炎も気づいたら一瞬にして種火くらいに小さくなってしまっておりました。

社内Slackに設けた出題チームのプライベートちゃんねるも5月中旬からパタリと止み、6月に1度ヤバイ~という声が僕から上がるもののまたパタリと止み...

そして7/6、

f:id:ken39arg:20181029111016p:plain

合宿まであと1ヶ月無い!!もういい加減ヤバイ!ネタ決める!今日絶対決める!!!と、突然僕が発狂した結果マコピーさんから起死回生のISUCOIN というアイデアがでて、10分後にそれに確定するという流れで、あれだけ決まらなかったものがあっさり決定しました。 人間追い詰められると強い!

そこからは、下記のように頭とお尻を頑張るスタイルでギリギリで合宿に挑むことになりました。

  1. 7/11にISUCOINのざっくりとした概要をだして揉み合うみたいなことをして合宿に持っていく仕様が決定 commit
  2. 7/12 にいすこん銀行APIのモックが完成 commit
  3. 7/12 にロガーAPIのモックが完成 commit
  4. 7/26 に課題アプリのモックが完成 commit
  5. 8/1 進捗に焦りだす
  6. 8/3 0:54 ひとまず合宿に持っていくベンチマーカー完成 commit

ちなみに、この時は画面は一切なくAPIのみしかないので想像でお願いしますというものでした。

また、この期間の作業時間ですが、1日平均1時間〜2時間をISUCONに充てる時間としていて、実際はだいたい週末に焦ってまとめて作業をするみたいな感じになっていました。

運営合宿 (2018/8/3〜8/4)

公式に詳細がありますが、湯河原で運営合宿を行いました。

圧倒的進捗を出すため、ISUCON8 運営チームで湯河原で温泉合宿をしてきました!

大変なのは準備までで、合宿は超楽しかったです。 だってISUCON大好きな人たちが問題を持ち寄って交換して解くわけですからそりゃ楽しいです。

また、個人的にもせっかく湯河原にきたので趣味でやっているオープンウォータースイミングの練習までしてひたすら楽しい感じでした。

さて、この合宿で出た課題ですが、

  • とりあえず画面無しはキツイ
  • ロガーとか外部サービスのチェックは厳しくすべき
  • deadlockしすぎで申し訳ない気持ちになるのでもうすこし緩和する
  • /send_bulkヒント出しすぎ。(この時点では /send_bulkはモジュールの中に型の定義がされていました)

これをガンガン改善して予選より先に問題仕上げじゃうぞー!という高い志をもって帰路についたわけですが、、その燃え上がった炎も気づいたら一瞬にして種火くらいに小さくなってしまっておりました。。。

予選問題事前回答 (2018/8/26)

本戦問題のリポジトリは合宿後すっかり静かになってしまったのですが、予選を出題したDeNAさんはきっととっても頑張っていたと思います。

せめて事前回答でのバリューを!ということで、カヤックからも出題チームの谷脇と瀬戸に、ISUCON参加を見送っていた杉本を加えた3人のチームを予選問題の事前回答に送り込みました。

感想を聞いたところ合宿で我々が指摘したことは、ほぼ潰されていてかなり完成度が高くなっていたそうです。さすが!!

そして、この事前回答での仕上がり具合に感化されて僕のお尻にも火がつき始めます

f:id:ken39arg:20181029105347p:plain

予選当日 (2018/9/15,16)

出題担当のDeNAさんやGMOさんとは違い、本戦担当の我々は特にやることもないのでバッチリ睡眠をとって呑気に予選観戦をしました。

一応、この予選中に本戦のフロントエンド実装するぞーと意気込んではいたのですが、この予選2日間16時間の間に僕がやったのは旧ベンチマーカーを少し安定させたのと、きっと使うであろうbootstrapを配置し、参考実装のアプリから静的ファイルを配信するという部分だけでした。合計1時間程度でしょうか...

ISUCONの観戦はグラフを見ているだけでめちゃくちゃ楽しくて、とてもエンターテイメントだったので、スポーツ観戦をするように「◯◯チームきたー」とはしゃいでいました。

2日目に至っては我慢が効かなくなってしまい、一人でISUCON予選問題に挑戦していました。 開発合宿で一度解いてはいたので比較はできませんが、ベストスコア115,086、最終スコア38,737 という結果を残して大満足していました。(参加していたらギリギリ17位で本戦出場\(^o^)/)

f:id:ken39arg:20181029105439p:plain

(似たようなスコアの大冒険をしていたチームがいたため、気持ちがとても良くわかります。)

さて、この予選の様子を見ていて気になっていたのが、ベンチガチャと呼ばれるベンチ結果の不安定性でした。

その後の調べで、大きな要因がベンチ開始直後にエラーを出した場合、最初の負荷レベルの上昇がないため、それが最終的なスコアに大きな影響を与えているということでした。 これをなくすにはどんな調整をすべきかを藤原と話していたのですが、打ち上げ中に藤原がシェア機能を思いついて、本戦の問題に加えることになります。

ISUCON8 本選出題記 あるいはISUCONベンチマーカー負荷調整の歴史

ラストスパート(2018/9/17〜)

8/3の合宿以降、常に頭の片隅にISUCONのことがあるものの、予選が終わるまでは多くても週に1時間程度しかISUCON関連の業務はしていないという状態でした。 しかし、予選が終わってからはさすがに業務時間のISUCON作業が増えてきました。ここからはラスト1ヶ月の工数をぶっちゃけていきたいと思います。

事前回答まで (〜10/3)

  • 業務内での作業時間
    • 9/18〜21 2h/day
    • 9/25〜28 6h/day
    • 10/1,2 3h/day
    • 10/3 6h
  • 業務外での作業
    • 少々

この時期は基本的に合宿で出た課題を解決しつつ、予選後に思いついたシェア機能を入れたり、チューニングをしたり、コードを本番さながらに整理するといった作業に終始していました。

事前解答で利用した課題アプリは本戦で出したものと比較して、 /initialize/info に違いがあるという程度のものでした。

  • 事前回答での主な課題
    • /initialize が不安定だった
    • 画面のフィードバック
    • rate limit の基準を明示してほしい
    • 白金動物園チームに全クリされた

言語移植開始まで(10/4〜10/9)

言語移植期間を1週間取るためには10/9には初期実装をFixして依頼を出す必要がありました。

  • 業務内での作業
    • 10/4 ISUCON作業なし
    • 10/5 4h
    • 10/9 8h (全振り)
  • 業務外での作業
    • 結構あり

10/9についに業務時間をISUCONに全振りする日がやってきました。 全振りになるとISUCONに理解のあるメンバーからは応援されますが、他の職種の違うチームメンバーに対しては自分の中で勝手に加害者意識が生まれてしまい、 目を合わせることができなくなるのでヤバイです。
※もちろん業務としてやっているので後ろめたさを感じる必要など無いのですが、にんげんだもの...

移植をお願いした段階でアプリについては、ほぼISUCON当日の状態でしたが、初期データについてうまくいっていないことがあり、移植後にすべの参考実装に修正を加えました。(慣れていない言語で苦戦したものもありました...)

ベンチマーカーの最終調整(10/10〜19)

移植の依頼を出すと基本的にアプリはいじれないので、ひたすらベンチマーカーの調整に入ります。

ベンチを修正しては初期状態と特別賞ラインのアプリでベンチを回すと言うのを無限に繰り返していました。

  • 業務内での作業
    • 10/10 1h
    • 10/11 4h
    • 10/12〜19 8h/day (全振り)
  • 業務外での作業
    • とてもあり

最後は一週間全振りしていました。(また、有給休暇も余っていたので2日ほど休みをとって集中して作業するなどもしました。)

実はこのタイミングでベンチマーカーのバグが見つかってしまい、修正コストや追加したいシナリオを考えると作り直したほうが良いという判断をして、半分くらい作り直したりしました。

また、10/18には僕の作業はほぼ完了していましたが、ドキュメントの最終チェックや、特別賞のための最終確認、オープニングムービー作成などで2日ほど使いました。

本戦当日 (10/20)

7時間くらい睡眠をとって会場に向かいました。

オープニングムービーはとても緊張しましたが、後は楽しく皆さんの奮闘を見守っておりました。

本戦後

  • 問題公開
    • もともとのプライベートリポジトリから不要な情報をfilter-branchなどをしたり、手元で動かせる状態にして公開しました。2hくらい
  • 講評執筆

本戦後はぐったりしたり感想が気になってエゴサなどをしてしまい業務にはあまり集中できない感じでしたので、問題公開や講評の執筆などはちょうどよかったかもしれません。

まとめ

ISUCONの出題はとても楽しいし勉強になります。

しかし、かなり時間がかかるのも事実で業務時間を使わないと過労で倒れます。 なので本番前後の2週間程度のすべてと、それ以外の期間も20%程度の工数を一定期間確保する必要があるというのが現実です。
(※個人差があります)

カヤックではこの他に、藤原や、フロントエンドを担当した板橋、perlとphpを移植した谷脇や瀬戸なども作業をしています。 (特に藤原と板橋は直前期間はかなりの作業をしておりました。)

来年以降もISUCONという楽しいイベントを続けるために、皆さんの参考に慣れば幸いです。

最後に、カヤックではISUCONが大好きなエンジニアを募集しております

もしご興味のある方は「ISUCONが好きで・・・」といっていただいて応募いただけると泣いて喜びます。

どうぞよろしくお願いいたします。

Amazon CloudWatchのアラームをMackerelアラートにする仕組み

SREチームの吉村です。

弊社では主にAmazon Web Service(AWS)を使ってサービスを構築、運用しており、そのサービスの監視にMackerelを利用しています。 Amazon CloudWatchのメトリクスのうち主要なものはfluentd(fluent-plugin-cloudwatchfluent-plugin-mackerel)を利用してMackerelに登録していますが、量が多くなるとfluentdの設定が煩雑になってしまいます。

そこで、Mackerelへの登録に必要なそれらの設定なしに、CloudWatch Alarmを使ってMackerelのアラートとして発報できる仕組みを作りました。 Mackerelへアラートを発報するようにした理由は、アラートの通知や管理はMackerelで一元管理をしているためです。

今回その仕組みについてご紹介、組み込み手順まで説明したいと思います。

概要

cloudwatch-alarm-to-mackerel

cloudwatch-alarm-to-mackerel

上図はCloudwatch AlarmからMackerelへアラートが上がるまでの流れを表した図です。 CloudWatch Alarmが発火されたら、そのアラームのactionsに設定したAmazon Simple Notification Service(SNS)のtopic1へメッセージが飛びます。 そのメッセージは予めSNS上で登録したサブスクリプションによってcloudwatch-alarm-to-mackerel関数へ流れていきます。 最後に、メッセージはMackerelへアラートを上げます2

次に、実際の導入方法を説明します。

導入方法

必要なもの

以下のツールを事前に用意してください。

1. SNSのtopicを作成する

CloudWatch Alarm発火後の通知先に、先に述べたSNSのtopicが必要のため事前に作成します。

$ aws sns create-topic --name sample

2. Mackerelにホストを作成

Mackerelの POST /api/v0/monitoring/checks/report ではhost idが必要なので、 cloudwatch-alarm-to-mackerel専用のhostをMackerel側に登録します。

$ mkr create sample_host

3. Lambdaの組み込み

cloudwatch-alarm-to-mackerelはapexの利用を想定しています。 以下、apexのディレクトリ階層の例です。

.
├── functions
│   └── cloudwatch-alarm-to-mackerel
│       ├── function.json
│       └── main.go
└── project.json

main.goとfunction.jsonを作成します.

  • main.go
package main

import (
    "github.com/kayac/cloudwatch-alarm-to-mackerel"
)

func main() {
    cwa2mkr.ApexRun()
}
  • function.json
{
  "name": "cloudwatch-alarm-to-mackerel",
  "description": "alert cloudwatch alarm to mackerel",
  "runtime": "go1.x",
  "memory": 128,
  "timeout": 60,
  "handler": "main",
  "environment": {
    "HOST_ID": "mackerelのhost id",
    "MACKEREL_APIKEY": "mackerel apikey"
  }
}

※ HOST_ID, MACKEREL_APIKEY は適宜正しい値を入力してください。
※ 注意: mackere_apikeyは秘密情報なので、apex deploy --env ... 等で実行時のみ入れるなどの工夫をしたほうが良いです。

  • project.json
{
  "name": "sample",
  "description": "sample project",
  "memory": 128,
  "timeout": 60
}

この状態で、先のディレクトリ階層の現在位置でapexよりデプロイを実行します。

# dryrunで確認
$ apex deploy --dry-run cloudwatch-alarm-to-mackerel

# デプロイ
$ apex deploy cloudwatch-alarm-to-mackerel

# 登録の確認 & arnのチェック
$ aws lambda get-function --function-name sample_cloudwatch-alarm-to-mackerel | jq -r .Configuration.FunctionArn

※ function名に sample_ がつくのはproject.jsonのnameがsampleなためです。

次にSNSサブスクリプションを登録します。

$ aws sns subscribe --topic-arn <topic arn> --protocol lambda --notification-endpoint <lambda arn>

<topic arn>には1で作成したtopicのarn、<lambda arn>には先程登録したLambdaのarnを入れます。

4. Cloudwatch Alarmを作成

アラート検知させたいCloudwatch Alarmを作成します。 アラーム設定したいメトリクスを選択肢、登録したトピックを通知先に設定します。

以下、console上でのアラームの通知先設定の画面です。

cloudwatch alarmの設定

①ではデータが不足している場合の挙動を設定できます。 画像では現状維持を設定しています。 こちらは適宜設定してください。 ②で、通知先のアクションが設定します。 先に作ったsampleのSNSトピックを選択します。

5. 動作確認

登録した Cloudwatch Alarm の閾値を調整して、現在のメトリクス値で適当に発報されるようにconsole上から設定して動作を確認します。 以下の画像のようにMackerel側にアラートが上がるのを確認できます。

warningのアラート結果

criticalアラームの設定

cloudwatch-alarm-to-mackerelは、デフォルトでWarningレベルでアラートを投稿しますが、 alarmのdescriptionの先頭がCRITICALという文字列だった場合、Criticalレベルでアラートを投稿します。

以下、設定を変更してCriticalレベルのアラートになるか確認した結果です。

criticalのアラート結果

おまけ: PostChecksReports の利用

Mackerelへpostする部分は別パッケージからも利用できるようにしてあります。 このAPIだけ簡単に利用したい場合にどうぞ。

package main

import (
    "log"
    "time"

    "github.com/kayac/cloudwatch-alarm-to-mackerel"
)

func main() {
    now := time.Now().Unix()
    apiKey = "Your mackerel api key"

    reports := cwa2mkr.Reports{
        Reports: []cwa2mkr.Report{
            cwa2mkr.Report{
                Source: cwa2mkr.Source{
                    Type:   "host",
                    HostID: "host id",
                },
                Name:       "test alarm",
                Status:     cwa2mkr.StatusWarning,
                Message:    "this is a test",
                OccurredAt: now,
            },
        },
    }

    if err := cwa2mkr.PostChecksReport(apiKey, reports); err != nil {
        log.Println(err)
    }
}

さいごに

AWSのリソースに対する監視をCloudwatch Alarmの新規作成のみで設定できるようになる点が良いところだと思います。設定が簡単なので、AWSのメトリクスから一旦アラートだけ上げられるようにして、あとから細かく監視を調整していくと行った場合でも便利かと思いました。

kayacではいつでもエンジニア募集中です!


  1. SNS topicはメッセージを送信、受信するための通信チャネルで、CloudWatch Alarm以外の様々なAWSサービスから利用できます。

  2. MackerelのアラートにはPOST /api/v0/monitoring/checks/reportを使っています。