ISUCON (実際のコンテスト/競技) と XSUCON (ISUCON 10 本選問題に登場する仮想的なコンテスト/競技) 内の用語を区別するために、本マニュアルでは「実」を付けた場合には ISUCON を、「仮想」を付けた場合には XSUCON を指します。
例:
実負荷走行の中で開催される XSUCON は、以下のような状態を持ちます。
上記のフェーズ遷移の時間は目安です。実際には、実ベンチマーカーによって POST /initialize
で時間が指定されます。
仮想選手が仮想チームを登録したり、メンバーを招待したりします。
実ベンチマーカーは、アプリケーションがチーム登録リクエストを処理できるかぎり、仮想チームを登録しようとします。これに対しアプリケーションは、レスポンスコードによって仮想チームの参加上限数を制限する事ができます。
例えば、XSUCON の参加チーム数を 10 チームにしたい場合は、11 チーム目以降のチーム登録リクエスト POST /api/registration/team
に対し HTTP レスポンスコード 403 を返してください。
なお仮想チームには 1 チームあたり最大 3 人まで参加する事ができます。
仮想コンテスト(XSUCON)が開始します。これ以降の仮想チーム登録はできません。
仮想コンテスト開催中は、実ベンチマーカーは以下の 3 種類のユーザーを模したリクエストを行います。
仮想コンテスト終了まで、ダッシュボードは「スコアフリーズ」状態になります。
スコアフリーズ状態では、各ダッシュボードは以下のような挙動になります。
GET /api/contestant/dashboard
GET /api/audience/dashboard
仮想スコアフリーズ中も仮想コンテストは続いており、仮想選手達は引き続き競技に関するリクエストを行います。
仮想コンテストが終了すると、仮想選手達は競技に関するリクエスト(仮想負荷走行のエンキューなど)ができなくなります。
各ダッシュボードにおける仮想スコアフリーズは解除され、他仮想チームの最新スコアが再び見られるようになります。
実競技に用いるチューニング対象のアプリケーション(以下アプリケーションと表記)は「仮想ポータル (HTTPS)」と「仮想ベンチマークサーバ (gRPC)」からなります。
仮想ベンチマーカーは実ベンチマーカーの中に実装されているため、実装を見たり変更したりすることはできません。
以下、仮想選手が仮想負荷走行をエンキューしてから、その走行結果を仮想選手が受け取るまでの流れを説明します。
HTTP リクエスト: POST /api/contestant/benchmark_jobs
仮想選手が仮想ポータルに対し、仮想負荷走行(benchmark_job)のエンキューをリクエストします。エンキューしたとき、ジョブのステータスは PENDING
として設定されます。
仮想選手は、エンキューができる状況にあるときは即時にエンキューを試みます。以下のような状況にある場合は仮想選手はエンキューをせず、状況が変化するのを待ちます。
FINISHED
になっていない所属仮想チームのジョブ(PENDING
, SENT
, RUNNING
)がすでにあり、まだ仮想負荷走行終了の通知を受け取っていないときgRPC サービス: xsuportal.proto.services.bench.BenchmarkQueue
, プロシージャ: ReceiveBenchmarkJob
仮想ベンチマーカーは仮想ベンチマークサーバに対し、常にキューをポーリングしています。キューにジョブがあった場合はそのジョブがデキューされます。デキューされたジョブは、ステータスが PENDING
から SENT
に変更されます。
仮想ベンチマーカーは仮想チームと同じ数だけ用意されており、最大で全ての仮想チームの仮想負荷走行を同時に処理できる能力を有します。
各仮想ベンチマーカーは、ReceiveBenchmarkJob
の実行でジョブが返却されなかった場合(キューが空)、待ち時間を挟まず、即時にリトライをします。また、仮想負荷走行後、レポートを登録した後も、待ち時間を挟まず即時に ReceiveBenchmarkJob
を実行します。
gRPC サービス: xsuportal.proto.services.bench.BenchmarkReport
, プロシージャ: ReportBenchmarkResult
仮想ベンチマーカーは仮想負荷走行に対し、以下の通りレポートを送ります。
SENT
から RUNNING
に変更されます。RUNNING
から FINISHED
に変更されます。仮想負荷走行は開始したあと瞬時に完了します。
仮想選手のブラウザは一定時間ごとに通知リスト(GET /api/contestant/notifications
)をポーリングしています。仮想ポータルは、仮想選手の仮想チームが実行した仮想負荷走行が完了していた場合、ベンチマーク完了通知(xsuportal.proto.resources.Notification.BenchmarkJobMessage
)を通知リストに加えます。
仮想選手はこの通知を受け取ったとき、ジョブ詳細(GET /api/contestant/benchmark_jobs/:id
)にアクセスし、仮想負荷走行の結果を確認します。実ベンチマーカーはこの確認が完了したとき「仮想負荷走行が 1 回成功した」としてカウントします。
仮想オーディエンスは、XSUCON を盛り上げるために欠かせない存在です。仮想オーディエンスは、仮想オーディエンス用ダッシュボード(GET /api/audience/dashboard
)を一定間隔で閲覧し、仮想コンテストの動向を見守っています。
仮想オーディエンスは、仮想コンテスト開始直後は 0 人の状態から始まります。いずれかの仮想チームが仮想負荷走行を 1 回成功させるたびに、仮想オーディエンスは 1 人増えます。また、仮想オーディエンスの行動中にエラーがあった時、1 回のエラーにつき仮想オーディエンスは 1 人減ります。
仮想選手は、一定の頻度で質問を投稿(POST /api/contestant/clarifications
)します。仮想運営は質問を一定間隔で確認しており (GET /api/admin/clarifications
)、未回答の質問を検知した場合即時に回答を書き込みます(PUT /api/admin/clarifications
)。
仮想選手は、本人あるいはチームメンバーが質問を投稿したら、仮想運営による回答を確認できるまで、以下の行動を停止します。
仮想選手のブラウザは、一定時間ごとに通知リスト(GET /api/contestant/notifications
)をポーリングしており、質問に対する回答が来たかどうかはこの通知の中身を見て判断します。仮想選手は、通知リストに回答通知(xsuportal.proto.resources.Notification.ClarificationMessage
)が含まれていることを確認したら、回答の本文を質問リスト(GET /api/contestant/clarifications
)から閲覧します。仮想選手は質問リストを見て回答されていることが確認できたら、停止していた上記の行動を再開します。
仮想選手からの質問に対する仮想運営からの回答には、「個別回答」と「全体回答」があります。仮想運営は、質問に対しどちらの種別の回答を行うかは、一定の確率で選択します。
個別回答では、質問を投稿した仮想チームのみ、質問および回答を閲覧できます。
全体回答では、質問を投稿した仮想チーム以外の仮想選手も含め、全ての仮想選手が質問とその回答を閲覧できます。全体回答が行われた場合、全仮想選手の通知リストに回答通知が加えられます。回答の通知を受け取った仮想選手は、その回答を即時に閲覧するため、質問リストへアクセスします。
仮想ポータルの通知機能は、ブラウザの Notifications API および Push API を用いて実装されています。仮想ポータルの仮想選手向けダッシュボード画面(/contestant/dashboard
)右上にある「通知を有効にする」ボタン(上図)を押すと、ブラウザを通じて通知を受け取ることができます。ブラウザで動作確認する際には、お使いのブラウザが Notifications API および Push API に対応しているかどうかを確認してください(Safari は対応していません)。実運営は、Chrome と Firefox を用いて動作確認を行っています。
アプリケーションの参考実装(仮想ポータルのクライアントサイド)において、新着通知の取得は前述の通り GET /api/contestant/notifications
へのポーリングで実装されています。
参考実装のサーバサイドでは未実装ですが、クライアントサイドはこのポーリング方式に加え、 Web Push 方式が実装されています。
以下の 2 つの API は、参考実装ではステータスコード 503 を返すようになっています。これらを正しく実装し、200 を返すようにすることで、Web Push による通知を購読/購読解除できるようになります。
POST /api/contestant/push_subscriptions
DELETE /api/contestant/push_subscriptions
実ベンチマーカー内の仮想選手が使うブラウザも同様に、Web Push が実装されているため、上記 API が実装されていれば Web Push による通知を購読するようになります。
アプリケーションは、Web Push で全ての通知を送るように変更した場合は、通知リスト(GET /api/contestant/notifications
)のリクエストに対して空の通知リストを返す事ができます。空の通知リストを返す場合も、正常時の HTTP ステータスコードは 200
であることが期待されます。
また、アプリケーションは 1 人の仮想選手に対して、Web Push 方式の通知とポーリング方式の通知を混在させても構いません。また、通知に関しては既に受信したものを重複して受信した場合でも、繰り返し処理されません。
ブラウザの Push API の挙動を再現するため、実ベンチマーカーには RFC8030 push service が実装されています。
実ベンチマーカーが実装する push service は RFC8030 Section 5. に記載されている push resource をサポートしています。user agent については、実ベンチマーカーに内包しているため、その他のエンドポイントについては実装されていません。
加えて、一般的なブラウザで Push API を利用する際必要になる RFC8291 (メッセージの暗号化), RFC8292 (VAPID を利用したサーバ認証) をサポートしています。
実ベンチマーカーはアプリケーションを実際のブラウザで利用した場合の挙動を模倣しています。すなわち、Push API を利用して得られる push subscription 情報を、必要に応じてアプリケーションへ送信します。ただし、RFC8292 における public key (VAPID 公開鍵) がアプリケーションより、実ベンチマーカへ送信されている必要があります (詳細は参考実装の挙動を確認してください)。
push subscription 情報については、push resource の URL に加え、W3C Push API: getKey() メソッド における p256dh
, auth
の値が提供されます。提供方法も参考実装の挙動に準じます。
そして、push resource へ送信した push message は即座に user agent (仮想選手) へ送信されます。仮想選手は通知を順次処理するため、同時に複数の通知に対応する動作を取ることはありません。
また、各言語の参考実装において既に存在するライブラリを利用しての動作を検証しています (サンプルコードについては後述)。
重複する内容もありますが、実ベンチマーカーが送信する push resource のエンドポイントについて、RFC に定義されていない動作、あるいは RFC を意図的に違反している点は下記の通りです。
これらは ISUCON10 本選競技の課題の範疇においては、Web Push ライブラリなどを利用している限り問題にはならないと考えています。
p256dh
, auth
の値が送信されます。Authorization
ヘッダの vapid
スキームを利用して認証される必要があります。aud
, exp
クレームのみを検証します。Prefer: respond-async
ヘッダが与えられたときの Link
レスポンスヘッダについては実装されていますが、 Link
ヘッダが示す URL は 404 Not Found を返答します。Prefer: respond-async
ヘッダを与えたときの動作については保証しません。TTL
ヘッダ, Section 5.3. Urgency
ヘッダ, Section 5.4. Topic
ヘッダに関しては、受け付けますが意味を持ちません。
仮想ポータルのクライアントサイド (Web ブラウザで表示されるフロンドエンド実装) に対して Web Push 通知でテストメッセージを送ることができる各言語のコードが用意されています。
これらのサンプルコードは、 xsuportal.proto.resources.Notification.TestMessage
をメッセージとして持つ通知 (xsuportal.proto.resources.Notification
) を生成し、指定された contestant の持つ push_subscriptions に対し Web Push で送ります。
~isucon/webapp
上で ./generate_vapid_key.sh
を実行し、VAPID 用の ECDSA 鍵 webapp/vapid_private.pem
を生成する
~isucon/webapp/vapid_private.pem
をロードするようになっています。/contestant
から通知を購読するsend_web_push
(後述) を実行し、通知を送信する正常に Web Push が送信できた場合、下図のような通知を即座に受け取ることができます。
(初期状態のサーバーサイド実装を利用している場合、send_web_push
で生成される通知は、ポーリング方式からも送信されます)
以下に各言語の実装および、その実行方法を記載します。いずれもサーバー上かつ、初期状態での実行を想定しています。
set -o allexport; source ~isucon/env; set +o allexport
cd ~isucon/webapp/ruby
bundle exec send_web_push.rb -c contestant_id -i vapid_private_key_path
-c contestant_id
通知を送信する contestant の ID(必須)-i vapid_private_key_path
ECDSA 秘密鍵 PEM ファイル(必須)
~isucon/webapp/generate_vapid_key.sh
で生成した鍵を利用できます。set -o allexport; source ~isucon/env; set +o allexport
cd ~isucon/webapp/rust
cargo run --bin send_web_push -- -c contestant_id -i vapid_private_key_path
-c contestant_id
通知を送信する contestant の ID(必須)-i vapid_private_key_path
ECDSA 秘密鍵 PEM ファイル(必須)
~isucon/webapp/generate_vapid_key.sh
で生成した鍵を利用できます。set -o allexport; source ~isucon/env; set +o allexport
cd ~isucon/webapp/golang
make
./bin/send_web_push -c contestant_id -i vapid_private_key_path
-c contestant_id
通知を送信する contestant の ID(必須)-i vapid_private_key_path
ECDSA 秘密鍵 PEM ファイル(必須)
~isucon/webapp/generate_vapid_key.sh
で生成した鍵を利用できます。set -o allexport; source ~isucon/env; set +o allexport
cd ~isucon/webapp/nodejs
npm run send-webpush -- ${contestant_id} ${vapid_private_key_path}
${contestant_id}
通知を送信する contestant の ID(必須)${vapid_private_key_path}
ECDSA 秘密鍵 PEM ファイル(必須)
~isucon/webapp/generate_vapid_key.sh
で生成した鍵を利用できます。