ANTIGRAVITY LABEN
記事一覧/Agents & Manager
Agents & Manager/2026-09-01中級

数百ファイルを一気に書き出させるか、区切って書かせるか

エージェントに大量のファイルを生成させたとき、遅さの正体は書き込みではなくファイル監視側の再スキャンでした。書き出し方を4通り実測して、どこで区切るべきかを判断できるようにします。

エージェント運用16ファイル監視パフォーマンス8Node.js6ワークスペース設計2

プレミアム記事

書き込みそのものは、33ミリ秒で終わっていました。

400個のファイルを生成する処理の所要時間を測ったときの数字です。体感としてはもっと待たされている気がしていたので、この値を見て、待ち時間はファイルを書く側ではなく、それを見ている側で発生しているのではないかと考えました。

エージェントに一括生成をさせる作業は、いまや珍しくありません。記事の HTML を出力する、アイコンを全サイズ書き出す、codemod で数百ファイルに手を入れる。どれも「書き終わったあと、しばらく操作が重い」という共通の体験を伴います。

その重さの出どころを、書き出し方を変えながら測りました。結果は、私が事前に立てていた予想と2か所で食い違いました。

遅いのは書き込みではなく、その後ろで起きていること

ファイルを書くと、そのディレクトリを監視しているプロセスに通知が飛びます。エディタ、開発サーバー、テストランナー、エージェントのワークスペース索引。ひとつのディレクトリに対して、いくつもの監視が同時に張られているのが普通の状態です。

通知を受け取った側は、たいてい短い時間だけ待ってから処理を始めます。連続して届く通知をひとまとめにして、1回の再スキャンで済ませるためです。この待ち時間をデバウンス窓と呼びます。100ミリ秒前後に設定されていることが多い値です。

つまり、こちらが何個ファイルを書いたかより、通知がデバウンス窓の中に収まったかどうかが、相手側の仕事量を決めます。

この観点で測るには、書き込み時間だけでなく「100ミリ秒の窓で数えたとき、再スキャンが何回起きる形になったか」を数える必要があります。

計測に使ったスクリプト

Node.js の fs.watch で監視を張り、書き込み方を変えながらイベントのタイムスタンプを記録します。デバウンス窓は100ミリ秒として、間隔がそれを超えたところで再スキャン1回と数えます。

// bench.mjs — 書き出し方によるファイル監視イベントの違いを測る
// 使い方: node bench.mjs <inplace|stage> <ファイル数> <書き込み間隔ms> <recursive:true|false>
//   例) node bench.mjs inplace 400 0 true
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
 
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const BODY = 'x'.repeat(2048); // 1ファイル2KB。中身は測定対象ではないので固定
const DEBOUNCE_NS = 100n * 1000000n; // 100ms をナノ秒で持つ(hrtime.bigint と単位を合わせる)
 
async function run(mode, count, gapMs, recursive) {
  // 監視対象を毎回作り直す。前回の残骸が混ざると数が合わなくなる
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'watchbench-'));
  const target = path.join(root, 'out');
  fs.mkdirSync(target);
 
  let events = 0;
  const stamps = [];
  const watcher = fs.watch(target, { recursive }, () => {
    events++;
    stamps.push(process.hrtime.bigint());
  });
 
  const t0 = process.hrtime.bigint();
 
  if (mode === 'inplace') {
    // 監視対象のディレクトリへ直接書く
    for (let i = 0; i < count; i++) {
      fs.writeFileSync(path.join(target, `f${i}.txt`), BODY);
      if (gapMs) await sleep(gapMs);
    }
  } else {
    // 監視の外で作り、最後にまとめて移動する
    const stage = path.join(root, 'stage');
    fs.mkdirSync(stage);
    for (let i = 0; i < count; i++) {
      fs.writeFileSync(path.join(stage, `f${i}.txt`), BODY);
    }
    fs.renameSync(stage, path.join(target, 'batch'));
  }
 
  const t1 = process.hrtime.bigint();
 
  // 監視イベントは非同期に遅れて届く。閉じるのが早すぎると取りこぼす
  await sleep(1200);
  watcher.close();
 
  // デバウンス窓を超えた回数 = 相手側が再スキャンを開始する回数
  let rescans = 0;
  let last = null;
  for (const s of stamps) {
    if (last === null || s - last > DEBOUNCE_NS) rescans++;
    last = s;
  }
 
  console.log(
    JSON.stringify({
      mode,
      count,
      gapMs,
      recursive,
      events,
      rescans_100ms: rescans,
      write_ms: +(Number(t1 - t0) / 1e6).toFixed(1),
    })
  );
 
  fs.rmSync(root, { recursive: true, force: true });
}
 
const [mode, count, gapMs, recursive] = process.argv.slice(2);
await run(mode, Number(count), Number(gapMs), recursive === 'true');

await sleep(1200) を入れている理由だけ補足します。監視イベントは書き込みの完了より遅れて届きます。書き終わった直後に watcher.close() を呼ぶと、届く途中のイベントを落として、実際より少ない数を計測してしまいます。最初にこれで数が合わず、しばらく悩みました。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
大量ファイル生成が遅いとき、原因が書き込み側なのか監視側なのかを、自分の環境で切り分けられるようになります
負荷を下げるつもりで書き込み間隔をあける対処が、かえって状況を悪くする条件を、実際に踏む前に見分けられるようになります
出力先をワークスペースのどこに置くかを、監視の張り方まで含めて設計できるようになります
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

関連記事

Agents & Manager2026-08-17
サブエージェントが完了せず止まるとき、親側の未承認アーティファクトを先に確認する
サブエージェントに任せた工程が朝まで終わっていない。原因が子ではなく親の承認待ちだったときの切り分け方を、ログから滞留を洗い出す短いスクリプトと、8月に入った「always proceeds」の使いどころ、自動承認をどこで止めるかの判断とあわせてまとめました。
Agents & Manager2026-08-07
変更したファイルからテストを選ばせたら、9 割が「該当なし」でした — import グラフで届く範囲を 3 リポジトリで測る
変更ファイル起点でテストを選ばせたところ、9割の変更で対象が0本になりました。import グラフの到達範囲を3つのリポジトリで実測し、バレルファイルが経路を広げる問題や、選択契約を組み直すまでの過程を、実測データを添えた記録としてまとめています。
Agents & Manager2026-07-16
AGENTS.md を厚くするほどルールが守られなくなった — 遵守率を計測して指示を削るまで
AGENTS.md を厚くするほどルールが守られなくなる現象を、ルールを述語に分解して3週間分の遵守率として計測しました。console.log 残留の見逃しから始まった検証と、残すルール・lint に移すルールを分けて書き直すまでの一部始終を記録しています。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →