Y

「ISUCON11 予選問題の解説と講評」を読んで試してみた

この記事は何か?

ISUCON12予選に参加しました。そのことは別の記事で書きますが、

ISUCON12予選参加時に参考にしたのは自分のブログでした。

なので、去年やってブログに書いていなかった「"ISUCON11 予選問題の解説と講評"を読んで試したこと」を書きます。

たぶん1年後の自分が参考にすると思います。

ISUCON11 予選問題の解説と講評

ISUCON11 予選問題の解説と講評
https://isucon.net/archives/56044867.html

ISUCON11 予選問題実践攻略法
https://isucon.net/archives/56082639.html

上記の2サイトに書いてあることをやりました。

実行環境

AMIがまだなかった(?)ため、matsuuさんのVagrantfileを利用させていただいた気がします。いつもありがとうございます。

vagrant-isucon/isucon11-qualifier-standalone/Vagrantfile
https://github.com/matsuu/vagrant-isucon/blob/master/isucon11-qualifier-standalone/Vagrantfile

コミットログとスコア

  1. 初期スコア (1,402)
  2. alp設定 (1,491)
  3. mysqldumpslow設定 (1,458)
  4. pprof設定 (1,386)
  5. プライマリキー変更 (17,340)
  6. バルクインサート (18,628)
  7. LIMIT 1 追加 (23,848)
  8. レベルカラム追加 (27,000)
  9. グラフデータ条件追加 (28,630)
  10. ログ停止 (32,770)

1 初期スコア (1,402)

https://github.com/zono/isucon11-qualify/issues/1#issue-1007352307

PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
190249 mysql     20   0 1713420 126116  11744 S  73.4   3.3   1:26.76 mysqld
212863 isucon    20   0 1372068 144984  11548 S  43.9   3.8   0:16.23 bench
212664 isucon    20   0 1526580  29908   7632 S  40.5   0.8   0:27.59 isucondition
   162 root      19  -1  303276 123968 122668 S  13.3   3.3   0:07.85 systemd-journal
188178 www-data  20   0   16616   9200   4116 S  12.6   0.2   0:09.35 nginx
188179 www-data  20   0   14728   7276   4116 S   7.0   0.2   0:03.13 nginx
   520 syslog    20   0  224500   4860   3416 S   5.6   0.1   0:03.58 rsyslogd

mysqld、対象アプリ(isucondition)、nginxがCPUを使っています。またbenchも同じサーバーで動かしていますね。

2 alp設定 (1,491)

https://github.com/zono/isucon11-qualify/issues/1#issuecomment-927283219

+-------+--------+------------------------------+-------+-------+---------+-------+-------+
| COUNT | METHOD |             URI              |  MIN  |  MAX  |   SUM   |  AVG  |  P99  |
+-------+--------+------------------------------+-------+-------+---------+-------+-------+
| 69454 | POST   | /api/condition/[-a-z0-9]+    | 0.020 | 0.232 | 706.684 | 0.010 | 0.104 |
|   403 | GET    | /api/isu                     | 0.004 | 3.000 | 342.580 | 0.850 | 1.012 |
|   530 | GET    | /api/isu/.+/icon             | 0.004 | 1.812 |  93.848 | 0.177 | 1.008 |
|   156 | GET    | /api/condition/[-a-z0-9]+    | 1.000 | 1.004 |  28.992 | 0.186 | 1.004 |
|    35 | GET    | /api/trend                   | 0.028 | 1.020 |  27.828 | 0.795 | 1.020 |
|   126 | POST   | /api/auth                    | 0.004 | 3.004 |  25.176 | 0.200 | 3.000 |
|    80 | GET    | /api/isu/.+/graph            | 1.000 | 1.004 |  14.092 | 0.176 | 1.004 |
|   130 | GET    | /api/isu/[-a-z0-9]+          | 3.004 | 3.004 |  13.328 | 0.103 | 1.000 |
|    55 | POST   | /api/isu                     | 0.004 | 0.116 |   4.208 | 0.077 | 0.116 |
|    39 | GET    | /api/user/me                 | 0.000 | 0.632 |   0.920 | 0.024 | 0.632 |
|    23 | POST   | /api/signout                 | 0.004 | 0.088 |   0.312 | 0.014 | 0.088 |
|    81 | GET    | /assets/favicon.d0f5f504.svg | 0.004 | 0.020 |   0.256 | 0.003 | 0.020 |
|    81 | GET    | /assets/vendor.ee7444dd.js   | 0.004 | 0.020 |   0.240 | 0.003 | 0.020 |
|    81 | GET    | /assets/index.23dac98b.js    | 0.004 | 0.012 |   0.196 | 0.002 | 0.012 |
|     1 | POST   | /initialize                  | 0.164 | 0.164 |   0.164 | 0.164 | 0.164 |
|    81 | GET    | /assets/index.144d8ca8.css   | 0.004 | 0.012 |   0.148 | 0.002 | 0.012 |
|    48 | GET    | /                            | 0.004 | 0.012 |   0.124 | 0.003 | 0.012 |
|    81 | GET    | /assets/logo_white.svg       | 0.000 | 0.008 |   0.104 | 0.001 | 0.008 |
|    37 | GET    | /assets/logo_orange.svg      | 0.000 | 0.008 |   0.096 | 0.003 | 0.008 |
|    21 | GET    | /?jwt=                       | 0.000 | 0.012 |   0.056 | 0.003 | 0.012 |
|    30 | GET    | /isu/........-....-.+        | 0.000 | 0.004 |   0.004 | 0.000 | 0.004 |
|     3 | GET    | /register                    | 0.000 | 0.004 |   0.004 | 0.001 | 0.004 |
+-------+--------+------------------------------+-------+-------+---------+

書き込みが多いことが分かります。解説にも以下の記載があります。

例年と違う要素として、予選では珍しい大量の書き込み処理と、書き込まれた分だけ重くなる表示処理をいかにパフォーマンスチューニングするかという要素も問題に取り入れました。

3 mysqldumpslow設定 (1,458)

https://github.com/zono/isucon11-qualify/issues/1#issuecomment-927284684

Count: 2504  Time=0.16s (405s)  Lock=0.00s (0s)  Rows_sent=1.0 (2483), Rows_examined=40767.5 (102081883), Rows_affected=0.0 (0), isucon[isucon]@localhost
  SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = 'S' ORDER BY `timestamp` DESC LIMIT N

Count: 1721  Time=0.08s (129s)  Lock=0.00s (0s)  Rows_sent=246.2 (423744), Rows_examined=18328.1 (31542676), Rows_affected=0.0 (0), isucon[isucon]@localhost
  SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = 'S' ORDER BY timestamp DESC

Count: 7035  Time=0.01s (38s)  Lock=0.00s (0s)  Rows_sent=0.0 (0), Rows_examined=0.0 (0), Rows_affected=0.0 (0), isucon[isucon]@localhost
  COMMIT

Count: 4016  Time=0.00s (11s)  Lock=0.00s (0s)  Rows_sent=0.0 (0), Rows_examined=0.0 (0), Rows_affected=0.0 (0), isucon[isucon]@localhost
  #

Count: 2304  Time=0.00s (10s)  Lock=0.00s (0s)  Rows_sent=0.0 (0), Rows_examined=0.0 (0), Rows_affected=1.0 (2304), isucon[isucon]@localhost
  INSERT INTO `isu_condition`   (`jia_isu_uuid`, `timestamp`, `is_sitting`, `condition`, `message`) VALUES ('S', 'S', N, 'S', 'S')

Count: 102  Time=0.04s (4s)  Lock=0.00s (0s)  Rows_sent=91.0 (9282), Rows_examined=7201.6 (734561), Rows_affected=0.0 (0), isucon[isucon]@localhost
  SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = 'S'  AND `timestamp` < 'S'    ORDER BY `timestamp` DESC

Count: 29  Time=0.04s (1s)  Lock=0.00s (0s)  Rows_sent=142.6 (4135), Rows_examined=7799.1 (226174), Rows_affected=0.0 (0), isucon[isucon]@localhost
  SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = 'S' ORDER BY `timestamp` ASC

合計405秒かかっているクエリーがあります。

4 pprof設定 (1,386)

https://github.com/zono/isucon11-qualify/issues/1#issuecomment-927287786

1109行目で1.98秒かかっています。pprof分かりやすいです。(ISUCON12予選では存在を忘れてて使いませんでした)

5 プライマリキー変更 (17,340)

https://github.com/zono/isucon11-qualify/issues/1#issuecomment-927289590

- PRIMARY KEY(`id`)
+ PRIMARY KEY(`jia_isu_uuid`, `timestamp`)
// 変更前
Count: 1877  Time=0.08s (144s)  Lock=0.00s (0s)  Rows_sent=251.8 (472671), Rows_examined=18193.5 (34149146), Rows_affected=0.0 (0), isucon[isucon]@localhost
  SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = 'S' ORDER BY timestamp DESC

// 変更後
Count: 4768  Time=0.00s (22s)  Lock=0.00s (0s)  Rows_sent=538.2 (2566154), Rows_examined=538.2 (2566142), Rows_affected=0.0 (0), isucon[isucon]@localhost
  SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = 'S' ORDER BY timestamp DESC

この発想はありませんでした。余分なインデックス作成が無くなるので良いです。

6 バルクインサート (18,628)

https://github.com/zono/isucon11-qualify/issues/1#issuecomment-927294136

var rows []IsuCondition

rows = append(rows, IsuCondition{
        JIAIsuUUID: jiaIsuUUID,
        Timestamp:  timestamp,
        IsSitting:  cond.IsSitting,
        Condition:  cond.Condition,
        Message:    cond.Message,
})

_, err = tx.NamedExec(
        "INSERT INTO `isu_condition`"+
                "       (`jia_isu_uuid`, `timestamp`, `is_sitting`, `condition`, `message`)"+
                "       VALUES (:jia_isu_uuid, :timestamp, :is_sitting, :condition, :message)",
        rows)
// 変更前
Count: 1064  Time=0.00s (3s)  Lock=0.00s (0s)  Rows_sent=0.0 (0), Rows_examined=0.0 (0), Rows_affected=1.0 (1064), isucon[isucon]@localhost
  INSERT INTO `isu_condition`   (`jia_isu_uuid`, `timestamp`, `is_sitting`, `condition`, `message`) VALUES ('S', 'S', N, 'S', 'S')

// 変更後
Count: 255  Time=0.00s (0s)  Lock=0.00s (0s)  Rows_sent=0.0 (0), Rows_examined=0.0 (0), Rows_affected=10.0 (2550), isucon[isucon]@localhost
  INSERT INTO `isu_condition`       (`jia_isu_uuid`, `timestamp`, `is_sitting`, `condition`, `message`)       VALUES ('S', 'S', N, 'S', 'S'),('S', 'S', N, 'S', 'S'),('S', 'S', N, 'S', 'S'),('S', 'S', N, 'S', 'S'),('S', 'S', N, 'S', 'S'),('S', 'S', N, 'S', 'S'),('S', 'S', N, 'S', 'S'),('S', 'S', N, 'S', 'S'),('S', 'S', N, 'S', 'S'),('S', 'S', N, 'S', 'S')

7 LIMIT 1 追加 (23,848)

https://github.com/zono/isucon11-qualify/issues/1#issuecomment-927302335

- "SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? ORDER BY timestamp DESC",
+ "SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? ORDER BY timestamp DESC LIMIT 1",
// 変更前
Count: 4862  Time=0.00s (20s)  Lock=0.00s (0s)  Rows_sent=637.5 (3099623), Rows_examined=637.5 (3099537), Rows_affected=0.0 (0), isucon[isucon]@localhost
  SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = 'S' ORDER BY timestamp DESC

// 変更後
Count: 1378  Time=0.00s (4s)  Lock=0.00s (0s)  Rows_sent=1.0 (1357), Rows_examined=1.0 (1353), Rows_affected=0.0 (0), isucon[isucon]@localhost
  SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = 'S' ORDER BY timestamp DESC LIMIT N

8 レベルカラム追加 (27,000)

https://github.com/zono/isucon11-qualify/issues/1#issuecomment-927305929

ALTER TABLE `isu_condition` ADD `level` VARCHAR(8) NOT NULL DEFAULT "warning";
UPDATE `isu_condition` SET `level`="info" WHERE `condition`="is_dirty=false,is_overweight=false,is_broken=false";
UPDATE `isu_condition` SET `level`="critical" WHERE `condition`="is_dirty=true,is_overweight=true,is_broken=true";

高得点を狙うならデータ構造の変更は必ず発生する と思ってます。(またはISUCON12予選のSQLiteからMySQLヘの変更のようにDB自体を変更することも)

9 グラフデータ条件追加 (28,630)

https://github.com/zono/isucon11-qualify/issues/1#issuecomment-927307267

- rows, err := tx.Queryx("SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ? ORDER BY `timestamp` ASC", jiaIsuUUID)
+ rows, err := tx.Queryx("SELECT * FROM `isu_condition` WHERE `jia_isu_uuid` = ?"+
+          " AND ? <= timestamp AND timestamp < ?"+
+          " ORDER BY `timestamp` ASC", jiaIsuUUID, graphDate, graphDate.Add(24*time.Hour))

これは仕様理解が必要で、ISUCON11予選は仕様理解が難しかったです。前日は仕事を休む方がいいです。(ISUCON12予選は休んだけど遊びに行きました)

10 ログ停止 (32,770)

-        c.Logger().Warnf("drop post isu condition request")

これも気づきませんでした。詰めが甘いです。

まとめ(1年前に思ったこと)

  1. pprof便利
  2. アプリ仕様理解するの難しい
  3. benchのソースコード理解したい

補足 Makefile

https://github.com/zono/isucon11-qualify/blob/f9c424a0beafc719c4b4b67600c135d26f5ee532/Makefile

例: alp結果表示
$ make alp

1年間の自分は便利なツールを用意してくれたのに、ISUCON12予選では気づきませんでした。(それでは勝てません)

2022/7/30に思うこと

成長が足りません。がんばるます。(2022年にはやっている言葉です to 来年の僕)