ISUCON12 予選通過しました
ISUCON12に「第7西東京市」で参加し、最終スコア86470 (1位!) で予選を通過しました このチームで参加するのは7回目らしく、そろそろ常連チームを名乗ってもよいかもしれない
ISUCONの知名度は近年めちゃくちゃ上がっており、なんと3回募集がある参加枠のうち2回は開始3分以内、3回目も20分経たず埋まるという大人気ぶりです。人気アーティストのチケット争奪戦かよ
ISUCON12 予選参加応募にお申し込みいただいた皆さん、ありがとうございました!🚀
— ISUCON公式 (@isucon_official) 2022年6月11日
各回の申し込み状況を確認したところ
・第一期 220枠 2:44
・第二期 215枠 1:10
・第三期 215枠 18:52
となり、一般応募650枠が22分48秒で埋まりました。参加者の皆さんにはDiscordにて連絡をいたします。 #isucon
以下、詳細です
日時
- 2022/07/23 10:00 - 18:00
メンバー
- whywaita(インフラ全般)
- icchy(応援係)
- nomeaning(アプリ全般)
本当は3人集まって参加する予定だったのですが、急遽whywaitaが来れなくなったのでicchy, nomeaningのみオフィスで集まり、whywaitaはDiscordでリモート参加という形になりました。 会議室を提供してくれた弊社に感謝
基本方針
- Golang
- ベンチマーカーをデバッガー代わりに使う(ローカル環境構築は行わない)
- 終了約1時間前(17:00以降)は破壊的な変更を行わず、再起動試験に徹する
主な感想
- ポータル・ベンチマーカー共にノートラブルだった
- 問題紹介動画のクオリティがすごい
レポジトリ
最終的なコード
タイムライン
※ commit logとSlackの発言をベースにしています
時間 | できごと | スコア |
---|---|---|
10:00 | CloudFormationでVM作成開始 当日マニュアル精読開始 初期ベンチ計測 |
2598 |
10:10 | VM作成完了 初期のコードをpush コードリーディング開始 ボトルネック計測環境の準備 |
- |
11:00 | コードを大体読み終わる SQLite3をMySQLに載せ替えるのを決意 一行ずつだとクソ遅いので変換スクリプトを書き始める |
- |
11:05 | billingReportByCompetitionを大会終了時のみ計算するように変更 | 3602 |
11:40 | SQLite3変換スクリプト書き終わる アプリケーション側のMySQL対応 |
3338 |
12:18 | playersAddHandlerをbulk insertにする | 2905 |
12:30 | SQLite3から移植してきたテナントデータが不完全であることが判明 | - |
12:35 | 変換スクリプトのバグに気づく(後述) | - |
12:40 | 正しく変換すると /initialize が間に合わないことに気づく |
0 |
~13:30 | bulk insert→ダメ mysqldump→ダメ LOAD DATA INFILE→ダメ テーブルごとバックアップしておいてSELECT INSERT→ダメ で頭を抱える、MySQL移行作業撤退も検討 |
0 |
~14:50 | 虚無の時間 SQLite3に戻しながらちょくちょく計測 この辺りで予選落ちも覚悟 |
3000~4000 |
14:50 | /initialize が呼ばれる度に毎回/var/lib/mysqlをリストアすることを思いつくcompetitionRankingHandlerの1+N解消 /initialize が通る |
6923 |
15:20 | playerHandlerの1+N解消 | 7025 |
15:25 | competitionScoreHandlerをbulk insertにする | 7972 |
15:29 | flockを消す | 8662 |
15:31 | MySQLのコネクション数を50まで引き上げる | 9506 |
15:35 | competitionScoreHandlerの1+N解消 | 11346 |
15:38 | competitionScoreHandlerでdead lockが発生していたので修正 | 23121 |
15:45 | App+DBの2台構成に変更 | 45857 |
15:57 | dispenseIDを高速化 ユーザーが異常に増えたことによりスコアは下がる |
20212 |
~17:00 | 虚無の時間2 | - |
17:10 | player_scoreで最新の一件のみに注目するように修正 これが最後の破壊的な変更 |
66960 |
17:19 | SQLLoggerとechoのDEBUGログを無効化 | 76721 |
17:19~17:29 | 再起動試験 | 77990~81167 |
17:31 | デフォルトのencoding/jsonは遅いのでgo-jsonを使うように修正 | 86872 |
17:33 | マニュアルを再度読む | - |
17:36 | dispenseIDの高速化をもう一度試す (ここでulidをそのまま使うと文字種がhexadecimal ([0-9a-f]) からBase32 ([A-Z2-7=]) に変わるため、レギュレーションに抵触するのではないかと思い"%x"に修正) やはりスコアが下がるのでLastInsertIdで行くことを決定 |
80859 |
17:38~ | ひたすらベンチを回し続ける | 85941~86999 |
17:53 | 86470が出たところでフィニッシュ | 86470 |
15時以降の伸びがすごいですね。
これね、開始5時間後の様子です #isucon pic.twitter.com/qA3VMVNEO6
— マイナンバーカード受取、自転車 (@icchyr) 2022年7月23日
その他
SQLite3→MySQL変換スクリプトのバグ
#!/usr/bin/env python3 import sqlite3 values_competition = [] values_player = [] values_player_score = [] for i in (1, 100): conn = sqlite3.connect(f'file:{i}.db?mode=ro', uri=True) cur = conn.cursor() for r in cur.execute('select * from competition'): values_competition.append(r) for r in cur.execute('select * from player'): values_player.append(r) for r in cur.execute('select * from player_score'): values_player_score.append(r) conn.close() sql = """ DROP TABLE IF EXISTS competition; DROP TABLE IF EXISTS player; DROP TABLE IF EXISTS player_score; CREATE TABLE IF NOT EXISTS competition ( id VARCHAR(255) NOT NULL PRIMARY KEY, tenant_id BIGINT NOT NULL, title TEXT NOT NULL, finished_at BIGINT NULL, created_at BIGINT NOT NULL, updated_at BIGINT NOT NULL ); CREATE TABLE IF NOT EXISTS player ( id VARCHAR(255) NOT NULL PRIMARY KEY, tenant_id BIGINT NOT NULL, display_name TEXT NOT NULL, is_disqualified BOOLEAN NOT NULL, created_at BIGINT NOT NULL, updated_at BIGINT NOT NULL ); CREATE TABLE IF NOT EXISTS player_score ( id VARCHAR(255) NOT NULL PRIMARY KEY, tenant_id BIGINT NOT NULL, player_id VARCHAR(255) NOT NULL, competition_id VARCHAR(255) NOT NULL, score BIGINT NOT NULL, row_num BIGINT NOT NULL, created_at BIGINT NOT NULL, updated_at BIGINT NOT NULL ); """[1:] CHUNK_SIZE = 100000 for i in range(0, len(values_competition), CHUNK_SIZE): sql += "INSERT INTO competition VALUES " + ', '.join(f"{d}".replace('None', 'NULL') for d in values_competition[i:i+CHUNK_SIZE]) + ';' + '\n' for i in range(0, len(values_player), CHUNK_SIZE): sql += "INSERT INTO player VALUES " + ', '.join(f"{d}".replace('None', 'NULL') for d in values_player[i:i+CHUNK_SIZE]) + ';' + '\n' for i in range(0, len(values_player_score), CHUNK_SIZE): sql += "INSERT INTO player_score VALUES " + ', '.join(f"{d}".replace('None', 'NULL') for d in values_player_score[i:i+CHUNK_SIZE]) + ';' + '\n' print(sql)
このスクリプトは正しく動きません。なぜでしょうか?
答え
... for i in (1, 100): conn = sqlite3.connect(f'file:{i}.db?mode=ro', uri=True) ...
1.dbと100.dbしか読まないため
for i in (1, 100+1):
って書いたときに101.dbが見つからないよと言われた時点で気づくべきだったね
最後に
- 運営の皆さん本当にありがとうございました。過去最高に快適な予選だったと思います
- ベンチマーカーもポータルもトラブルなくて本当にすごい
- 実は今回で5回連続予選通過しているらしい
- ありがとうwhywaita、ありがとうnomeaning
- 本選は3人で集まって参加したい
- 本選に参加される方よろしくお願いします