💺

ISUCON12 やったこと

2022/07/25に公開

ISUCON12 予選に出場したのでやったことのまとめ。ほぼ自分用のメモです。
まだ追記予定。

準備でやったこと

  • Cloud Formation Template を適用してリソースを構築

  • SSH 用の config ファイル(以下) 作成

    ~/.ssh/config
    Host i1
      HostName XX.XX.XX.XX
      User isucon
    
    Host i2
      HostName XX.XX.XX.XX
      User isucon
    
    Host i3
      HostName XX.XX.XX.XX
      User isucon
    
  • 事前準備していた Ansible を全サーバに適用

    • alp
    • bash_it
    • percona_toolkit (pt-query-digest)
  • 作業ファイルを Git 化

    • GitHub
      • webapp フォルダ以下
      • Nginx config
    • DB サーバローカル Git
      • /etc/mysql 以下
  • 各サーバで使用しないサービス停止

    sudo systemctl stop XXX
    sudo systemctl disable XXX
    
  • MySQL スロークエリ有効化

  • alp 設定

    • Nginx ログフォーマット形成
    • alp ログまとめコマンド形成
  • notify_slack インストール

改善でやったこと

  • MySQL サーバーをアプリケーションサーバから分離

  • index

  • GET /api/player/competition/:competition_id/ranking の SELECT tenant に LIMIT 1 つけた

  • dispenseID 関数の ID 生成をアプリのみでやるように変更

  • visit_history 周りの改善

    • 事前に visit_history テーブルから visit_history2 テーブルを作成するようにした

      CREATE TABLE `visit_history2` AS (SELECT `player_id`, `tenant_id`, `competition_id`, MIN(`created_at`) AS `created_at`, MAX(`updated_at`) AS `updated_at` from `visit_history` GROUP BY `player_id`, `tenant_id`, `competition_id`);
      ALTER TABLE `visit_history2` ADD PRIMARY KEY (`competition_id`, `tenant_id`, `player_id`);
      
    • アプリケーションコードで visit_history 使っていた部分を visit_history2 に置き換え。GROUP BY など不要になった。

  • sqlite3 を MySQL に以降

    • sqlite3 ダンプファイルを csv に変換
    for i in `seq 1 100`
    do
        sqlite3 -header -csv initial_data/$i.db "select * from competition;" > output/competition_$i.csv
        sqlite3 -header -csv initial_data/$i.db "select * from player;" > output/player_$i.csv
        sqlite3 -header -csv initial_data/$i.db "select * from player_score;" > output/player_score_$i.csv
        echo $i.db;
    done
    
    • Python で クエリ作成(以下サンプル)
    competition
    schema = "INSERT INTO `competition` (`id`, `tenant_id`, `title`, `finished_at`, `created_at`, `updated_at`) VALUES "
    with open("sql/competition.sql", "w") as w:
      for i in range(100):
        with open("output/competition_" + str(i+1) + ".csv") as f:
          lines = f.readlines()
          count = 0
          for l in lines:
            if count == 0:
              count += 1
              continue
            ls = l.split(",")
            res = ''
            # print(ls[0], ls[1], ls[2].rstrip('"').lstrip('"'), ls[3], ls[4], ls[5]);
            if ls[3] == '':
              res = schema + "('{0}', {1}, '{2}', NULL, {4}, {5});\n".format(ls[0], ls[1], ls[2].rstrip('"').lstrip('"'), ls[3], ls[4], ls[5].rstrip('\n'))
            else :
              res = schema + "('{0}', {1}, '{2}', {3}, {4}, {5});\n".format(ls[0], ls[1], ls[2].rstrip('"').lstrip('"'), ls[3], ls[4], ls[5].rstrip('\n'))
            w.writelines(res)
    
    • 出力した SQL をMySQL 初期化シェルに追加

    • player_score の行数が多く、初期化に時間がかかりすぎるので player_score は tenant_id, player_id, competition_id が同じデータの中で row_num が最大のもののみを残す

    • MySQL 移行に伴いアプリケーションコード書き換え

  • POST /api/organizer/competition/:competition_id/score でバルクインサート

できなかったこと

  • /api/player/player/ を 3 台目に割り振る
  • Nginx, カーネルパラメータをチューニングする

反省点

  • MySQL をダンプしてベンチ実行のたびにデータを初期化しなければならなかった

Discussion