Skip to content

Instantly share code, notes, and snippets.

@sorah
Created October 3, 2020 03:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sorah/a53534b54130bc6fe96327167e2167fd to your computer and use it in GitHub Desktop.
Save sorah/a53534b54130bc6fe96327167e2167fd to your computer and use it in GitHub Desktop.

ISUCON10 本選当日マニュアル

課題アプリケーション XSUCON について

20XX 年、人類はオーガナイザー X41 氏の主催するパフォーマンスチューニングコンテスト「XSUCON」に夢中になっていた。しかし開催直前となったある日、最新版の XSUCON ポータルサイトにはパフォーマンスに問題があることが発覚。このままでは多くの人にとって不満の残る大会になってしまうだろう。あなたは XSUCON 運営チームの一人としてパフォーマンスを改善しつつ、また大会を大いに盛り上げ、選手およびオーディエンスを満足させなければならない。

課題アプリケーション仕様

課題アプリケーション XSUCON の仕様については、XSUCON アプリケーションマニュアルを参照してください。

ISUCON10 本選 ポータルサイト

ISUCON10 本選の競技 (実競技) は下記ポータルサイトを利用します。 事前に登録した情報を用いてログインしてください。 なお、このページは競技開始時刻までアクセスすることはできません。

ポータルサイトでは、ベンチマークの実行・結果確認、質問/サポート依頼の送信、リーダーボードの確認ができます。

https://portal.isucon.net/contestant

リーダーボードの更新について

ポータルサイト上のリーダーボードのスコアは、競技終了前の 1 時間は他チームのスコアが更新されなくなり、自チームのスコアのみ確認が可能になります。

Discord の利用について

ISUCON 10 サポート Discord サーバは競技時間前後はすべてのチャンネルが発言不可となります。 ポータルサイトを通して質問やサポート依頼を送信することができますので、そちらを利用してください。

ただし、選手は Discord の確認が可能な状態、通知が受け取れる状態を維持してください。 これはポータルで送信した質問/サポート依頼の内容を運営が確認した上で、リアルタイムでのチャットが必要だと判断した場合、こちらから Discord 上でプライベートチャンネルを作成し、mention の上よびかけを行う場合があるためです。

また、アナウンス等も Discord で実施されます。

サーバ、ネットワーク構成について

事前に用意されたサーバは 3 台あり、これらのサーバに SSH を用いて接続し競技を行います。 ポータル右上に表示されているチーム ID を元に、以下の gist より割り当てられている「踏み台用 IP アドレス」と「チームサブネット」を確認してください。

[REDUCTED (公開版のため接続情報は非公開です)]

競技に用いる 3 台のサーバはチームのサブネットに対してそれぞれ 101 ~ 103 が第 4 オクテットとなります。具体的な IP アドレスはポータルにて確認することができます。

上記のサーバに接続するために踏み台を経由して SSH 接続を行います。 ssh_config(5) の例を以下に示します。なお、あくまで例示であり必ず以下の設定を利用する必要はありません。

 Host isucon-bastion
   HostName <踏み台用IPアドレス>
   Port [REDUCTED]
   User isucon

 Host isucon-server
   ProxyJump isucon-bastion
   User isucon
   HostName <自チームサーバのIPアドレス>

なお、踏み台用のサーバはポート番号 [REDUCTED] でログインすることができます。

重要事項

競技終了後は、ベンチマーク走行成績の追試を行いますので、Discord サーバ および http://isucon.net/ にて運営からお知らせをするまで、サーバの操作はしないでください。 競技終了後の作業は禁止行為にあたります。

競技に利用できる計算機資源は運営側が用意した 3 台のインスタンスのみです。 外部のメトリクス計測サービスの使用のみ特例として許可しますが、スコアを向上させるいかなる効果も持つものであってはいけません。

作業手順

以下の順序で作業を開始してください。

1. サーバーへのログイン

上記に記載したサーバーに対して SSH 接続してください。 ログインには参加登録に利用した ( = ポータルのログインに利用している) GitHub アカウントに登録されている SSH 鍵を利用します。

SSH ログインのユーザ名は isucon です。

[重要]isucon 以外のユーザーに関して、ユーザー削除や既存の公開鍵の削除、その他 sshd の設定変更等を行ったことにより運営による追試をおこなうことができない場合は、失格とします。

2. アプリケーションの動作確認

アプリケーションは Web ブラウザから利用することができます。

踏み台を経由したブラウザアクセスには、 SSH におけるローカルポートフォワーディング などを用いて表示することができます。 これ以外の方法で動作確認してもかまいません。

なお、初期状態ではアプリケーションの動作に必要なデータが不足しているため、アプリケーションをブラウザから動作確認するためには、一度負荷走行を実行してください (後述)。

以下に Team001 におけるローカルポートフォワーディングを実行するコマンドを例示します。 これは「リモートホスト isucon-server に SSH 接続をした上で」「ローカルの localhost:10443 への TCP 接続を」「リモートホストを通して 10.160.1.101:443 へ転送する」というコマンドです。

$ ssh -L localhost:10443:10.160.1.101:443 isucon-server

サーバーへ配置されている TLS 証明書の subject name は *.t.isucon.dev となっています。 *.t.isucon.dev の DNS レコードは 127.0.0.1, ::1 が設定されているため、 t.isucon.dev のサブドメインを利用して localhost のポートへアクセスすることで、TLS 証明書検証エラーを回避することができます。

以上を踏まえると、上記のコマンド例を実行している場合、 https://xsuportal.t.isucon.dev:10443 でアプリケーションへアクセスすることができます。

アプリケーションへのログイン方法

以下は https://xsuportal.t.isucon.dev:10443 でアプリケーションへアクセスできるように設定した場合の例です。

負荷走行が正常に実行された場合下記の 4 ユーザが登録されるので、動作確認にご利用ください。ログインはトップページ(仮想オーディエンス向けページ)から行うことができます。

ログイン ID (contestant_id) パスワード 備考
admin admin 仮想運営
isucon1 isucon1 仮想選手
isucon2 isucon2 仮想選手
isucon3 isucon3 仮想選手

データベースの初期化方法

初期状態のアプリケーションでは、初期化処理(POST /initialize)においてデータベースのレコードは削除しますがスキーマは初期化されません。スキーマを初期状態に戻したい場合は、以下の SQL ファイルをご利用ください。

~isucon/webapp/sql/schema.sql

動作確認に便利なスクリプト集

競技サーバーの ~isucon/webapp/tools ディレクトリ以下には、アプリケーションの動作確認に利用できる下記のスクリプトが置いてあります。 詳しい使い方は ~isucon/webapp/tools/README.md を参照してください。

File 説明
add_benchmark_job 仮想ベンチマークジョブのエンキューを行えます。
finish_benchmark_job 仮想ベンチマークサーバから 1 件デキューし、仮想負荷走行を完了させます。
show_notifications notifications テーブルの中身をデコードして表示します。

3. 負荷走行 (ベンチマーク)

負荷走行はポータルサイト上からリクエストします。 ポータルサイトの 競技参加者向けページ にアクセスし、 "Job Enqueue Form" から負荷走行対象のサーバーを選択、"Enqueue" をクリックで負荷走行のリクエストが行われ、順次開始されます。

なお、負荷走行が待機中 (PENDING) もしくは実行中 (RUNNING) の間は追加の Enqueue を行うことはできません。

参考実装

下記の言語での実装が提供されています。

  • Ruby
  • Rust
  • Go
  • Node.js

参考実装の切り替え方法

初期状態では Ruby による実装が起動しています。

各言語実装は systemd で管理されています。 例えば、参考実装を Ruby から Go に切り替えるには次のようにします。

sudo systemctl disable --now xsuportal-web-ruby.service
sudo systemctl disable --now xsuportal-api-ruby.service

sudo systemctl enable --now xsuportal-web-golang.service
sudo systemctl enable --now xsuportal-api-golang.service

/etc/hosts および *.t.isucon.dev ドメインについて

サーバーには初期状態で、割り当てられている 3 台のサーバーの IP アドレス + ベンチマーカーの IP アドレスが事前に /etc/hosts へ登録されています。これらのホスト名は自由に利用して構いません (これを利用しなくても構いません)。

例:

isu1.t.isucon.dev 10.160.1.101
isu2.t.isucon.dev 10.160.1.102
isu3.t.isucon.dev 10.160.1.103
isubench.t.isucon.dev 10.160.1.104

前述の通り、これ以外の *.t.isucon.dev の FQDN に関しては 127.0.0.1 (IPv6 は ::1) の DNS レコードが登録されています。また、サーバーに配置されている TLS 証明書は subject name が *.t.isucon.dev であるため、この名前であれば TLS 証明書の検証が通る状態で HTTPS 接続等を行うことができるようになっています。

ベンチマーカーにも同様の /etc/hosts エントリが登録されています。負荷走行の際は、ベンチマーカーより負荷走行の対象となるサーバーに対応する isu1 - isu3.t.isucon.dev のホスト名を利用した HTTPS 接続が行われます。その際、TLS 証明書検証が通る必要がある旨、留意してください。

また、ベンチマーカーの IP アドレスは isubench.t.isucon.dev として登録されています。この /etc/hosts エントリについて、編集しても構いませんが、その場合に負荷走行が正しく動作するかは運営は保証しません。

時計について

運営が管理するベンチマーカーについては、Google Public NTP サーバー (time1.google.com, time2.google.com, time3.google.com, time4.google.com) と systemd-timesyncd を利用して時刻同期が設定されています。

選手へ提供されるサーバーについても、初期状態で同様の設定がされています。

負荷走行について

ベンチマーカーによる負荷走行は以下のように実施されます。

  1. 初期化処理の実行 POST /initialize (20 秒でタイムアウト)
  2. アプリケーション互換性チェック (数秒~数十秒)
  3. 初期化処理の実行 POST /initialize (20 秒でタイムアウト)
  4. 負荷走行 (60 秒)
  5. 待ち時間 (5 秒)
  6. 整合性チェック (数秒〜数十秒)

初期化処理は 2 回実行され、それぞれ 20 秒以内に完了する必要があります。これを超えた場合、負荷走行は失敗になります。

ベンチマーカーは負荷走行終了ののち、5 秒待ってから整合性チェックを行います。 ジョブキューによる遅延処理等を追加した場合は、このタイミングまでに処理の整合性をとってください。

負荷走行終了後、5 秒経ってもレスポンスが返ってきていないリクエストはすべて強制的に切断され、タイムアウトとして数えられます。

アプリケーション互換性チェックもしくは整合性チェックが通らなかった場合、その負荷走行は失敗となります。

キャッシュについて

アプリケーションは下記の条件においてキャッシュが認められています。

  • GET /api/audience/dashboard
    • アプリケーションは、データの更新から最大 1 秒古い情報を返すことができます。ただし、ベンチマーカーが検知しない限りはそれより古い情報を返しても構いません。
  • 上記以外の HTTP リクエスト
    • データの更新が即時反映されていることを期待してベンチマーカーは検証を行います。ただし、ベンチマーカーが検知しない限りは古い情報を返しても構いません。

Conditional GET のサポートについて

また、実ベンチマーカーは一般的なブラウザの挙動を模した Conditional GET に対応しています。アプリケーションは、 Cache-Control やその他必要なレスポンスヘッダを返すことで、ベンチマーカーから Conditional GET リクエストを受けることができます。データが更新されていないことが期待されるリクエストにおいては、304 Not Modified を返したり、あるいはブラウザのキャッシュ有効期限の制御によってリクエストが発生していなかったりしても、ベンチマーカーはそれらのキャッシュを利用してレスポンスがあったものとみなします。

なお、ベンチマーカー内のユーザーは独立しているため、 Cache-Control: public 等が指定されていたとしても、ユーザー同士でキャッシュを共有することはありません。すなわち、ユーザー (仮想選手、仮想オーディエンス、仮想運営等) の間でブラウザキャッシュが使い回されることはありません。

タイムアウトについて

実ベンチマーカーにおいて設定されているタイムアウト値は下記の通りです。

  • 仮想ポータル
    • POST /initialize
      • 20 秒以内にレスポンスを返す必要があります。これを超えた場合、負荷走行は即時失敗します。
    • GET /api/contestant/dashboard, GET /api/audience/dashboard
      • 2 秒以内にレスポンスを返す必要があります。これを超えた場合、後述のスコア計算に従い減点の対象となります。
    • 上記以外の HTTP リクエスト
      • 10 秒以内にレスポンスを返す必要があります。これを超えた場合、後述のスコア計算に従い減点の対象となります。
  • 仮想ベンチマークサーバ
    • タイムアウトは規定しません。

なお、負荷走行終了から 5 秒経った時点でレスポンスを返していないリクエストについては強制的に切断され、上記のタイムアウト時間にかかわらずタイムアウトとして処理されます。

スコア計算

負荷走行のスコアは以下の計算式によって算出されます。

スコア = (仮想選手スコア * 大会規模ボーナス) + 仮想オーディエンススコア - 減点
  • 仮想選手スコア
    • 仮想負荷走行が 1 回完了する (10 点)
    • 仮想選手の質問(Clarification)に対して仮想運営から回答が行われたとき、その回答を質問者の仮想チームが初めて確認する (10 点)
    • 仮想選手がダッシュボードを表示する (2 点)
  • 大会規模ボーナス
    • 仮想選手の参加数によるボーナス (後述)
  • 仮想オーディエンススコア
    • 仮想オーディエンスがダッシュボードを表示する (10 回ごとに 1 点)

大会規模ボーナス

大会規模ボーナスは、仮想コンテストに参加した仮想選手の数(team に所属している contestant の総数)によって、以下の表に従って算出されます。

参加した仮想選手の数 大会規模ボーナス(倍率)
1-59 1.0
60-119 1.2
120-179 1.4
180-239 1.6
240-299 1.8
300- 2.0

失敗 (fail) と減点について

負荷走行時に発生するエラーによっては、減点されたり、即時失敗(fail)したりします。条件は以下の通りです。

  • アプリケーション互換性チェック、もしくは整合性チェックに失敗した
    • 1 回以上で即時失敗
  • 負荷走行中にアプリケーション起因の致命的な問題が発生した
    • 1 回以上で即時失敗
    • (例として、仮想チーム登録フェーズ中に、1仮想チームも登録ができなかった場合が該当します)
  • HTTP ステータスコードやレスポンス内容などに誤りがある
    • 1 回あたり減点 50 点
    • 100 回を越えたら即時失敗
  • リクエストがタイムアウトした場合 (条件は前項に記載)
    • 100 回あたり減点 100 点
    • 即時失敗は無し

計算の結果、スコアが 0 点以下になった場合は失敗として扱われます。また、即時失敗となったケースについても、0 点と数えられます。

特別賞

競技時間中のスコアが、最初に 20,000 を超えた 1 チームを特別賞とします。

最終スコア

競技時間終了後、再起動後に運営によって実行された負荷走行のスコアを最終スコアとします。

競技時間終了後に運営によって行う作業手順

  1. 全チームのサーバーを再起動し、10 分以上待つ
    • 運営がサーバー上で sudo systemctl poweroff を実施し、サーバーのシャットダウンを確認した後、サーバーを起動します。
    • お願い: 再起動のテストのため、意図的に poweroff されたサーバーの起動依頼について、運営では対応する余力がなく、ベストエフォートでの対応になります。確実にシステムを再起動するため、再起動手順をこのように定義していますが、競技時間中に再起動をテストする場合、sudo systemctl reboot 等で実施してください
  2. 全チームのベンチマークを運営より実行
    • 各チームの、競技時間中最後の負荷走行の対象となったサーバーへ負荷走行を行う。
    • 計測は各チーム 3 回ずつ実施する。
  3. (2) で実施した最後の負荷走行が fail となっているチームに関しては、再度計測を実施する
    • この際、再度 fail となったチームは失格とする。
  4. 全チームのサーバーを (1) と同手順で再起動し、ブラウザから動作確認を行う
    • 10 分を待たずにチェックを開始するが、動作が確認できなかった場合は 10 分経過を待つ。
    • ここで正常な動作が確認できなかったチームは失格とする。
  5. (2) の負荷走行で計測したスコアのうち、その中で最も高いスコアをそのチームの最終スコアとする
    • fail については 0 点と数える。

競技終了後の注意事項

  • 運営によるブラウザからの動作確認の際、運営は POST /initialize をリクエストしない旨留意すること。
  • 競技時間終了後は、選手は与えられたサーバーにおいて一切の操作を行わないでください。競技時間終了後の操作は失格となります。

レギュレーション

ソフトウェア事項

選手は主催者から Web アプリケーション (問題) が与えられ、選手は競技時間内にその Web アプリケーションの高速化を行う。選手は高速化された実装 (回答) を作成する時、主催者より与えられたソフトウェア (初期実装) をベースに実装しても良いし、しなくても良い。 どのプログラミング言語で初期実装が提供されるかは主催者は事前にアナウンスするが、その各々の性能が一致することは保証されない。

高速化の際、主催者より問題として与えられた Web アプリケーションから、以下の部分は変更しないこと。

  • アクセス先の URI、ただしサーバー側で生成する部分(ID など)は文字種([0-9] や [0-9a-zA-Z_] など)を変えない範囲で自由に生成しても良い
  • レスポンス (HTML の DOM, JSON オブジェクト等) の構造(表示に影響しない範囲での空白文字の増減は許可される)
  • JavaScript/CSS ファイルの内容
  • 画像および動画等のメディアファイルの内容

各サーバにおけるソフトウェアの入れ替え、設定の変更、アプリケーションコードの変更および入れ替えなどは一切禁止しない。 ベンチマーク中にポータル・マニュアル・レギュレーションといった、主催者の指示以外で利用が認められたサーバー以外の外部リソースを使用する行為(他のインスタンスに処理を委譲するなど) は禁止する。 ただしモニタリングやテスト、開発などにおいては、PC や外部のサーバーを利用しても構わない。

許可される事項には、例として以下のような作業が含まれる。

  • 複数台あるサーバーの役割の変更
  • DB スキーマの変更やインデックスの作成・削除
  • データベースに利用するミドルウェアの変更
  • キャッシュ機構の追加、ジョブキュー機構の追加による遅延書き込み
  • Web Push 実装の追加(XSUCON アプリケーションマニュアルを参照)
  • HTTP レスポンスの圧縮
  • 他の言語による再実装

ただし以下の事項に留意すること。

  • コンテスト進行用のメンテナンスコマンドが正常に動作するよう互換性を保つこと
  • サーバ再起動後にすべてのアプリケーションコードが正常動作する状態を維持すること
  • ベンチマーク実行時にアプリケーションに書き込まれたデータは再起動後にも取得できること

質問

選手は主催者へ質問を送信することができる。競技時間中は原則としてポータルから、競技前後は Discord 上のサポートチャットを利用する。質問は競技内容・マニュアル・レギュレーション等に対する疑問点・明確にしたい事項の確認や、サーバー障害などのトラブル報告・サポート依頼に利用することができるが、これに限らない。主催者は質問された内容について競技の一環である場合は、回答できない旨返答することがある。

競技時間中の質問について、主催者からの回答は全選手へ公開、あるいは個別に回答される。全選手へ公開される場合、質問内容の原文、あるいは主催者による内容の要約が公開される。未回答の質問・未公開の回答については質問した選手およびそのチームメンバー、主催者のみが確認できる。質問への回答/更新はポータルサイト上にて選手へ通知される。

主催者は競技時間中の質問への回答について、原則として全選手へ公開する。ただし、重複する質問や、選手およびチーム個別の問題 (サーバ障害など) に対する対応の場合、この限りではない。

禁止事項

以下の行為を特に禁止する。

  • 競技終了時間までに、競技の内容に関するあらゆる事項 (問題内容・計測ツールの計測方法など)を公開・共有すること(内容を推察できる発言も含む)
    • 不特定多数への公開はもちろん、他チームの選手と連絡を取り、問題内容等を共有する事 (結託行為) も禁止とする。
    • ただし主催者が Twitter, Web サイトにおいて公開している情報は除く。ポータルサイトにおけるログインを要するページにおいて記載されている内容は公開情報でない旨留意すること。
  • 主催者が他チームへの妨害、競技への支障となるとみなす全ての行為
  • 主催者が競技に不必要とみなす競技環境からのネットワークアクセス
    • 競技に必要なネットワーク利用としては、主催者の指示 (レギュレーション, マニュアル等含む) で許可・想定されている行為、提供されたインスタンスの初期状態で利用可能なサービスや、外部のバージョン管理システム (GitHub 等)、ソフトウェアパッケージのリポジトリ (apt, yum リポジトリや、docker, npm, RubyGems レジストリ, proxy.golang.org, crates.io 等) が例として挙げられる。
    • 一方、提供されたインスタンスからインターネットまでの経路上にあるホスト等、選手がその選手へ提供されていないホストについて直接のアクセスを試みる行為や、外部への不正アクセスを試みる行為は、主催者により不必要と判断される場合がある。なお、例示のため、これに限らない。

本マニュアルにおいて禁止とされた行為 (禁止事項) への違反は、失格となる。

XSUCON アプリケーションマニュアル

用語

ISUCON (実際のコンテスト/競技) と XSUCON (ISUCON 10 本選問題に登場する仮想的なコンテスト/競技) 内の用語を区別するために、本マニュアルでは「実」を付けた場合には ISUCON を、「仮想」を付けた場合には XSUCON を指します。

例:

  • ISUCON
    • 実ベンチマーカー
    • 実選手
    • ...
  • XSUCON
    • 仮想ベンチマーカー
    • 仮想選手
    • ...

XSUCON の流れ

実負荷走行の中で開催される XSUCON は、以下のような状態を持ちます。

  1. 0 秒時点: 仮想チーム登録フェーズ
  2. 10 秒時点: 仮想コンテスト開始
  3. 50 秒時点: 仮想スコアフリーズ
  4. 60 秒時点: 仮想コンテスト終了

上記のフェーズ遷移の時間は目安です。実際には、実ベンチマーカーによって POST /initialize で時間が指定されます。

1. 仮想チーム登録フェーズ

仮想選手が仮想チームを登録したり、メンバーを招待したりします。

実ベンチマーカーは、アプリケーションがチーム登録リクエストを処理できるかぎり、仮想チームを登録しようとします。これに対しアプリケーションは、レスポンスコードによって仮想チームの参加上限数を制限する事ができます。

例えば、XSUCON の参加チーム数を 10 チームにしたい場合は、11 チーム目以降のチーム登録リクエスト POST /api/registration/team に対し HTTP レスポンスコード 403 を返してください。

なお仮想チームには 1 チームあたり最大 3 人まで参加する事ができます。

2. 仮想コンテスト開始

仮想コンテスト(XSUCON)が開始します。これ以降の仮想チーム登録はできません。

仮想コンテスト開催中は、実ベンチマーカーは以下の 3 種類のユーザーを模したリクエストを行います。

  • 仮想選手
    • 仮想コンテストの参加者です。仮想ベンチマーカーへのエンキューや仮想ダッシュボード閲覧、仮想運営への質問など、競技に関する動作を行います。
  • 仮想運営
    • 仮想選手からの質問への回答を行います。
  • 仮想オーディエンス
    • 仮想コンテストには参加しませんが、仮想選手達のスコアを見守り、応援しています。一定の条件を満たすと仮想オーディエンスは増えていきます(後述)。

3. 仮想スコアフリーズ

仮想コンテスト終了まで、ダッシュボードは「スコアフリーズ」状態になります。

スコアフリーズ状態では、各ダッシュボードは以下のような挙動になります。

  • 仮想選手向けダッシュボード GET /api/contestant/dashboard
    • 仮想選手は、他仮想チームについては、スコアフリーズ状態になった時点までのスコアしか見られなくなります。自チームについては引き続き最新のスコアが表示されます。
  • 仮想オーディエンス向けダッシュボード GET /api/audience/dashboard
    • 仮想オーディエンスは、全ての仮想チームについて、スコアフリーズ状態になった時点までのスコアしか見られなくなります。

仮想スコアフリーズ中も仮想コンテストは続いており、仮想選手達は引き続き競技に関するリクエストを行います。

4. 仮想コンテスト終了

仮想コンテストが終了すると、仮想選手達は競技に関するリクエスト(仮想負荷走行のエンキューなど)ができなくなります。

各ダッシュボードにおける仮想スコアフリーズは解除され、他仮想チームの最新スコアが再び見られるようになります。

仮想ベンチマークサーバと仮想負荷走行について

image

実競技に用いるチューニング対象のアプリケーション(以下アプリケーションと表記)は「仮想ポータル (HTTPS)」と「仮想ベンチマークサーバ (gRPC)」からなります。

仮想ベンチマーカーは実ベンチマーカーの中に実装されているため、実装を見たり変更したりすることはできません。

以下、仮想選手が仮想負荷走行をエンキューしてから、その走行結果を仮想選手が受け取るまでの流れを説明します。

1. 仮想選手: 仮想負荷走行のエンキュー

HTTP リクエスト: POST /api/contestant/benchmark_jobs

仮想選手が仮想ポータルに対し、仮想負荷走行(benchmark_job)のエンキューをリクエストします。エンキューしたとき、ジョブのステータスは PENDING として設定されます。

仮想選手は、エンキューができる状況にあるときは即時にエンキューを試みます。以下のような状況にある場合は仮想選手はエンキューをせず、状況が変化するのを待ちます。

  • まだ FINISHED になっていない所属仮想チームのジョブ(PENDING, SENT, RUNNING)がすでにあり、まだ仮想負荷走行終了の通知を受け取っていないとき
  • 仮想選手本人あるいは所属仮想チームのメンバーが仮想運営への質問(Clarification)を投稿したあと、まだ仮想運営からの回答を確認していないとき

2. 仮想ベンチマーカー: ベンチマークキューのポーリング

gRPC サービス: xsuportal.proto.services.bench.BenchmarkQueue, プロシージャ: ReceiveBenchmarkJob

仮想ベンチマーカーは仮想ベンチマークサーバに対し、常にキューをポーリングしています。キューにジョブがあった場合はそのジョブがデキューされます。デキューされたジョブは、ステータスが PENDING から SENT に変更されます。

仮想ベンチマーカーは仮想チームと同じ数だけ用意されており、最大で全ての仮想チームの仮想負荷走行を同時に処理できる能力を有します。

仮想ベンチマーカーのポーリング挙動について

各仮想ベンチマーカーは、ReceiveBenchmarkJob の実行でジョブが返却されなかった場合(キューが空)、待ち時間を挟まず、即時にリトライをします。また、仮想負荷走行後、レポートを登録した後も、待ち時間を挟まず即時に ReceiveBenchmarkJob を実行します。

3. 仮想ベンチマーカー: レポートの登録

gRPC サービス: xsuportal.proto.services.bench.BenchmarkReport, プロシージャ: ReportBenchmarkResult

仮想ベンチマーカーは仮想負荷走行に対し、以下の通りレポートを送ります。

  • 仮想負荷走行が開始したとき
    • ジョブのステータスは SENT から RUNNING に変更されます。
  • 仮想負荷走行が完了したとき
    • ジョブのステータスは RUNNING から FINISHED に変更されます。

仮想負荷走行は開始したあと瞬時に完了します。

4. 仮想選手: 通知のポーリング

仮想選手のブラウザは一定時間ごとに通知リスト(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)から閲覧します。仮想選手は質問リストを見て回答されていることが確認できたら、停止していた上記の行動を再開します。

質問に対する個別回答と全体回答について

仮想選手からの質問に対する仮想運営からの回答には、「個別回答」と「全体回答」があります。仮想運営は、質問に対しどちらの種別の回答を行うかは、一定の確率で選択します。

個別回答では、質問を投稿した仮想チームのみ、質問および回答を閲覧できます。

全体回答では、質問を投稿した仮想チーム以外の仮想選手も含め、全ての仮想選手が質問とその回答を閲覧できます。全体回答が行われた場合、全仮想選手の通知リストに回答通知が加えられます。回答の通知を受け取った仮想選手は、その回答を即時に閲覧するため、質問リストへアクセスします。

仮想ポータルの通知機能について

image

仮想ポータルの通知機能は、ブラウザの 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 による通知を購読/購読解除できるようになります。

  • Web Push 通知の購読: POST /api/contestant/push_subscriptions
  • Web Push 通知の購読解除: DELETE /api/contestant/push_subscriptions

実ベンチマーカー内の仮想選手が使うブラウザも同様に、Web Push が実装されているため、上記 API が実装されていれば Web Push による通知を購読するようになります。

アプリケーションは、Web Push で全ての通知を送るように変更した場合は、通知リスト(GET /api/contestant/notifications)のリクエストに対して空の通知リストを返す事ができます。空の通知リストを返す場合も、正常時の HTTP ステータスコードは 200 であることが期待されます。

また、アプリケーションは 1 人の仮想選手に対して、Web Push 方式の通知とポーリング方式の通知を混在させても構いません。また、通知に関しては既に受信したものを重複して受信した場合でも、繰り返し処理されません。

実ベンチマーカーが実装する Web Push service について

ブラウザの 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 (仮想選手) へ送信されます。仮想選手は通知を順次処理するため、同時に複数の通知に対応する動作を取ることはありません。

また、各言語の参考実装において既に存在するライブラリを利用しての動作を検証しています (サンプルコードについては後述)。

Caveats

重複する内容もありますが、実ベンチマーカーが送信する push resource のエンドポイントについて、RFC に定義されていない動作、あるいは RFC を意図的に違反している点は下記の通りです。

これらは ISUCON10 本選競技の課題の範疇においては、Web Push ライブラリなどを利用している限り問題にはならないと考えています。

  • RFC8291 (暗号化) の利用が必須です。
    • W3C Push API の Section 4. 等 をはじめ、Web ブラウザの Web Push における RFC8030 の利用では、push message は暗号化される事が前提となっているためです。
    • したがって、push resource へ送信するリクエストについては、 RFC8291 に従い暗号化して送信する必要があります。暗号化されていないリクエストについては、push resource は HTTP 400 エラーを返します。
    • 実ベンチマーカーは push subscription をアプリケーションへ提供するとき、かならず RFC8291 で定義された鍵交換のための EC 公開鍵、共有鍵である、 Push API で取得できる p256dh, auth の値が送信されます。
    • push resource へ送信した push message の復号の失敗は実負荷走行のエラーとして記録されます。
  • RFC8292 (VAPID) の利用が必須です。
    • 実ベンチマーカーは Section 4. における restricted push message subscription のみを生成します。
    • したがって、アプリケーションからの push resource へのリクエストは HTTP Authorization ヘッダの vapid スキームを利用して認証される必要があります。
    • Section 2., Section 4.2. に従い、aud, exp クレームのみを検証します。
    • VAPID の ECDSA 公開鍵がアプリケーションより実ベンチマーカーへ送信されていない場合、実ベンチマーカーは push resource を生成し、アプリケーションへ送信することはありません。
  • RFC8030 Section 5.1. に定義されている push message receipt については、実装されていません (RFC 違反)。
    • push resource へのリクエストにおいて Prefer: respond-async ヘッダが与えられたときの Link レスポンスヘッダについては実装されていますが、 Link ヘッダが示す URL は 404 Not Found を返答します。
    • また、Prefer: respond-async ヘッダを与えたときの動作については保証しません。
  • RFC8030 Section 5.2. TTL ヘッダ, Section 5.3. Urgency ヘッダ, Section 5.4. Topic ヘッダに関しては、受け付けますが意味を持ちません。
    • user agent (仮想選手) へは即座に送信され、user agent は即座に push message に対応した動作を取ります。
    • user agent は通知を順次処理します。同時に複数の通知に対する行動は取りません。
  • 一部ライブラリにおける RFC8291 Section 3.1. 実装ミスを救済するため、1 度目の復号エラーについて、誤った ECDH shared secret の導出手順によりリトライを試みるようになっています。
    • 具体的には、 Go 実装は https://github.com/SherClockHolmes/webpush-go の不具合を救済しています。
    • RFC8291 Section 3.1. の ECDH 処理について、SEC1 Section 6.1.3., 2.3.5., 2.3.7. に従って実装すると shared secret (output) が常に 32 バイトになるところ、32 バイト未満になるケースが存在するためです。
    • リトライでも復号に失敗した場合は「不正な Web Push メッセージ」としてエラーになります。リトライで発生したエラーについては破棄され、エラーメッセージには 1 回目 (リトライ前) のエラーが記録されます。
  • 存在しない push resource へのリクエストは実負荷走行のエラーとして記録されます。
  • 存在したが expire した push resource へのリクエストは、エラーとなりませんが、user agent (仮想選手) へは送信されません。

Web Push サンプルコードについて

仮想ポータルのクライアントサイド (Web ブラウザで表示されるフロンドエンド実装) に対して Web Push 通知でテストメッセージを送ることができる各言語のコードが用意されています。

これらのサンプルコードは、 xsuportal.proto.resources.Notification.TestMessage をメッセージとして持つ通知 (xsuportal.proto.resources.Notification) を生成し、指定された contestant の持つ push_subscriptions に対し Web Push で送ります。

Web Push の購読とサンプルコードの実行方法

  1. ~isucon/webapp 上で ./generate_vapid_key.sh を実行し、VAPID 用の ECDSA 鍵 webapp/vapid_private.pem を生成する
    • 参考実装では自動で ~isucon/webapp/vapid_private.pem をロードするようになっています。
    • 複数のサーバーを利用する場合、同じ鍵ファイルを共有するようにする必要があります。
  2. 仮想ポータルにブラウザでアクセスして通知を受け取りたい仮想選手としてログインし、/contestant から通知を購読する
  3. send_web_push (後述) を実行し、通知を送信する

正常に Web Push が送信できた場合、下図のような通知を即座に受け取ることができます。

(初期状態のサーバーサイド実装を利用している場合、send_web_push で生成される通知は、ポーリング方式からも送信されます)

image

以下に各言語の実装および、その実行方法を記載します。いずれもサーバー上かつ、初期状態での実行を想定しています。

Ruby 実装: ~isucon/webapp/ruby/send_web_push.rb

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 で生成した鍵を利用できます。

Rust 実装: ~isucon/webapp/rust/src/bin/send_web_push.rs

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 で生成した鍵を利用できます。

Go 実装: ~isucon/webapp/golang/cmd/send_web_push/main.go

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 で生成した鍵を利用できます。

Node.js 実装: ~isucon/webapp/nodejs/src/sendWebpush.ts

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 で生成した鍵を利用できます。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment