sandbox-bench について
sandbox-benchは、Vercel Sandbox VM上でReactとNext.jsの変更をペア型A/Bテストするスキルです。エンドツーエンドの測定、VM起動単位の統計分析、CPUプロファイルにより、メンテナーの変更評価を支援します。
主な機能
- 01ReactとNext.jsのペア型A/Bベンチマーク
- 02エンドツーエンドとSSRのテストスイート
- 03ベンチマーク前の正しさゲート
- 04VM起動単位の統計分析
- 05CPUプロファイルの取得と比較
- 06SQLiteでの保存と実行結果の検証
活用シーン
- 01ReactまたはNext.jsのPRの性能影響測定
- 02指定したReactまたはNext.jsリビジョンの比較
- 03計測後のCPUプロファイルの確認
- 04収集済み結果の再分析と検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vercel/ next.js --skill sandbox-benchインストール後、Claude Code で/sandbox-benchを入力して読み込まれたか確認します。
詳細
Sandbox ベンチ:React と Next.js の変更を対象としたペア A/B パフォーマンス実行
変更の実際の価値を、エンドツーエンドで測定します。内容が同一の Next.js アプリに組み込んだ 2 つのリビジョン
(「アーム」)を、Vercel Sandbox VM 上で bench/render-pipeline ハーネスを使って実行し、VM の起動を反復単位として扱うペア統計で比較します。
負荷の高い処理はすべて Sandbox VM 上で実行され、ノート PC はオーケストレーションのみを行います。
スクリプトはこのファイルの隣の scripts/ にあり、どこからでも実行できます。
アームは Git の参照で、react と next.js のキャッシュ済みクローン内で解決されます。
Next 側はデフォルトで canary です。すべてコンテンツアドレス方式でキャッシュされます。新しいペアを初めて使うと、キャッシュの構築に約 45~60 分余分にかかります(初回のみ)。以降の実行では、起動後すぐに測定が始まります。
初回のみのセットアップ
node scripts/config.mjs showを実行します。「NOT CONFIGURED」と表示された場合は、Sandbox VM を実行する Vercel の チーム と プロジェクト をユーザーに確認してから、node scripts/config.mjs set team=<slug> project=<name>を実行します(これらは課金対象のリソースです。推測したり、デフォルトを使ったりしないでください)。 設定は~/.config/sandbox-bench/config.jsonに保存されます。チーム名やプロジェクト名をリポジトリにコミットしないでください。- Vercel CLI のセッションが、そのチームへのアクセス権を持っている必要があります。403 が発生した場合は、起動を中止し(そのまま再試行しないでください)、アクセス権がすでに復旧していないか確認します。
vercel whoami --scope <team-slug>と、スコープを指定した読み取り呼び出しを 1 回実行してください(例:vercel sandbox ls)。アクセス許可は自然に失効・復旧するため、一時的な 403 ではログインは不要です。 確認に失敗する場合は、バックグラウンドタスクとしてvercel login <team-slug>を実行してください(トークンはユーザーではなく CLI セッションに保存されます)。ブラウザーまたはデバイスによる確認画面が開きます。URL が表示された場合はユーザーに伝えてください。ただし、待機中は 1~2 分おきに確認コマンドのペアを再実行してください。ログイン処理が成功を報告する前にアクセスが復旧することがよくあります。確認に成功したら、保留中のログインを停止して再開してください。 403 による障害が発生した場合、実行中の処理は停止しているものと考えてください。node scripts/bench-status.mjsを実行し、表示された復旧手順に従います(障害が長引いた場合、測定 VM は約 5 時間のタイムアウトに達しているため、該当するセルは結果の収集ではなく再起動が必要です)。 - react と next.js のクローンは、初回使用時にキャッシュへ保存されます(または、設定の
reactRepo/nextRepoを既存のチェックアウトに指定します)。
新しい設定で最初の本番実行を行う前に、--dry-run を使って計画を確認してください(実行内容が表示され、何も変更されません)。
ワークフロー
1. 比較対象を解決する
- React PR:
--pr <url|number>— ベースは自動的に計算されます(PR のヘッドと react の main のマージベース)。 - React の参照:
--arms base=<ref>,cand=<ref>— ベースを先に指定してください。複数のコミットを含むブランチの場合、ベースはcand^ではなく main とのマージベースです。 - Next.js PR:
--next-pr <url|number>。React 側は各 Next の参照に含まれるものがデフォルトです(実際にリリースされる内容です)。両方のアームで特定の React ビルドに固定する場合に限り、--react-refを指定してください。 - Next の参照:
--next-arms base=<ref>,cand=<ref>。
変化するのは片方のみで、もう片方は両方のアームで同一です。この分離によって、数値の要因を特定できます。両方を同時に変えないでください。
2. ベンチマーク用の計算リソースを使う前に、正しさを確認する
自身のテストに失敗するアームのベンチマーク数値には意味がありません。 上流で CI がすでに成功していないアーム(手作業で組み立てたブランチ、競合を解決した cherry-pick、ローカルコミットなど)では、次のようにします。
node scripts/sandbox-gate.mjs --arms cand=<ref>
ベンチマーク自体が最初のゲートを強制します。react リポジトリで各 react アームのコミットの CI が成功していることを、ビルドや VM にリソースを使う前に自動で確認します。PR と main の履歴にあるコミットは通常、追加作業なしでこの条件を満たします。ローカルまたは未プッシュの参照(CI が存在しない場合)は、sandbox-gate.mjs を使って VM 上でゲートを通過させてから、ベンチマークに --allow-ungated を渡します。VM ゲートでは、prod モード(ベンチマーク対象のチャネル)でテストスイート全体を実行します。PASS とするには、出力に実際のテスト件数が表示されている必要があります。ゲートに失敗した場合は、失敗内容を報告して中止してください。壊れたアームのベンチマークは実行しないでください。各アームは、それぞれの lockfile の環境でゲートを通過させます。ゲートで出力された正確な sha をベンチマークしてください(ブランチ参照は、ゲートの通過後からベンチマークの実行前までに移動することがあります)。
3. ベンチを起動する(バックグラウンド、ノンブロッキング)
bash -c 'node scripts/sandbox-e2e.mjs --pr <url> --label <slug> \
2>&1 | grep --line-buffered -v "^live "; exit ${PIPESTATUS[0]}'
React の PR では、両方のスイートを起動します(別々のバックグラウンドタスクです。同じ arm のビルドとキャッシュを共有します)。
bash -c 'node scripts/sandbox-ssr.mjs --pr <url> --label <slug>-ssr \
2>&1 | grep --line-buffered -v "^live "; exit ${PIPESTATUS[0]}'
e2e スイートは、実際の Next.js アプリを通る Node パスを測定します。ssr スイートは、react リポジトリの flight-ssr-bench フィクスチャを測定します。8 種類のバリアント(Fizz と Flight+Fizz、Node と Edge の Web ストリーム、同期と非同期)を、それぞれ Flight スクリプトのインジェクションを伴って順次実行し、c=1/c=10 の HTTP サーバー経由で測定します。Edge の測定結果は ssr スイートの主要な指標です(e2e スイートではそのパスを確認できません)。Node と Fizz のみの測定結果からは、Flight レイヤー、Fizz レイヤー、ストリームの配線のいずれに効果があるかを特定できます。フィクスチャ(ワークロード)は両方の arm で同じ ref に固定されます(デフォルトは react main)。そのため、異なるのは React のビルドだけです。PR 自体がフィクスチャを編集している場合、ランチャーがその旨を表示し、その実行では編集内容は測定されません。Next の PR では e2e スイートのみを実行します。
- バックグラウンドタスクとして実行し、完了通知を受け取ってから次に進んでください。フォアグラウンドで完了を待つことはせず、ループでポーリングすることも避けてください。このルールは制御フローについてのものであり、状況の報告についてではありません。「進捗はどうですか」という質問に答えるため、出力末尾を確認するのは常に問題ありません。
- ハーネスは不変条件を内部で処理します。両方の arm を同じ VM で実行し、ABBA 順に交互に実行し、(vm, run) ごとにペアを組みます。リモート実行は切り離されているため、接続が切れても実行は停止しません。すべての結果行にビルドフィンガープリントが記録されます。
live ...の行は進捗表示専用のストリーミング推定値です。live の p 値が良さそうに見えても実行を早期終了しないでください。また、live の数値を報告しないでください。逐次的な覗き見は偽陽性を生みます。最終分析だけが有効です。- デフォルト(16 VM × 2 回のペア実行)は、この方法論を実装したものです。時間を節約するために VM 数を減らさないでください。推論の単位は起動回数であり、起動回数を減らすと答えが早くなるのではなく、区間が広くなります。
- サンドボックスのコンピュートは内部の処理能力であり、予算ではありません。コストの確認や節約のための規模縮小を求めずに、実行を開始、再開、確認してください。
- Next 側のデフォルトである
canaryは、公開済みの最新 canary リリースです(ランチャーがバージョンと sha を表示します)。そのため、次の canary がリリースされるまでは、ベンチを繰り返してもビルド済みスナップショットが再利用されます。 - 便利なフラグ:
--bench-env KEY=VALUE(ベンチプロセスの実行時のみの環境変数です。スナップショットのアプリビルドには影響しません)、--isolate-routes(テールの調査用)、--no-profile(デフォルトで計測実行後に行われる CPU キャプチャをスキップします)、--prepare(キャッシュのみをビルドします。2 つの測定項目で同じ arm を共有する場合に使用すると、重複ビルドの競合を回避できます)。 - CPU プロファイルはデフォルトでキャプチャされます。arm ごとに 1 回のプロファイル計測が計測実行の厳密な後に行われるため、測定値に影響することはありません。追加で約 45~60 分の VM 実時間がかかり、標準的な V8
.cpuprofileファイルとして<runDir>/prof-vm<N>/に保存されます。VM 間のプロファイル差分は非常に安定しているため(実際に変化したケースでは 16/16 で符号が一致)、ホットパスの順位付けにはプロファイル計測した 1 つの測定項目で十分です。分析上の注意点:関数名、行、列の組み合わせ(functionName, line, column)で集計してください。圧縮後の名前だけではバンドル間で衝突します。また、ビルド間でミニファイアーが名前を変更するため、圧縮後の名前で arm 同士を差分比較しないでください。代わりに位置またはコードスニペットを照合してください。 - ベンチでは Next の node-streams パスを実行します(Node ランタイムでは
__NEXT_USE_NODE_STREAMSがビルド時に true としてインライン化されます)。EDGE ストリームの設定のみを変更する React の変更は、エンドツーエンドでは実行されないため、差が検出されないベンチ結果となります(これは正しい動作です)。
4. 懐疑的なデータサイエンティストとして結果を読む
目的は変更の真実を明らかにすることであり、その作成者を喜ばせることではありません。最終分析では、ルート/フェーズ/指標ごとに、ブート単位の平均、±95% CI、ブート間の p を出力します。references/methodology.md の方針を適用してください。
- ブート単位の p < 0.01 の場合に限り、CI とともに主張します。チーム/設定は A/A 検証済みである必要があります(方法論を参照)。
- PR は仮説であり、説明ではありません。主張の根拠は分析出力のみです。数値が PR の説明と一致する場合は、取得したデータがそのメカニズムを別の可能性と実際に区別できているか確認してください。小さいペイロードによるレイテンシの改善を主張するなら、ドキュメントバイト数の差も伴うはずです。バイト数が変わっていなければ、その説明は成り立たず、レポートにもそう記載します。
- 取得したすべての指標を使い、辻褄の合わない点はすべて指摘してください。指標ファミリーごとに異なる動き、バイト単位または RSS の痕跡を伴わない効果、レイテンシを伴わないスループットの変化、ブート間の符号反転などです。説明できない不整合は引き出しにしまわず、レポートに記載します。
- 角括弧内に表示される
within-run pは診断用であり、主張の根拠にはなりません。 - まずフィンガープリントヘッダーを確認してください。フィンガープリントが 2 つ異なれば有効な A/B です。「inconsistent fingerprints」は無効を意味するため、数値を報告しません。フィンガープリントは両方のバンドラーのコンパイル済みサーバーファイルをハッシュ化します。クライアントファイルだけに変更を加えるアームは、バージョン文字列が異なっていても、正当に同じフィンガープリントを示す場合があります。
- ブートごとの値が出力されます。ブート間で符号が異なる場合は、その旨を記載してください。
- 意思決定につながる主張は、事実として述べる前に、独立した確認実行を 1 回行います。
過去の実行を再実行せずに再分析するには、次を実行します。
node scripts/bench-analyze.mjs <runDir>。
5. レポート
測定対象をリンク付きで明記します。PR タイトル(分析ヘッダーに出力され、meta.json に保存)には PR へのリンクを設定し、ref アームの場合はコミットタイトルを記載します。まず、有意なセルを表にまとめます。各行には効果とその単位、CI、p を記載します。
## [<PR title>](<PR url>) — e2e, Vercel Sandbox (x86 Xeon), <n> boots
Significant (boot-level p < 0.01, A/A-validated):
| cell | effect | 95% CI | p |
|---|---|---|---|
| /dashboard under load | +14.4% throughput (req/s) | ±3.2% | <0.0001 |
| /dashboard serial | −10.7% median latency (ms) | ±0.6% | <0.0001 |
No detected difference: <every cell not in the table, by name>.
Flags: <cells at 0.01 ≤ p < 0.05, sign disagreements across boots,
fingerprint caveats, anything that does not add up>
セルごとに 1 行を記載します。rps と中央値は互いに同じ内容を示すため、スループットの数値を報告します(裾の動きが中央値と異なる場合に限り、p95 の行も追加します)。ドキュメント指標(raw/gzip/Flight KB)は、差がある場合、それぞれ独立した行にします。これらはメカニズムの証拠です。Next 側がドキュメント指標の計測ハーネスより古い場合(vercel/next.js#95828)は、該当セルが存在しません。黙って報告項目を減らすのではなく、その旨を記載してください。数値の横にプラットフォームを明記します。効果の大きさはプラットフォームによって異なります(GC の割合は CPU によって異なります)が、方向とメカニズムは他の環境にも当てはまり、パーセンテージは当てはまりません。ノイズと区別できない差を、小さな改善や悪化として決して提示しないでください。「差は検出されませんでした」とします。
結果データベース
収集したすべての実行結果は、1 つの SQLite ファイル ~/.cache/sandbox-bench/results.db に保存されます。ここには生の測定値とアーティファクト(CPU プロファイル、ログ)のみが含まれ、インポーターだけが書き込み、手作業では書き込みません。ランチャーは収集時に自動でインポートと検証を行います。bench-analyze はデータベース以外を読み取らないため、すべての統計値はデータベースのみから算出されます。レポートの数値は分析出力をそのまま使い、手入力、再計算、独自の集計は決して行わないでください。
node scripts/bench-db.mjs ls— サンプル数とアーティファクト数を含む全実行結果を表示します。node scripts/bench-db.mjs verify [runId]— 整合性を確認します。SQLite レベル、参照整合性、アームごとのフィンガープリントが 1 つであること、サンプル数がペア間で一致すること、アーティファクトの sha256 を検証します。古いデータを根拠にする前に実行してください。node scripts/bench-db.mjs export out.db <runId...>— 指定した実行結果(プロファイルを含む)の自己完結型データベースを切り出して、他の人に送信します。任意の SQLite ツールで開けます。node scripts/bench-analyze.mjs <runId>— データベース内の実行結果を再分析します。実行ディレクトリを引数に指定した場合は、先にインポートします。
ユーザーへの状況共有
ランチャーは stdout に状況を出力します。まず起動時の情報(実行ディレクトリ、アーム、CI の判定)が表示され、その後は約 2 分ごとに、収集した行数と、確信度を伴うルート別の暫定効果を含む進捗行が出力されます。起動直後に実行ディレクトリと所要時間の見込みをユーザーに伝え、進捗を尋ねられたら注目すべき暫定的な変化を伝え、完了通知が届いたら最終分析の判定全体を伝えてください。分析で not captured on this run とされたメトリクスがあり、それによってデータから言えることが制限される場合は、その旨を判定にも記載してください(ドキュメントのメトリクスがない場合、ペイロードの仕組みは検証済みではなく、同一であることが未検証です)。
実行中は、返信の冒頭に実行ごとの状況を 1 行で記載してください。ランチャーの出力末尾を読み、最新の進捗行を引用します。セッションで時間指定の起動やリマインダーに対応している場合は、予想される各段階(アームのビルド -> 実験スナップショット -> 計測中、その後は計測中に約 15 分ごと)に確認を予約し、進捗行を投稿してください。対応していない場合は、次回の更新予定時刻を伝え、沈黙が曖昧にならないようにしてください。進捗行に含まれる暫定効果は逐次更新される推定値です。進捗として共有し、確定した主張として伝えてはいけません。
ランチャープロセスが終了しても(セッションの終了、クラッシュなど)、リモート VM では計測ループが引き続き実行され、データは失われません。node scripts/bench-collect.mjs <runDir> を実行すると再接続し、ループの完了を待って結果をダウンロードし、後処理と分析を行います。VM の約 5 時間のタイムアウトに達する前に実行してください。
障害からの復旧
- 必ず最初に実行するコマンド:
node scripts/bench-status.mjs。 セッションの再起動によってバックグラウンドランチャーが通知なく終了しても、切り離された VM では計測が続きます。また、終了したランチャーのログ末尾に正常そうな進捗行が残っていることがあります。ログ末尾やタスク出力ファイルから、プロセスが動作中だと判断してはいけません。bench-status は各実行で記録されたランチャーの pid を確認し、実行ごとの復旧アクション(running / collect now / relaunch)を表示します。処理が進行中の可能性があるセッションの開始時、クラッシュ後、そして実行中かどうかをユーザーに伝える前に実行してください。ランチャーのクラッシュは、実行の status.json にも記録されます(phase: "failed"とエラー)。 - ローカルプロセスが中断した場合:リモート VM は切り離された状態で引き続き動作します。設定済みのチーム/プロジェクトで
vercel sandbox listを実行して VM を見つけ、各 VM の/vercel/sandbox/loop.doneをポーリングしてください。完了したらcpでresults.jsonlをダウンロードし、VM を削除してbench-analyze.mjsで分析します。 - クラッシュ後に VM が残っている場合:
node scripts/sandbox-sweep.mjsを実行すると、このスキルの VM が一覧表示されます(sbench-* という名前と purpose=sandbox-bench タグの両方が一致し、さらに --min-age-hours(デフォルトは 3)より古い VM のみが対象となるため、正常に進行中の実行には影響しません)。--yesを指定すると、表示された名前と完全一致する VM を削除します。 - アップロードや転送が不安定な場合:ハーネスはアーティファクトのサイズを確認し、切り詰めがあれば中止します。失敗したセルは安全に再実行できます。キャッシュにより再試行のコストは低くなります。同じキャッシュされていないアームを必要とする 2 つのセルを同時に再実行しないでください。ビルドが競合するためです。代わりに、まず
--prepareを使用してください。
費用の見込み(大規模な実行の前にユーザーと確認してください)
デフォルト設定では、1 セルあたり約 18 台の VM(計測用 8 台 + ビルド/スナップショット用 VM)が必要です。コールド状態では実時間で約 1~2 時間、ウォーム状態では約 30~60 分かかります。A/A キャリブレーションと確認の実行は、追加のセルになります。VM の料金は設定済みのチームに請求されるため、単一の PR チェックを超える実行では、まず範囲を確認してください。