14分

isutools 開発日記 — Claude Code と Codex で1日でつくった ISUCON プロファイラ

isucongoossdevelopmentai

isutools は ISUCON 用のオールインワンプロファイラです。何ができるかは紹介記事に、それで何点出たかはチューニング記録に書きました。この記事は3本目、「どう作ったか」の開発日記です。

時系列は git log に残っている通りで、最初のコミット(設計書)が8月3日の21:22、v1.0.0 のタグが8月4日の01:18でした。裏では並行して private-isu をスコア 0 → 541,650 までチューニングしていました。どういう進め方だったのか、そしていま何を悩んでいるのかを、記録として残しておきます。

8/3 日中 — 発端はチームメンバーのいつものツール

始まりは自作ではありません。go-sql-logger は同じチームの一員がいつも使っている SQL ロガーで、私も private-isu の練習でいつものように使っていました。

使っているうちに、「これをブラウザで、ソート済みで見たい。ついでに pprof も nginx のログも1箇所で見たい」という欲が出てきます。ただ、正直に書くと私にはそれを自力で実装する力も、かける時間もありませんでした。そこで Claude Code と Codex を使って拡張していた、というのが実際のところです。

拡張を重ねるうちに元の形からだいぶ離れてきたこと、公開するにあたって元リポジトリにライセンスファイルがないことから、着想元への敬意はそのままに、別モジュールとして新規に作り直すことにしました(このドライバをラップして計測するという方式は、いまも isutools の核です)。

21:22 — 最初のコミットは設計書

リポジトリの最初のコミットはコードではなく DESIGN.md です。要求を先に文章で固定しました。

  • MySQL / PostgreSQL / MariaDB / nginx / Apache / GraphQL / HTTP1-3 / WebSocket に対応する
  • アプリの変更は3行以内。オーバーヘッドは2%未満を検証する
  • git ハッシュ(+dirty)とホスト情報(CPU/コア/メモリ)を全レポートに必ず出す
  • スナップショット第一: リモートで計測してファイルを持ち帰り、手元でダッシュボードを開く
  • TDD でカバレッジ80%以上

「スナップショット第一」は21:25に追記した決定で、これが後の設計を規定しました。ライブビューはおまけで、本体は「ベンチごとに残るファイル」です。実行一覧・diff・スコア記録は、この決定から自然に出てきたものです。

実装に入る前に、設計書自体のレビューも挟みました。別の LLM に設計書を批評させて、on/off の契約や永続化まわりの曖昧さを先に潰しています。

21:49 — 最初の動くもの

設計書の27分後に最初の実装コミットが入っています。SQL の正規化(リテラルをマスクして WHERE id = ? に集約)、集計、ビルド情報、ホスト情報、依存ゼロの Web UI、管理サーバ。ここで v0.1.0 を打ちました。

組み込みが1行で済むのは、ドライバプロキシ(shogo82148/go-sql-proxy)に乗ったためです。

db, err := sqlx.Open(isutools.SQLDriverName("mysql"), dsn)

計測に失敗してもアプリは落とさない(fail-open)というのは、競技で使うツールの前提条件だと考えています。

22:37〜23:40 — ダッシュボード化と、認証の設計転換

  • 22:37 v0.2。ホームをダッシュボードにし、DB スキーマ検査(最初の接続で DSN を自動捕捉し、reset のたびにインデックス一覧を取る)、HTTP / アクセスログ / プロセスの各コレクターを追加しました
  • 22:57〜23:02 認証ポリシーの転換です。当初はトークン必須で作ったのですが、自分で使ってみると localhost で気軽に開けないことに耐えられませんでした。最終形は「loopback は認証不要 / リモート公開はトークン必須(未設定なら拒否)/ SSH トンネル運用だけ環境変数で明示的に opt-in」。計測は fail-open、公開は fail-closed という非対称に落ち着きました
  • 23:10 ホームを「実行一覧」に変更。ベンチのたびに日時 ID の行が増え、スコアと git リビジョンが並びます。「どのコミットで何点だったか」が消えないことがこのツールの一番の価値だと、使いながら気づきました
  • 23:25 タイムスタンプを JST に固定(tzdata のないコンテナでも time.FixedZone で動くように)
  • 23:40 pprof 統合。reset に連動して CPU プロファイルを自動採取します

isutools ダッシュボードの実行履歴。この夜のベンチがスコアとgitリビジョン付きで並んでいる
isutools ダッシュボードの実行履歴。この夜のベンチがスコアとgitリビジョン付きで並んでいる

この間ずっと、裏では private-isu のチューニングが走っています。機能を足す → すぐ実戦で使う → 足りないものが分かる → 次のコミット、というループが15〜40分間隔で回っていました。

00:12〜01:18 — advisor、そして v1.0

  • 00:12 v0.5。マシン全体の CPU busy/idle 内訳を追加しました。「アプリは速くなったのにハードは11.6%しか使われていない」と分かったのはこの機能のおかげです
  • 00:39 v0.6。advisor を実装しました。MySQL・nginx・OS・Go の設定を読んで「ISUCON の定石なのに未設定」を指摘するものです。初回実行で自分の環境から interpolateParams なし・gzip なし・buffer_pool 過小の3件が検出され、適用するとスコアが16%伸びました。作った本人が最初の受益者になった形です
  • 00:56 v0.7。パス正規化ルール・カウンタ API・WebSocket/SSE の分離
  • 01:07 v1.0。実行間 diff・セッション User Flow・k6 の例・ABBA 計測スクリプト
  • 01:18 ABBA 計測(off→on→on→off)の結果 -0.58%(誤差内)を記録して v1.0.0 のタグを打ちました

翌朝は README の整備とブログの執筆、日中もチューニングを続けて 541,650 で締めています。

開発体制について

すでに書いた通り、実装のほとんどは Claude Code と Codex によるものです。私がやっていたのは、要求を決めること、設計の分岐で判断すること、上がってきたものをレビューすること、実機で確かめることでした。

ふりかえって、速度に効いたのは次の3つだと思っています。

  1. 最初のコミットが設計書だったこと。 エージェントは仕様の曖昧さに弱いので、要求・非機能(オーバーヘッド予算・認証の方針)・やらないことを先に文章で固定したのが、並行作業の衝突を防ぎました
  2. TDD と race detector をマージ条件にしたこと。 生成されたコードは、全パッケージのテストと -race を通らない限り取り込みません。深夜の速度でも品質が下がらなかったのはこの安全網のおかげです
  3. ドッグフーディングが最終テストだったこと。 全機能を private-isu に即日投入したので、ベンチマーカーという厳しい結合テストが毎回走っていました

事故も一度起きています。2つのエージェントが同一ツリーを同時に編集して、書きかけのファイルが別の作業のコミットに混入しました。退避ブランチと一時 index(GIT_INDEX_FILE)で復旧しましたが、以後「同時に触ってよいのは1系統まで」をルールにしています。

楽になったこと、悩んでいること

正直な感想も残しておきます。

楽になったのは間違いありません。 これまでは ISUCON の勉強をしようと思うと、練習環境を立てて、alp や pt-query-digest を設定して、ログの置き場所を整えて……という準備だけで休日が終わることがありました。それが今回は、欲しい計測環境そのものを1日で作れてしまいました。環境構築の重さが理由で練習から遠ざかる、ということはもう起きない気がします。

一方で、悩みも増えました。

これで勉強に なっているのか? 手を動かしてコードを書いたのはエージェントで、私は判断とレビューをしていただけです。設計の分岐や計測結果の読み方は確かに自分の中に残った感覚がありますが、「SQL の正規化を自分で書けるか」と問われると、以前と変わらず書けない気がします。理解した気になっているだけではないか、という不安は消えていません。

本番で差がつくのか? ISUCON の本番では誰もが同じように AI を使える(レギュレーション次第ですが)とすると、ツールを1日で作れること自体は差になりません。むしろ、限られた8時間で「どの計測を信じ、どの改善に張るか」という判断の質と速さだけが差になるのだとしたら、練習で鍛えるべきものは何なのか。環境構築が一瞬になった分、その問いがむき出しになった感じがしています。

まだ答えは出ていません。ただ、少なくとも「計測結果を読んで次の一手を決める」練習は AI があっても自分にしか積めないので、しばらくはそこに時間を使うつもりです。

タイムライン

時刻内容
8/3 日中チームメンバーの go-sql-logger を利用・拡張 → 新規開発を決断
21:22最初のコミット(設計書)
21:49最初の実装 → v0.1.0(SQL 正規化・集計・Web UI)
22:37v0.2(ダッシュボード・DB スキーマ検査)
23:02認証ポリシー確定(fail-closed + SSH トンネル opt-in)
23:10〜23:40実行一覧・スコア永続化・JST 固定・pprof
00:39advisor(初回実行で3件検出 → +16%)
01:07diff・User Flow・k6・ABBA
01:18v1.0.0(オーバーヘッド -0.58% を記録)
8/4 日中README・記事執筆・チューニング継続 → 541,650

学び

  1. 設計書は速度を落とすものではなく、上げるものでした。 特に並行開発では、文章化された契約だけが衝突を防ぎます
  2. 最初のユーザーは自分にするのがよいです。 advisor の3件検出も、認証の使い勝手の転換も、ドッグフーディングがなければ出てきませんでした
  3. 計測は fail-open、公開は fail-closed。 ツールの信頼はこの非対称で決まると考えています
  4. リリースは細かく刻む。 一晩で11タグは多く見えますが、「どの機能がどのスコアに効いたか」を後から追えるのはタグのおかげでした

リンク