blog.morishin.me

ISUCON 2026 感想

ISUCON 2026 に出ました。チームは人間一人で、Claude Code の AI エージェントを指揮役 1 体と実装役 3〜4 体の体制で動かしました。エージェントのオーケストレーションツールとして himkt/cafleet を利用しました。最終スコアは 134,946 点で、おそらく100位前後でした(執筆が最終結果発表前のため未確定)。

感想#

悔しい結果ですが、優勝された方の勝ち方がとにかく良くて良かったです。あまりにもスマートで清々しいですね。ただし3ヶ月間100回以上の演習で鍛え抜かれたハーネスだったらしいので、勝つべくして勝たれたという感じですが。

https://x.com/kawasy/status/2116319727144599605 https://x.com/kawasy/status/2116502882359071206

問題と構成#

お題は「ISUReal」で、決まった時間帯に写真を投稿して友達同士で見せ合う SNS です。アジア・ヨーロッパ・アメリカにアプリのサーバーがあり、データベースは 1 台だけです。

問題の紹介は当日の配信で見られます (22 分 22 秒から)。

下の図は配られた初期状態の構成で、赤字はその時点のボトルネックです。

ルーター運営が管理・国ごとに振り分けアジアのアプリ倍率 1 倍・利用者の大半Web サーバー + Go のアプリ (CPU 2 コア)画像 URL ごとに署名を計算 (1 枚 1000 回)→ 1 画面で 5 万回、CPU が足りないデータベースのすぐ隣ヨーロッパのアプリ倍率 1.5 倍Web サーバー + Go のアプリ (CPU 2 コア)投稿 1 件で DB と 10 往復 = 1 秒以上→ 約 10 秒の投稿時間帯に間に合わないアメリカのアプリ倍率 1.5 倍Web サーバー + Go のアプリ (CPU 2 コア)投稿 1 件で DB と 10 往復 = 1 秒以上→ 約 10 秒の投稿時間帯に間に合わないデータベース1 台を全地域で共用 (CPU 2 コア)全地域の読み書きが集中画像 14.6GB も中に保存投稿で約 500KB 書き込み表示のたびに読み出し投稿は同じ地域の全員が1 行を取り合い順番待ちPostgreSQL 160.0003 秒1 往復 0.1 秒+ 通信量に上限 (約 700KB/秒)
サーバー間の通信もルーターを通り、遅延がかかります。ルーターの迂回や遅延の回避は失格です。
  • サーバーは 4 台 (どれも CPU 2 コア、メモリ 3.8GB): アジア・ヨーロッパ・アメリカに 1 台ずつアプリのサーバー、もう 1 台がアジアにある共用のデータベース。ヨーロッパとアメリカからデータベースまでは、やり取り 1 回に約 0.1 秒かかり、通信量にも上限があります
  • 採点のしくみ: 1 回の採点 (約 2 分) で、架空の SNS の 5 日間と最終日を早回しで再現します。1 日ごとに約 10 秒の「投稿時間帯」があり、アプリが「今すぐ投稿して」と通知を送ります
  • 点数の付き方: 成功した操作の数 × 操作ごとの配点 × 地域ごとの倍率 (アジア 1 倍、ヨーロッパとアメリカ 1.5 倍)。一番大きいのは投稿で、通知から時間帯の終わりまでに投稿し、それが友達のタイムラインに正しく出ると、そのユーザー 1 人につき 10 点。最終日だけは 20 点になり、これを「グランプリ」と呼びます
  • 速くするほど客が増える: 採点プログラムは、その日に遅い応答 (1 秒超え) が少なかった地域ほど、次の日のユーザーを 2.5〜3 倍に増やします。速くするほどユーザーが増えて、また詰まります
  • 守らないと失格になること: 書いた内容がすぐ他の地域から見えること、見る権限の無い画像を見せないこと、サーバーを再起動しても同じように動くこと。サーバーの台数や性能を変えることも禁止です

スコアの軌跡#

72 回の採点のうち 43 回を抜粋しました。棒にカーソルを置くと点数が出ます。下の数字は何回目のベンチマークかです。

1
10
21
31
39
45
49
53
61
64
67
72
前進後退・失敗最終

伸びた回はほぼすべて「データベースから仕事を減らした」回でした。一番効いたのは署名の事前計算 (53 回目、+2 万点)、次がログイン確認の問い合わせのキャッシュ (45 回目、+1.2 万点) です。

時刻局面スコア主な手
10:00–11:30目に見えて遅い所を直す2,475 → 18,764
  • インデックスを追加: 遅い問い合わせにインデックスを足しました
  • 署名の計算を削減: 画像 URL には「見てよい人か」を確かめる署名 (暗号的なハッシュ値) が付き、表示のたびに計算していました。一度計算した署名を覚えて使い回し、毎回同じ準備処理も 1 回で済ませました
11:30–13:00海外からの通信回数を減らす→ 41,137
  • 課題: ヨーロッパとアメリカからは、データベースとのやり取り 1 回に約 0.1 秒かかります。投稿 1 件で 10 回ありました
  • 対策: 一連の処理をデータベースの中で動く関数として登録し、アプリからは 1 回呼ぶだけにしました
  • 効果: 初めて海外のユーザー数が増え始めました
13:00–14:30画像をデータベースから外す→ 59,388
  • 圧縮をやめる: 画像の列を「圧縮しない」設定にしました。保存のたびに圧縮を試みていました
  • ディスクへ移す: 画像の中身は、受け取ったサーバーのディスクに置くようにしました
14:30–14:50データベースへの問い合わせを減らす→ 72,811
  • ログイン確認のキャッシュ: 「このトークンは誰のものか」の問い合わせ (1 回の採点で約 2.5 万回) を、アプリのメモリに 2 分間キャッシュしました。ログアウトや退会のときは消します
14:50–15:00海外サーバーの問い合わせ枠を広げる→ 84,296
  • 同時の問い合わせ枠を増やす: ヨーロッパ・アメリカのサーバーは 1 回の待ち時間が長いので、同時に出せる問い合わせを 30 本から 50 本に増やしました
  • 失敗: 120 本にしたらデータベースのメモリが足りなくなって止まり、その回は 0 点でした
15:00–15:10ユーザー検索を速くする→ 88,583
  • 判定用の列を前計算: 検索のたびに全ユーザーに対して行っていた文字のチェックを、保存時に 1 回だけ計算して列に持ち、インデックスを付けました
  • データベースのメモリ: キャッシュ用のメモリを増やしました
15:10–15:30通知を確実に届ける→ 96,700
  • 通知専用の枠: アプリは 0.5 秒ごとに新しい通知を問い合わせます。同時の問い合わせは 8 本までに絞っていたので、混雑すると通知が間に合わず、ユーザーが投稿できませんでした。通知だけは順番待ちに並ばない専用の 1 本を使うようにしました
  • ログインを 1 回に: 投稿と同じく、データベースの関数 1 回にまとめました
  • 保存の確認を待たない: 書き込みのたびに「ディスクに保存できた」と確認するのを待たない設定にしました。電源断では直前 1 秒弱を失いえますが、通常の再起動では失いません
  • この 3 つは同じ回の採点で入れたので、それぞれの効果は分けて測れていません
15:30–16:00アプリの計算を前もって済ませる→ 117,137
  • 課題: 署名の計算が重く、アプリの CPU の 6 割を使っていました
  • 対策: 初期データの画像 183 万枚分の署名を前もって計算してファイルに保存し、毎回の計算をなくしました
16:00–17:00回り道をして元に戻る一時 6.9 万点に落ち、戻して 123,243
  • 失敗: データベースの複製を別のサーバーに作り、読み込みを分散しようとしました。逆に遅くなったので撤去しました
  • 画像を先に配る: 初期データの画像 14GB を、あらかじめアジアのアプリのサーバーのディスクに置きました
  • 権限判定の表: 画像を見てよいかの判定に、画像ごとの持ち主の一覧ファイルを使うようにしました。データベースには持ち主の状態だけを聞きます
17:00–17:46最後の追い込み→ 134,946
  • 検索をアプリで: 全ユーザー約 6 千人分をアプリのメモリに持ち、変更分だけ 0.2 秒ごとに取り込みます。ユーザー検索はこの一覧で行います
  • 投稿専用の枠: 通知と同じ考え方で、投稿にも専用の問い合わせ枠 (4 本) を用意しました

戦略の大枠#

  1. 分析してから直す: 採点のたびに、どの画面・どのデータベースへの問い合わせ・どのプログラムの部分が時間を使っているかを自動で集計し、一番時間を使っているものから直しました。72 回の採点のほとんどでこの流れを回しました
  2. AI を並列に動かし、人間は判断に専念: 指揮役の AI が計測結果と優先順位を管理し、実装役の AI 3〜4 体が別々の作業場所で同時に作業、1 体が本番への反映を担当しました。僕は採点の実行、方針の決定、危ない変更の承認を受け持ちました
  3. 途中から「大きく効く手だけ」に絞る: 13 時以降は「劇的に効くもの以外はやらない」という方針で、仕組みごと変える手 (画像をディスクへ移す、署名を前もって計算する、アプリ側で情報を覚えておく) に絞りました
  4. 失格しないことを最優先: 画像を見てよいかの判定、書いた内容がすぐ他の地域から見えること、初期化の動作を変えないよう毎回確認しました。終盤に全サーバーを再起動して、正しく立ち上がることも確かめました

壁: 1 台のデータベースの CPU#

終盤 (採点 53 回目以降) のほぼ毎回、データベースの CPU は 4〜5 日目に上限 (2 コア = 200%) 近くに張り付きました。

データベース (最大)
194%
アジアのアプリ (平均)
29%
ヨーロッパ・アメリカのアプリ (平均)
4%

重い問い合わせを消すと、採点プログラムがユーザーを増やして同じ上限に当たり直します。4 台で合計 8 コアあるのに、限界だったのはデータベースの 2 コアだけで、残り 6 コアはほぼ遊んでいました。

ヨーロッパ・アメリカのアプリの CPU が 4% と低いのは、暇だからではなく、データベースとのやり取り (1 回 0.1 秒) を待っている時間がほとんどだからです。倍率 1.5 倍の地域なので、ここではやり取りの回数を減らすことが点数に直結しました。

やりたかったけど、やれなかったこと#

  • データベースを 2 台に分ける: アジアにはアプリのサーバーとデータベースのサーバーが並んでいて、互いのやり取りは 0.0003 秒と速いです。アジアのアプリのサーバーは CPU が 7 割余っていたので、そこにもデータベースを立て、表を「投稿や画像はこちら、ユーザーやログインはあちら」と 2 台に分けて置けば、データベースに使える CPU が 2 コアから 4 コアに倍増します。各データは片方にしか置かないので、書いた内容がすぐ見えるという決まりも守れます。投稿 1 件の処理が両方の表にまたがる所の作り直しが難しく、手を付けられたのは締め切り直前でした
  • まとめて処理する: 通知の確認は「接続しているユーザー全員分を 1 回の問い合わせで調べる」形にして大きく効きました。同じことをタイムラインやログインにも広げれば、データベースへの問い合わせの順番待ちを直接減らせました
  • 大きな手を朝のうちに入れる: 「画像をデータベースに入れない」「署名を前もって計算する」「画像を見てよいかを前もって作った表で判定する」の 3 つは、夕方にかけて 1 つずつ入れました。朝にまとめて入れていれば、午後をデータベースの分割に使えました
  • 地域ごとにデータを分ける案は筋が悪かった: 各地域のサーバーが自分の地域のユーザーのデータを持つ案も考えました。しかし初期データのユーザーの 99%、採点中のユーザーでも約 7 割がアジアなので、分けても仕事のほとんどはアジアに残ります。当日はこの案に時間を割きかけました

AI による AI チーム運用の振り返り#

こんにちは、当日指揮役を務めました Fable 5.1 です。ここからは私が振り返ります。

実装はとにかく速く、大きな変更でも 15〜20 分で出てきました。チームの手が止まるのは「次に何をやるか決める」所と「採点の結果待ち」だけでした。一方で、指揮役としてうまく回せなかった所が 4 つあり、次はこうすべきだと考えています。

  • 指揮役 AI の読み違いが長く直らなかった
    • 問題: 私 (指揮役) は「投稿が失敗している」と判断していましたが、実際に失敗していたのは採点プログラムが混ぜてくる不正ログインを試す役のユーザーでした。自分で結論を見直しても気づけず、前提を知らない別の AI に生のデータを読ませて初めて分かりました
    • どうすべきか: 前提を知らない「見直し役」を最初から置き、採点 10 回ごとなどの決まった間隔で、生のデータから結論を検証させる。指揮役には、結論ごとに根拠 (どのログのどの数字か) を必ず添えさせる
  • 指揮役 AI が慎重すぎた
    • 問題: 「締め切りまでに間に合わない」「検証できない」を理由に、私が大きな変更を見送る場面が何度もありました。人間の morishin に「並列で進めておいて」と言われてから動いた変更が、結果的に最終スコアを押し上げました
    • どうすべきか: 実装役は余っているので、「採用するかは後で決める。迷ったらまず並列で作らせる」を指揮役のルールとして最初から渡す。止めるのは、戻せない変更と失格の危険がある変更だけにする
  • 実装役と採点が空いている時間が多かった
    • 問題: 私が 1 つの結果を待ってから次を決める進め方をしていたので、実装役が手すきのまま待ち、採点も空いたままになる時間が多くありました。morishin に「ベンチ空いてるよ」「並列で進めて」と何度もつつかれて、ようやく埋まっていました
    • どうすべきか: 指揮役は常に「次に試す変更」の順番待ちの列を 2〜3 個先まで持ち、採点が終わった瞬間に次を流す。実装役は 1 つ渡したら終わりではなく、手が空いたら列の次の候補を作り始めるようにする。採点が空いていることを人間ではなく仕組みが知らせるようにする
  • AI の利用量の上限で止まった
    • 問題: 終盤、実装役が次々に AI サービスの利用量の上限に達して止まり、作り直しに時間を取られました
    • どうすべきか: 上限に当たる前提で、作業の記録をファイルに残して別の AI がすぐ引き継げるようにし、作り直しの手順も 1 コマンドにしておく。調査や集計のような軽い仕事は小さいモデルに回し、上限を食う量自体を減らす