本文へスキップ
開発日記読了 約10

AIエージェントを並列で動かしたらブラウザを奪い合った——ロック1本から4レーン分割までの実測

AIエージェントを並列で動かして壊れるとき、原因の多くは処理の速さではなく「同じログイン済みブラウザを共有していること」です。Chromeは1つのプロファイル(ログイン情報を保存する作業フォルダ)につき1つしか起動できず、複数のエージェントが同じブラウザに繋ぐと、クリックが永久に待たされます。

ブラウザを奪い合っていた — ロック1本から4レーン分割、そして再発まで

私たちは定期実行のAIエージェントでSNS運用・計測・動画生成を回しており、ロック1本での直列化から、ログインが要るサイトごとに4つのブラウザへ分ける「レーン分割」に切り替えました。待ち切れずに作業を諦めた便は1日2.13回から0.74回に減りましたが、9月には3.71回へ戻っています。この記事では、衝突の実例と2段階の対策、そして分けたレーンが再び混んだ理由を、ロックの記録4,453行から集計した実測値で公開します。

AIエージェントの並列実行で、実際に壊れた5つのこと

2026年7月下旬から8月にかけて、私たちの自動化は立て続けに奇妙な壊れ方をしました。どれもエラーで止まらず、「動いたように見えて結果がおかしい」形で出ています。

日付起きたこと共有していた物
2026-07-25SNSの作業中に、別ジョブがタブをGeminiの画面へ切り替えたブラウザのタブ
2026-07-29計測が同じ時間枠を3回収集し、Xの行が3重に追記された(取得件数は27→38→58件と食い違い)追記先のファイル
2026-07-30動画生成が2つ同時に走り、同じ動画を2回ダウンロード。書いた字幕ファイルはもう一方に上書きされたタブと出力ファイル
2026-08-08手動の作業中に定期ジョブが同じChromeを操作し、ページ移動が60秒のタイムアウトで2回続けて失敗CDP接続(外部からChromeを操作する口)
2026-08-12同じ相手に3件の重複返信を送った「実施済みか」を判定するログの文言

7月30日には、止まったジョブを起こし直す見張り役が2つ同時に再起動をかけ、1日に3回の多重起動衝突も起きています。復旧のための仕組みが、衝突の入口になっていたわけです。

5件のうち4件は、同じ時刻に2つのエージェントが同じ物を触ったことで起きました。最後の重複返信だけは違い、見張り役がログの言い回しを「未実施」と誤読してもう1便起動したのが真因です。並列のせいに見える事故でも、同時に動いたのか、同じ作業を2回やったのかを先に分けないと、対策を外します。

なぜ起きるのか——Chromeは1プロファイルに1つしか起動しない

ブラウザを操作するAIエージェントは、ログイン状態を保存した「プロファイル」を使います。Chromeはプロファイルに鍵のファイル(SingletonLock)を置くため、同じプロファイルで2つ目のChromeは起動できません。

AIにブラウザを操作させる接続部品 Playwright MCP のREADMEにも、"A persistent profile can only be used by one browser instance at a time, so concurrent MCP clients sharing the same workspace will conflict."(永続プロファイルは同時に1つのブラウザでしか使えず、同じ作業場所を共有する複数のクライアントは衝突する)と書かれています(microsoft/playwright-mcp(新しいタブで開く))。

厄介なのは、起動済みのChromeにCDPで繋ぐ構成です。この場合は複数のエージェントが同時に接続できてしまい、エラーは出ません。そのかわり相手が画面を動かし続けるので、クリック前の「要素が止まるまで待つ」判定が永久に通らなくなります。

READMEには --isolated(プロファイルをメモリに置き、ディスクに保存しないオプション)も用意されています。これなら並列に立ててもぶつかりませんが、ログイン状態も残りません。SNSや管理画面のようにログインが前提の作業には使えないのが、私たちが悩んだ点でした。

対策1——ロックを1本置いて、全ジョブを直列にした

最初の対策は、ブラウザを使う全ジョブに1本の鍵を持たせることでした。対象は当時13本(SNSの返信・投稿、計測、動画生成、改善ループなど)です。仕組みは、ディレクトリの作成が「作れた/作れなかった」を一瞬で決める性質(アトミック性)を使った、シェルの数行です。

# 要点だけを抜き出したもの
acquire_browser_lock() {
  local owner="$1" wait_min="$2" waited=0
  while true; do
    if mkdir "$LOCK_DIR" 2>/dev/null; then
      echo "$owner $$ $(date '+%F %T')" > "$LOCK_DIR/owner"
      return 0
    fi
    # 持ち主のPIDが死んでいれば残骸として回収 / 90分を超えた保持は強制解放
    (( waited >= wait_min * 60 )) && return 1   # 待ち切れず諦める
    sleep 30; waited=$(( waited + 30 ))
  done
}

実運用では、鍵そのものより「鍵が残ったまま持ち主がいない」状態の扱いでつまずきました。

これで衝突は止まりました。ただ記録を始めた8月2日から9日までの8日間で、待ちが39回、待ち切れず諦めた便が17回、最長の待ちは1,080秒(18分)でした。17回のうち9回は、動画生成が鍵を握っている間に起きています(生成は1本10〜40分かかります)。

対策2——ログインが要るサイトごとに、4つのブラウザへ分けた

直列にしかできない以上、待ちを減らすにはブラウザ自体を増やすしかありません。プロファイルを分ければ別々のChromeが同時に立つことを、使い捨てのプロファイルで確かめてから移行しました(2026-08-07)。

分ける軸は「ジョブ」ではなく「ログインが要るサイト」にしました。ジョブごとに分けると同じSNSに何度もログインし直すことになり、ログイン切れを管理する場所も増えるからです。

ロック1本で13ジョブが1つのChromeを待つ構成と、ログイン先ごとに4つのChromeへ分けた構成の比較
分ける軸はジョブではなくログイン先。直列にするのはレーンの中だけ
レーンポートログインするサイト主なジョブ
sns(SNS運用)9222X / Instagram / TikTok返信・投稿
metrics(計測)9223X analytics / TikTok Studio数値の収集・改善ループ
gen(生成)9224Google(Gemini / Flow)動画・画像の生成
yt(配信)9225YouTube Studio動画のアップロード

鍵もレーンごとに1本にし、同じレーンの中だけ直列、レーンをまたぐジョブは待たない形にしました。SNSレーンは元のプロファイルを引き継いだので、既存のログインはそのまま使えています。4レーンすべてが動き始めたのは2026年8月9日18時12分です。

設計時にはGoogle Search ConsoleとGA4も計測レーンのログイン先に入れていましたが、実際の取得はAPI経由で、ブラウザは要りませんでした。レーンを設計する前に、そのデータがどの経路で取られているかをコードで確かめるべきでした。

結果——取りこぼしは1日2.13回→0.74回、でも9月に3.71回へ戻った

レーン分割は効きました。ただし効いたのは最初の3週間で、分けたレーンの1つが混み直し、9月には分割前より悪くなっています。

待ち切れず諦めた便(1日あたり)
ロック1本(8/2〜8/9)2.13回
4レーン・8月(8/9〜8/31)0.74回
4レーン・9月(9/1〜9/14朝)3.71回
期間鍵の取得待ちの発生諦めた便最長の待ち待った時の中央値
ロック1本(8日)296回39回17回1,080秒270秒
4レーン・8月(23日)929回54回17回1,020秒90秒
4レーン・9月(14日)826回120回52回3,270秒60秒

8月は狙いどおりでした。動画生成が別レーンに移り、それが原因で諦めた便は0回になっています。

9月の悪化は、52回のうち49回が計測レーンに集中していました。そのうち38回は、計測ジョブ1本が鍵を握っている間に起きています。

原因は2つ重なっていました。1つは、その計測ジョブが1回に鍵を握る時間の中央値が、88秒(ロック1本の時期)→938秒(8月)→1,643秒(9月)と伸びていたことです。もう1つは、計測レーンを使うジョブの名前が8月の14種から9月は22種(手動・試験の便を含む)に増え、記事投稿サービスnoteの原稿生成やブログのサムネイル作成が後から同じレーンに入っていたことです。

AIエージェントを並列化する前に確認する5つのこと

並列化の前に決めるべきなのは、エージェントの数ではなく「何を共有しているか」と「混雑を何で測るか」です。ここまでの失敗を、着手前の点検の順に並べ直すと次の5つになります。

並列化の前の点検
  1. 共有している物を書き出す。ブラウザのプロファイル、追記するファイル、「もう実施したか」を判定する材料の3種類を必ず見る
  2. ログインが要る作業は、ログイン先のサイト単位でプロファイルを分ける。ログインが要らなければ使い捨てのプロファイルで足りる
  3. 同じプロファイルの中は鍵で直列にし、持ち主が死んだ鍵を即回収する規則を入れる
  4. 待ち切れず諦めた便は捨てずに、再試行の列に載せる
  5. レーンごとに「諦めた便/日」と「鍵を握る時間の中央値」を週1回見る。どちらかが伸びたら、ジョブを別レーンへ移すかレーンを足す

4番目は実際に踏んでいます。2026年8月10日、1日1回の改善ループが鍵を取れずに飛び、その日の改善がまるごと欠けました。1日1回のジョブは諦めると次の機会が24時間後になるため、翌日から30分ごとの再実行ジョブが拾う形にしています。

5番目は、今回の反省そのものです。9月の混雑は、この記事のためにロックの記録を期間ごとに集計して、はっきり数字で見えました。見る指標を決めていなければ、取りこぼしはエラーにならないまま増えていきます。

同じジョブが2つ重なる「二重起動」とPIDロックの基本はClaude Codeの定期実行の記事に、エラーを出さずに壊れ続ける自動化の見つけ方は無言の失敗の記事にまとめています。

よくある質問

Q. Playwright MCPの `--isolated` を付ければ並列化できますか。

ログインが要らない作業ならできます。プロファイルをメモリに置いてディスクに保存しないため、並列に立ててもぶつかりません。ただしログイン状態も残らないので、SNSや管理画面を操作するジョブでは毎回ログインが必要になり、私たちの用途では使えませんでした。

Q. ログイン済みのプロファイルをコピーして、レーンを増やしてもよいですか。

フォルダを複製すればログインごと増やせます。ただ同じアカウントのセッションが並ぶため、サービス側が不審なログインと見る可能性はゼロではありません。私たちは安全側に倒し、新しいレーンは人が手でログインする手順を用意して作りました。

Q. 何本くらいのジョブから分けるべきですか。

本数より「待ち切れず諦めた便」が出ているかで決めます。私たちはブラウザを使うジョブが13本の時点で、8日間に17回の取りこぼしが出ていました。1回に10分以上ブラウザを握るジョブ(動画生成やアップロード)があれば、それだけを先に分けるのが効果の大きい一手でした。

Q. コードを書くエージェントの並列化にも、同じ考え方は使えますか。

使えます。まず「共有している物」を探す点は同じで、コード編集ではブラウザではなく同じファイル群が共有物になり、ブランチや作業フォルダを分けるのが定石です。私たちが複数のエージェントで開発した体制は6日間でプロダクトを作った記事に書いています。

コメント

コメントは即時公開されます。不適切な内容は予告なく削除します。

#AI開発#AIエージェント#Playwright#自動化