isucon/isucon12-qualify

benchシナリオの要望が集まるissue

ToshihitoKon opened this issue · 22 comments

benchシナリオの要望が集まるissue
  • 新規テナントはデータ量が軽くて回りやすいので、あんまり追加したくない
  • 開催中の大会のplayerアクセスが多いはずなので、既存テナントの大会数が多いところのアクセスが集中して、新規テナントにはそこまでアクセスはない
    • 初期データのbenchmarker.jsonはplayer数で偏って入ってるのでランダムに選べば偏るはず
  • 新規テナントには既存データと同じくらいplayer入れてOK、BulkInsertで最初にドンと入れて残りは少しずつ
  • 1つの新規テナントに大会->player->score->billing ...を繰り返すとvisitデータ量増えていく?
    • 初期と同じく75visit per playerをリクエストで飛ばすのは難しい
  • loadAdjustorは対象scenarioが一周していないと並列度を増やせないので、一周が遅いworkerに引っ張られて並列数が増えない
  • 新規テナントがn個追加されたらその後、とかですね
  • だんだん追加されるテナントがデカくなるとか
  • 巨大団体が過去データを大量に引っ越ししてcsvがドカンとか
  • セルフホストしてたのを移行してくるシナリオ
  • ISUコングロマリット

ベンチ中のCSV入稿について

  • CSVはおなじcompetitionに対しては常に後ろに追記される
    • アプリケーション仕様的に後ろのほうのデータが新しい = ランキングに採用される
  • 一人あたり平均100行ぐらいのデータが含まれている必要がある
    • ISUCON開催中に100回ぐらいはベンチを回すはず
    • ぴったり100回ではなくブレが欲しい
  • CSV追記は入稿のたびに追記されてどんどん入稿されるデータが巨大になっていく
  • 自動化されているのかほぼリアルタイムでCSVが入稿されていくシナリオ
  • 失格シナリオはブン回す必要はない、あまり回しすぎるとプレイヤーが減りすぎる
  • 初期データに関しては、heavy tenant(id=1), 非破壊的検証テナント(id=2~99から20個くらい), 不人気の破壊的並列化OKシナリオテナント(残り)で分けると良いかも
  • player一覧APIは失格者も含めて返ってくる。初期データにはすべてvisit_historyがあるので、返ってきたプレイヤー数に100をかけるとbillingを確認できる
  • admin billingはSaaS管理者向けなのでお客さん向けではないので遅くてもいいけど、tenant billingが遅いとtenantで大会が開かれない=playerも来ない=スコアが出ない、みたいなのがいいのかな organizerのresultとbillingを高速化しないとplayerが回らない、としたい
  • admin billingを1 workerで見続ける、見終わったら1テナント増える
  • tenantは大会を開催して終了して、billingが返ってきたら新規大会を作成する
  1. テナント管理者が大会追加
  2. 参加者が回る

の部分が同期的なので、ここも大会を追加したら参加者用のworkerを別に生やして、適宜秒数を散らしたsleepを入れながら複数人が同時アクセスするようにしたい

// 大会を1つ作成し、プレイヤーが登録し、リザルトを確認し続ける
comp := &CompetitionData{
Title: data.RandomString(24),
}
{
res, err := PostOrganizerCompetitionsAddAction(ctx, comp.Title, orgAg)
v := ValidateResponse("新規大会追加", step, res, err, WithStatusCode(200),
WithSuccessResponse(func(r ResponseAPICompetitionsAdd) error {
comp.ID = r.Data.Competition.ID
return nil
}))
if v.IsEmpty() {
sc.AddScoreByScenario(step, ScorePOSTOrganizerCompetitionsAdd, scTag)
} else {
return v
}
}
// 大会のランキングを参照するプレイヤーたち
for loopCount := 0; loopCount < conf.rankingRequestNum; loopCount++ {
var err error
var ve ValidationError
var ok bool
for _, player := range players {
err = sc.tenantPlayerJob(ctx, step, &tenantPlayerJobConfig{
tenantName: conf.tenantName,
playerID: player.ID,
competitionID: comp.ID,
})
if err != nil {
// ctxが終了のエラーでなければ何らかのエラー
if ve, ok = err.(ValidationError); ok && !ve.Canceled {
return err
}
break
}
}
// ctx終了で抜けてきた場合はloop終了
if err != nil && ve.Canceled {
break
}

  • 参加者はF5連打だけするわけではない
    • ノータイム連打でスコアが上がりすぎる
  • 参加者が動いている間テナント管理者が止まってしまう
  • 複数人が同時に行動してロックを取り合ってほしい

参加者の行動パターン

現状だと一覧、player、rankingが1:1:1になるが、ブラウザの挙動を考えるとこんなふうになるはず

  1. /api/player/competitions で大会一覧を取る
  2. 一覧に含まれている大会を全てリクエスト /api/player/competition/*/ranking
  3. ranking上位のplayerをいくつかリクエスト /api/player/player/*
    • 全ての大会でやる必要はなさそう
  • 大会一覧は軽い + チューニングポイントではないので呼び出し回数は多くなくてよい
  • ランキング閲覧は画面に表示される分、一気に取るはず
  • playerはたまに気になった人を見るぐらい?

admin billingを高速化するとどんどん新規テナントが増える => 負荷がどんどん増える、のでここを調整できないと最終的にはサーバーがどんなに頑張っても耐えられない負荷を与えてしまう。

任意のAPIについて、429 Too Many Requestsを返してよい、という仕様を追加して、クライアントは Retry-After ヘッダの秒数だけ次のリクエストまで待機する、という仕組みを入れたい。(同じリクエストをリトライするか、別のリクエストに進むかはシナリオ次第でよい)

PlayerHandlerのレスポンスをcacheして一度もpurgeしなくても通る -> validation追加が必要

PeacefulTenantScenarioで

  • Playerを失格させる
  • 失格プレイヤーで情報を見に行く -> 403を確認

としているが、この確認が /api/player/competitions のみ。playerやrankingのようなリクエストが多い(cacheしたい)エンドポイントで失格状態を見ていないので、不正にcacheされてしまう可能性がある。

最低限、player(他人)とranking(どれか適当に)を見に行って確認したい。

打ちきりになったときに、failしたよというのが分かるようにしたい。

スコアだけだと以下のように正の値が出ることがあり、この場合に完走してスコアが出たのか打ちきりでfailになったのかが判別できない(とポータルの表示で困るので)

09:42:54.688464 ERRORは最大30件まで表示しています
09:42:54.688997 SCORE: 82 (+402 -320)

打ちきりになったときに、failしたよというのが分かるようにしたい。

#155
resultSumがisPassedを返すようになりました

整合性チェックで静的ファイルをいくつか確認する

  • nginxの設定をいじったりした場合に、うっかりstaticの配信を壊す可能性がある
  • ベンチだけ掛けていると画面が壊れていることに気が付かなくて、目視チェックで失格する可能性がある
  • そのような不幸をなくすため、 / (index.html) と css と js を一個ずつ取得して壊れてないかhash値をチェックする
    • マニュアルにもHTMLとCSSとJSの内容は変更するなという記述があるか要確認

負荷走行中にはアクセスする必要はない

初期シナリオで tenant.id=1 に対して /api/organizer/billing が5分かかる (on EC2)

[00] {"time":"2022-07-18T04:35:11.992840274Z","level":"ERROR","prefix":"echo","file":"isuports.go","line":"194","message":"error at /api/organizer/billing: write tcp 127.0.0.1:3000->127.0.0.1:36794: write: broken pipe"}
[00] {"time":"2022-07-18T04:35:11.992877566Z","id":"","remote_ip":"127.0.0.1","host":"isucon.t.isucon.dev","method":"GET","uri":"/api/organizer/billing","user_agent":"isucandar","status":500,"error":"write tcp 127.0.0.1:3000-\u003e127.0.0.1:36794: write: broken pipe","latency":281267359570,"latency_human":"4m41.26735957s","bytes_in":0,"bytes_out":3993}

"latency_human":"4m41.26735957s"

ベンチが終わってもサーバー側の処理は終わらないので、延々とMySQLがクエリを実行し続ける状態になり、これを何も知らない状態で見ると大変困惑するはず。

どうにかする方法(案)

  • tenant.id=1 のボリュームをもうちょっと加減する?
  • API実行に適当にタイムアウトを設ける
    • Goだと用意だけどPerlみたいなシングルプロセスモデルだと結構面倒くさい
  • admin billing を一周するまでは tenant.id=1 のシナリオは走らせない
    • admin billing が一周できるなら organizer billing も問題ないはずなので

admin billing を一周するまでは tenant.id=1 のシナリオは走らせない

なるほど
admin billingはid=1も含めて全部回して、それが終わるまでPopularTenantHeavyを発火させない形ですよね、benchで対応出来るものとしてはこれが良さそうなので入れます

レスポンスに変更が無い場合は304 not modifiedを許容する

  • score、disqualifyが実行されない限りそのテナントについては問題なし
  • admin billing、対象範囲のテナントに操作が無ければ304返してもヨシ

JWT鍵確認

  • 違う秘密鍵
  • 違う鍵方式
    #190

PlayerValidateがスッカスカかもしれない

エラーメッセージ、常にアクセス先のvhostを出してほしい。

リハ向けに以下の修正を入れています