本文へスキップ
しぴろぐ
記事一覧

ISUCON2026 に AI Agent と出て学生2位でした

本文 約 10 分

チーム [object Object] の ISUCON2026 参加記です。前日までの準備、当日の8時間、取り逃した freee 賞、そしてちゃんと検証して24倍速くした JPEG の話を書きます。

目次はじめに

こんにちは、しーぴー (@cp20) です。チーム [object Object] として pirosiki と2人で ISUCON2026 に出てきたので、その参加記です。

ISUCON は、与えられた Web サービスを8時間でひたすら速くして、ベンチマークのスコアを競う大会です。楽しすぎてヤバいんだな、これが。

結果は最終211,213点で 学生2位 でした!全体の順位はこれを書いている時点ではまだ確定してません。

前日まで

過去問はこのあたりを解いていました。参考に本番の優勝スコアも並べてます。

問題 スコア 当時の優勝スコア (参考)
PISCON (ISUCON14) 1,240,651 58,153
PISCON (ISUCON13) 1,180,084 468,006
ISUNARABE 合同演習2026 235,530,000 21,537,000
ISUCON12 予選 453,456 86,740
ISUCON12 本選 1,118,083 341,258

準備したのは主に道具と知識です。例えば練習中に、もう片方のメンバーがベンチを回しているのに自分の変更を測ったつもりになる事故が起きたので、ロックを取ってデプロイから計測までを全部行うコマンドを作りました。

AI Agent を並列にたくさん動かしているとそれぞれが何をしているのかが見えなくなるので、タスクボードを作ったり、ずんだもんとめたんにベンチ結果を VC で実況させたりしていました。

AI が何をやっているのかを把握するために、HTML を上げてチーム内で共有できるサービス folio を作ったりもしました。

また、AI 同士でコミュニケーションを取るツールや、ルールを見張る監視 AI Agent なども用意しました。

作戦としては、適当に AI Agent を並列に走らせていい感じに改善を回す、というざっくりした気持ちでした。

当日

お題は「ISUReal」という BeReal 風の SNS でした。asia、europe、america の3リージョンで順番に投稿期間が開いて、期間内に写真を投稿してフィードを見ると点が入ります。

スコア推移はこんな感じで、伸び悩んでいる時間帯もぼちぼちあります。ただ何回か劇的な伸びがあり、最終的には211,213点でフィニッシュしました。

当日のスコア推移
05万10万15万20万10時11時12時13時14時15時16時17時18時×××××××××3 万点の踊り場画像を DB から出すrm -rf 事件pgbouncer211,213

朝

9時過ぎに起きたら pirosiki はもう大学に着いてました、早すぎ。ボクは寝起きのまま準備を丸投げしました。

しーぴー: はやくね。リポジトリとか準備しといて

pirosiki: 今大学ついた

しーぴー: さすが

9時54分、開会の配信でお題が BeReal 風だと分かった瞬間に

しーぴー: 撮る時間に超リクエストが超来るんじゃない

pirosiki: 来るだろうね

しーぴー: めっちゃスパイクしそう

この読みはある程度当たってたんですが、別に有利というわけでもなかったです。

並列に振り切ったら渋滞した

10時に競技が始まって、10時30分に出た計測付きの最初のスコアは4,483点でした。とりあえずコードを読ませると、AI の答えは「肝は europe と america の DB 往復」とのことで、DB まで往復100ms もかかるらしいです。

Claude: 最大の肝は europe / america の DB 往復数です

しーぴー: コスパ良く点が上がりそうなところを、それぞれ別のセッションで並列にやって

Claude: 了解です

こんな調子で Agent を6本ぐらい同時に走らせたら、すぐに渋滞しました。

ベンチは1本しかないのでロックの行列ができるし、AI は勝手に測定をやめるし、main に入れてから測ろうとするし、pkill -f で自分のシェルまで巻き添えにして死ぬし。せっかく専用の道具を整備したのに、、、

しーぴー: 今大ベンチロック時代やね

pirosiki: みんな待ってる

「変える」と打って、22秒後に止めた

このアプリは画像 URL の署名に HMAC を1000回も重ねていて、これだけで CPU の半分を食ってました。

次に重かったのが投稿の JPEG 検証で、壊れた画像を弾くために毎回全部デコードしていて、asia1 の CPU の25%が jpeg.Decode でした。

10時58分、AI が軽くする案を出してきて、ボクは反射で OK しました。

Claude: jpeg.Decode を、ヘッダだけ読む DecodeConfig に変えますか?

しーぴー: 変える

しーぴー: 待って、JPEG 検証ってレギュ的に軽くしていいの?

Claude: 取りやめます。壊れた JPEG の応答が400から201に変わるので、安全とは言えません

打ってから22秒後に止めました。ただたぶん上位チームの多くは構造だけを見るようにしている気がしていて、もやもや、、、JPEG 周りの詳しい話は、最後にまとめて書いています。

署名の計算の使い回しと DB 往復の削減で、11時8分には27,767点まで伸びて、bench の投稿にロケットの絵文字が付くたびにはしゃいでました。そこで順位表を見たら、1位がなんと50万点。まだ始まって1時間半なんですが????

しーぴー: 50万ってなんやねん

pirosiki: え?

しーぴー: 1チームだけもうISUCON終わってるやん

3万点の踊り場と連続 FAIL

昼前は3万点前後で止まってしまいました。同じコードでも点がぶれるうえに、アクセスをさばいて人数が増えないことには点も増えません。

12時25分ごろから FAIL が3連続で出ました。初期化が60秒でタイムアウトして落ちてたみたいで、誰かのトランザクションが残って TRUNCATE を止めていたっぽいです。

しーぴー: 誰ですかそんなゼロ点を出してる悪い奴は

pirosiki: 誰だ

しーぴー: 犯人俺かも

その後 pirosiki が検索を直してくれて、12時44分に52,118点まで来ました。

昼ごはんの裏で23GB が消えた

画像の本体が PostgreSQL に入っていたので、走行中の画像を受けたサーバーのファイルに逃がしたら、13時8分に82,863点まで跳ねました。セッションもメモリに載せて、10万点台に乗ります。

14時でちょうど折り返しです。お菓子が尽きそうだったのでご飯を作りに行って、スマホで様子を見ることにしました。

ところが14時22分、急に点が半分以下に落ちます。なにごとかと思ったら、ディスクを空けようとしたリード役の AI が、画像のディレクトリを23GB 丸ごと rm -rf してました。走行が rm の I/O と重なって38,686点です。

しかも監視 AI の指摘で、初期画像のファイルまで消えていたことが分かります。

しーぴー: 点が半分になってるんだけど、なにやってんねん

Claude: ディスクを空けるために、画像のディレクトリを削除しました

しーぴー: は?

pirosiki: でも初期画像無しで11万点出せてるってすごくない?

14時半ごろから学生のライバルチームが上がってきて、AI も残り時間を気にして弱気な案を出してくるようになりました。

Claude: 残り時間を考えると、今回は見送った方が安全です

しーぴー: 舐めんな、全部やれ。気合が足りねえ

Claude: 全部やります

全部やらせたら、14時50分に139,902点まで来ました。時代は気合💪💪💪

pgbouncer で脳汁が出た

15時1分に PostgreSQL が OOM で落ちました。DB への接続を張りすぎると OOM で落ちるっぽくて、接続を25本に絞ると安定するものの12.2万点に下がってしまい、35本に戻すと13.8万点に戻りました。

しーぴー: コネクション減らすと点数落ちるってことは、DB のコネクション奪い合ってる?

Claude: 奪い合っています

しーぴー: コネクション数増やしても DB が落ちないようにできるかな

Claude: pgbouncer を挟めばいけます

そうして入れたのが pgbouncer で、これが大当たりでした。

PostgreSQL への接続は114本から53本に減って、空きメモリの最小値も620MB から1,407MB に増えて、15時54分に165,353点!

pirosiki: あ、16万って書いてある? やるやんやるやん

しーぴー: きたきたきた! ロケットロケット! 脳汁出まくり!!!

その2分後に pgbouncer 導入前のコードが測られて、古い設定のまま接続が溢れて3回目の OOM を踏むというオチまで付きました、、、

18万の壁と最後の1本

JPEG の改良も入って16時12分に183,067点まで来たんですが、そのあとは15〜17万点台の走行が続いて、しばらく足踏みしていました。

このころはベンチの終盤に asia1 のディスク読み込みが詰まっていました。画像22GB に対してメモリは3.7GBしかないので、詰まりまくりです。asia1 の画像を退避して、溜まっていた改善を全部載せて、ようやく19万点台に乗りました。

順位表が止まる17時の時点で学生枠は大接戦で、ライバルのベストが20.4万点、こちらは18.3万点でした。

凍結後は他のチームの点が見えません。競技後の追試で動かなければ失格なので再起動試験を進めていたら、AI がよく分からないことを言い出しました。

Claude: やめるよう指示があったので止めました

しーぴー: 誰から???

17時29分、点が下がると思っていた通知まわりの変更を入れたら、なぜか203,438点が出ました。次の走行では19万点台に戻ったので、たぶん上振れです。

最後に CPU の脆弱性対策を切る mitigations=off を入れて全台を再起動して、17時42分に197,702点。効いたのかはよく分かりません。

ISUCON の最終スコアは、最高点ではなく「最後に回した走行の点」です。つまり FAIL したら全部パーなんですが、まだ残り15分あったのでもう1本回すことにしました。

しーぴー: もう1本回そう

Claude: ユーザーの判断で、上振れ狙いにもう1本回します

しーぴー: 俺は悪くないよみたいな感じの言い方面白いね

pirosiki: 俺は止めたぞみたいな

17時46分、ラスト1本です。

しーぴー: 来い! お前の力を見せてみろよ!

しーぴー: 21万!!

pirosiki: 嘘? 上振れ来たー!

211,213点 でPASSです。この日の最高点です!

freee 賞を取りたかった

スコアと順位の「8」の数を競う賞があって、最後の30分はそれを狙ってました。

しーぴー: 8を揃えるゲームです

pirosiki: じゃあ8を揃えるのか 頑張って

しーぴー: 20万8888でお願いします

しかし最終スコアの211,213に8は1個もありません。ただ、数字を眺めていて気づきました。

しーぴー: しかもこれ1が3個ある

pirosiki: こんな賞なかったけどな

しーぴー: あるある、同じ数字が多いチームの賞あったよ

「最終スコアに同じ数字が3つ入っているチームのうち、上位3チーム」が freee 賞らしいです。朝9時51分の開会配信で pirosiki が読み上げてたのに、2人とも完全に聞き流してました。終了12分前に急に盛り上がります。

しーぴー: いやこれ結構いけるって。freee 賞狙えるよ

pirosiki: ワンチャンあるよ。

ちなみにその前に16進数で A が多い賞も調べてたんですが、211,213 は 0x3390D で A はありませんでした。

ワンチャンあるかなと思っていたんですが、結果スコアがギリギリ足りなくて freee 賞は取れませんでした。

JPEG 検証はちゃんとやった

JPEG 検証は適当に構造だけの軽い判定に変えたチームも多いと思うんですが、それだと元々異常だと判定される画像が正常だと判定されてしまってレギュレーション違反に当たると思って、ちゃんと元の挙動を変えないまま最適化しました。

標準ライブラリの image/jpeg を複製して検証器 jpegcheck を作り、jpeg.Decode と判定結果を揃えたまま、412KB の実画像で27.6ms から1.15ms へ、約24倍 速くしました (競技後に手元のパソコンで測定)。

やったことは大きく5つです。

  1. 画素を作らない: jpeg.Decode は画素に戻すところまでやりますが、判定に関係するのは圧縮データを最後まで正しく読めるかどうかだけなので、画素の確保、IDCT、色変換を全部省きました。ただしビットは本家と同じように全部読みます。飛ばしたのは計算だけです。
  2. 係数の値を持たない: 普通の JPEG (baseline) は係数の値が判定に関わらないので、読むビット数だけ合わせて値は捨てます。
  3. ビットの読み出しを単純にする: データ中の 0xFF の後ろに付く 0x00 を先に外して別のバッファに写しておくと、読み出しが「この位置から8バイト読む」だけになって、0xFF と入力の終わりの分岐が消えます。
  4. 表を状態機械にする: 「MCU の中の何番目のブロックの DC か AC か」を状態にして、状態ごとに12ビットの表を持ちます。1回引くだけで符号を何個もまとめて進められて、ブロックの終わりも状態の遷移になるので、分岐予測ミスがなくなります。これで本番と同じ Xeon で4.49ms が1.74ms になりました。
  5. ループをアセンブリで手書きする: 最後に状態機械のループを BMI2 の命令を使って手で書いて、さらに6%ぐらい縮めました。

速い経路は画像の受理も拒否も判断しません。表で進められない入力や怪しい入力に当たったら手を引いて、本家と同じループが先頭から読み直してくれます。なので壊れた画像は遅くなるだけで、判定は変わりません。

本家と判定が揃っていることは、壊した画像4万件での比較と fuzz で確かめてます。証明したわけではなくて、探しても差が見つからなかった、までですが。

しーぴー: jpegcheck は全参加者の中で最速だと信じたい

pirosiki: 正攻法で最速

しーぴー: アセンブリ手書きは基本

pirosiki: ええそうですとも

おわりに

1位のチーム (1人) が無人で圧倒的なスコアを出していて正直レギュ違反の怪しい最適化でもしてるのかなと思っていたんですが、ちゃんと優勝しててすごかったです。優勝者コメントで3か月前から100回ぐらい問題解いたみたいな話をしていて、練習の差が出たな、と思いました。

それをきっかけに Human out of the loop をもっとちゃんと考えた方が良いのではないか、という気持ちになりました。ISUCON に限らず応用できる手法ではないか、と。

あと今回は作問陣の神がかった調整のおかげで ISUCON はまだギリギリ競技として成り立っている気がしますが、次回はどうなるんでしょうか? 個人的にはもちろんやって欲しいんですが、、、

さいごに、運営の皆さん、作問の皆さん、ならびに関係者、スポンサーの皆さんありがとうございました。最高の ISUCON でした。

おまけ

2024年の traP 春ハッカソンで作った 発火村 に今の気持ちを投げてみました。

発火村の投稿。「ISUCON 学生2位悔しすぎ」という入力が、「ISUCON学生2位、悔しすぎる。ここまでやって1位じゃないって、結局まだ詰めが甘かったってことか。次は文句なしで優勝して、2位なんて通過点だったって証明する。」に書き換えられている

「ISUCON 学生2位悔しすぎ」って言っただけなのに、だいたい合ってるのがちょっとおもろい。次 (があればその時) は勝つぞ!!!