Y

ISUCON12予選の復習をしました 9

この記事は何か?

ISUCON12予選の復習記録です。

第9回はISUCON12 予選の解説 (Node.jsでSQLiteのまま10万点行く方法)の「9 Ranking APIでランキング集計するのをやめる」を試します。

先に結論を書くと、この対応は「失敗」しました。途中までの経緯を書きます。

目次

  1. リクエスト傾向確認
  2. アップロードされるCSVファイル確認
  3. rankingテーブルを利用するように変更

1. リクエスト傾向確認

alpでみていくと、ranking APIの呼び出される回数とscoreが入稿される回数は10~20倍くらい差があります。

ということで、確認しました。その結果、解説に書かれているとおり、/rankingは/scoreの30倍くらい差があります。

  • ALP抜粋
+-------+--------+--------+----------+--------+--------+--------+--------+--------+-----------+------------+--------------+-----------+--------+---------------------------------------+
| COUNT |  MIN   |  MAX   |   SUM    |  AVG   |   P1   |  P50   |  P99   | STDDEV | MIN(BODY) | MAX(BODY)  |  SUM(BODY)   | AVG(BODY) | METHOD |                  URI                  |
+-------+--------+--------+----------+--------+--------+--------+--------+--------+-----------+------------+--------------+-----------+--------+---------------------------------------+
|   106 |  0.012 |  3.656 |   31.532 |  0.297 |  0.000 |  0.144 |  2.560 |  0.536 |    39.000 |     62.000 |     5656.000 |    53.358 | POST   | /api/organizer/competition/.*/score   |
|  3320 |  0.012 |  9.100 | 2489.008 |  0.750 |  0.012 |  0.472 |  4.272 |  0.887 |     0.000 |  17281.000 | 47525692.000 | 14314.967 | GET    | /api/player/competition/.*/ranking    |
+-------+--------+--------+----------+--------+--------+--------+--------+--------+-----------+------------+--------------+-----------+--------+---------------------------------------+
  • ALP全体
isucon@ip-192-168-0-12:~/webapp$ alp -f /var/log/nginx/access.log --cnt --aggregates='/api/player/competition/.*/ranking','/api/organizer/competition/.*/score','/api/player/player/.*','/api/organizer/competition/.*/finish','/api/organizer/player/.*/disqualified'
+-------+--------+--------+----------+--------+--------+--------+--------+--------+-----------+------------+--------------+-----------+--------+---------------------------------------+
| COUNT |  MIN   |  MAX   |   SUM    |  AVG   |   P1   |  P50   |  P99   | STDDEV | MIN(BODY) | MAX(BODY)  |  SUM(BODY)   | AVG(BODY) | METHOD |                  URI                  |
+-------+--------+--------+----------+--------+--------+--------+--------+--------+-----------+------------+--------------+-----------+--------+---------------------------------------+
|     1 | 15.724 | 15.724 |   15.724 | 15.724 | 15.724 | 15.724 | 15.724 |  0.000 |    55.000 |     55.000 |       55.000 |    55.000 | POST   | /initialize                           |
|     1 |  0.000 |  0.000 |    0.000 |  0.000 |  0.000 |  0.000 |  0.000 |  0.000 |   205.000 |    205.000 |      205.000 |   205.000 | GET    | /api/organizer/competitions           |
|     1 |  0.000 |  0.000 |    0.000 |  0.000 |  0.000 |  0.000 |  0.000 |  0.000 |   479.000 |    479.000 |      479.000 |   479.000 | GET    | /index.html                           |
|     1 |  0.000 |  0.000 |    0.000 |  0.000 |  0.000 |  0.000 |  0.000 |  0.000 | 33294.000 |  33294.000 |    33294.000 | 33294.000 | GET    | /js/app.3a4ec98c.js                   |
|     1 |  0.000 |  0.000 |    0.000 |  0.000 |  0.000 |  0.000 |  0.000 |  0.000 |  4868.000 |   4868.000 |     4868.000 |  4868.000 | GET    | /css/app.83b4c321.css                 |
|     1 |  0.000 |  0.000 |    0.000 |  0.000 |  0.000 |  0.000 |  0.000 |  0.000 |   162.000 |    162.000 |      162.000 |   162.000 | GET    | /favicon.ico                          |
|    11 |  0.004 |  0.520 |    1.088 |  0.099 |  0.004 |  0.016 |  0.228 |  0.153 |    39.000 |    185.000 |     1664.000 |   151.273 | POST   | /api/organizer/player/.*/disqualified |
|    15 |  0.928 | 11.904 |   53.232 |  3.549 |  0.928 |  2.184 | 10.264 |  3.159 | 14950.000 |  33660.000 |   393099.000 | 26206.600 | POST   | /api/organizer/players/add            |
|    16 |  0.004 |  0.168 |    0.896 |  0.056 |  0.000 |  0.052 |  0.136 |  0.047 |    39.000 |    200.000 |     2263.000 |   141.438 | POST   | /api/admin/tenants/add                |
|    70 |  0.004 | 30.000 |  106.576 |  1.523 |  0.004 |  0.408 |  9.060 |  3.805 |     0.000 |   5203.000 |   119388.000 |  1705.543 | GET    | /api/organizer/billing                |
|    80 |  0.004 | 23.352 |   88.356 |  1.104 |  0.000 |  0.112 | 15.248 |  3.529 |     0.000 |   1614.000 |   110481.000 |  1381.013 | GET    | /api/admin/tenants/billing            |
|    84 |  0.004 |  1.496 |   12.664 |  0.151 |  0.000 |  0.072 |  1.248 |  0.245 |     0.000 | 602067.000 |  3698133.000 | 44025.393 | GET    | /api/organizer/players                |
|    85 |  0.004 |  3.244 |   39.848 |  0.469 |  0.004 |  0.228 |  2.576 |  0.608 |     0.000 |     39.000 |     1761.000 |    20.718 | POST   | /api/organizer/competition/.*/finish  |
|    92 |  0.008 |  1.100 |   13.672 |  0.149 |  0.008 |  0.072 |  1.092 |  0.199 |    39.000 |    223.000 |    17613.000 |   191.446 | POST   | /api/organizer/competitions/add       |
|   106 |  0.012 |  3.656 |   31.532 |  0.297 |  0.000 |  0.144 |  2.560 |  0.536 |    39.000 |     62.000 |     5656.000 |    53.358 | POST   | /api/organizer/competition/.*/score   |
|   502 |  0.004 |  8.236 |   96.832 |  0.193 |  0.000 |  0.108 |  1.248 |  0.461 |     0.000 |   2831.000 |   519832.000 |  1035.522 | GET    | /api/player/competitions              |
|  3320 |  0.012 |  9.100 | 2489.008 |  0.750 |  0.012 |  0.472 |  4.272 |  0.887 |     0.000 |  17281.000 | 47525692.000 | 14314.967 | GET    | /api/player/competition/.*/ranking    |
|  6936 |  0.004 | 30.004 | 2716.212 |  0.392 |  0.004 |  0.196 |  2.644 |  1.042 |     0.000 |   1457.000 |  6777835.000 |   977.197 | GET    | /api/player/player/.*                 |
+-------+--------+--------+----------+--------+--------+--------+--------+--------+-----------+------------+--------------+-----------+--------+---------------------------------------+

2. アップロードされるCSVファイル確認

ベンチマークは同じ大会にscoreを何度か入稿してきますが、scoreの件数は減らない(追記式のスコアデータがCSVとして何度も入稿される)というベンチマーカーの仕様があってそれを知った上であれば ON DUPLICATE KEY UPDATE を使って1クエリでいけるのでMySQLを使いました。

ということで、確認しました。その結果、解説に書かれているとおり、socreの件数は変更がありませんでした。

  • CSVを出力するコードを一時的に追加
isucon@ip-192-168-0-12:~/webapp/go$ git diff
diff --git a/go/isuports.go b/go/isuports.go
index c943073..3e16807 100644
--- a/go/isuports.go
+++ b/go/isuports.go
@@ -1103,6 +1103,22 @@ func competitionScoreHandler(c echo.Context) error {
        }
        defer f.Close()

+         // アップロードされるファイルの傾向を調べるために一時的にファイルに保存
+  currentTime := time.Now()
+  outFilePath := fmt.Sprintf("/tmp/%v-%v-%v.csv", v.tenantID, competitionID, currentTime.UnixNano())
+  outFile, err := os.Create(outFilePath)
+  if err != nil {
+    return err
+  }
+  io.Copy(outFile, f)
+  defer outFile.Close()
+
+  f, err = fh.Open()
+  if err != nil {
+    return fmt.Errorf("error fh.Open FormFile(scores): %w", err)
+  }
+  defer f.Close()
+
        r := csv.NewReader(f)
        headers, err := r.Read()
        if err != nil {
  • 出力されたCSV一覧
ubuntu@ip-192-168-0-12:/tmp$ ls | sort
1-4068d268-26b4-11ed-a74f-0ef728f3fa93-1661679113511136695.csv
1-5dc158aa-26b4-11ed-a74f-0ef728f3fa93-1661679164143958364.csv
16-3fbc64fd-26b4-11ed-a74f-0ef728f3fa93-1661679112403862815.csv
16-4375511b-26b4-11ed-a74f-0ef728f3fa93-1661679118652853644.csv
...
99-45611dc0-26b4-11ed-a74f-0ef728f3fa93-1661679121847557828.csv
99-45611dc0-26b4-11ed-a74f-0ef728f3fa93-1661679122419324934.csv
  • 出力されたCSV内の行数
ubuntu@ip-192-168-0-12:/tmp$ cat 99-45611dc0-26b4-11ed-a74f-0ef728f3fa93-1661679121847557828.csv | wc
     76      77    1155
ubuntu@ip-192-168-0-12:/tmp$ cat 99-45611dc0-26b4-11ed-a74f-0ef728f3fa93-1661679122419324934.csv | wc
     76      77    1080
  • 出力されたCSV内の内容

3. rankingテーブルを利用するように変更

以下手順で変更しました。しかし、ベンチマークが失敗します。ランキングが正しくないとエラーが発生します。

  1. rankingテーブル追加
  2. /score時にranking追加
  3. /ranking時にrankingテーブルを参照するように変更

感想

この対応方法については別の方のブログを参照するなどしてリベンジしたいと思います。

参考

1 ISUCON12 予選の解説 (Node.jsでSQLiteのまま10万点行く方法)

2 ISUCON12 予選当日マニュアル