powered by TechFeed
表示モード
Deep Dive

複数のAIエージェントが並列でDBを触っても干渉しない —「Gitのworktree」のデータベース版が解決する状態分離の問題

10月28日、Microsoft Azure Cosmos DB チームが「Agentic CI/CD with Azure Cosmos DB: Isolated Environments for a Swarm of AI Agents」と題した記事を公開した。複数のAIコーディングエージェントが並列で動作する環境において、各タスクに独立したAzure Cosmos DBエミュレータ環境を割り当てるアーキテクチャパターンについて詳しく紹介されている。

10月28日、Microsoft Azure Cosmos DB チームが「Agentic CI/CD with Azure Cosmos DB: Isolated Environments for a Swarm of AI Agents」と題した記事を公開した。複数のAIコーディングエージェントが並列で動作する環境において、各タスクに独立したAzure Cosmos DBエミュレータ環境を割り当てるアーキテクチャパターンについて詳しく紹介されている。


問題の核心:コードは分離できるが、データベースは分離されていない

GitHub CopilotなどのAIコーディングエージェントを複数同時に動かすユースケースが増えている。それぞれのエージェントが別々のブランチ・別々のGit worktreeで作業しているとしても、接続先のデータベースが共有されていれば問題が起きる。

  • エージェントAがフィクスチャをリセットしている最中に、エージェントBが書き込みを検証する
  • あるテストが変更したドキュメントを、別のタスクが変更されていないと仮定してテストする
  • クリーンアップスクリプトがデータベースを削除した時点で、別のテストスイートがまだ動いている

コードは分離されている。状態は分離されていない。

Azure Cosmos DBはRDBMSのようなスキーママイグレーションが不要な分、こうした問題が見えにくくなりがちだ。しかしドキュメントの形状、パーティションキー、インデックスポリシー、テストデータの整合性はアプリケーションが依存する状態であることに変わりはない。


解決策:「Gitのworktree」のデータベース版

Gitのworktreeは、別々のディレクトリで別々のブランチを作業できる仕組みだ。ある作業ディレクトリへの変更が他のディレクトリのファイルを上書きしない。

この記事が提案するのはそのデータベース版だ。各コーディングタスクに対して、次のリソースをセットで割り当てる:

  • 専用のGit worktreeとフィーチャーブランチ
  • 実際のエンドポイントを持つ、所有権のあるエミュレータ環境
  • そのコードリビジョンに対応したバージョン管理済みの合成フィクスチャ
  • 分離されたログとテスト成果物
  • 成功・失敗いずれの場合もクリーンアップパスを持つ、実行時間の上限

「タスクごとに使い捨て可能なデータベース環境を持たせ、マージするのは変更されたコードだけ(テスト用データベースではない)」というのが基本思想だ。

なお、この設計はコピーオンライトスナップショットやプロダクション環境のクローンではない。ポイントはあくまで「各タスクの開始状態を再現可能にし、変更を独立させること」である。各エージェントが接続するのはAzure Cosmos DB Emulatorのコンテナインスタンスであり、プロダクションのCosmos DBアカウントとは完全に切り離されている。


実装:まず2環境で検証する

本記事のサンプルはFleetChargeリポジトリをベースにしている。まず「2つの環境が同一のドキュメントIDを使いながら互いに影響を与えず、クリーンアップもそれぞれ独立して行えるか」を検証する。

2環境は意図的に同じデータを使う設定になっている:

設定 環境A 環境B
データベース fleetchargetest-agent-isolation fleetchargetest-agent-isolation
コンテナ dispatchUpdates dispatchUpdates
ドキュメントID current-dispatch current-dispatch
パーティションキー SEA-01 SEA-01
更新後のpickup bay BAY-7 BAY-8

異なるIDを使えば衝突は偶然避けられるが、あえて同じIDを使うことで「境界の意図」を明確にしている。 この2つの書き込みが分離されるのは、それぞれが別のエミュレータインスタンスに接続しているからだ。エミュレータ内部のポートは8081/8080のままで、Dockerの動的ポート割り当てがホスト側に別々のポートをマッピングする。これにより、ポート競合を気にせず複数のエミュレータコンテナを同一ホスト上で同時に起動できる。

実行は1コマンドで済む:

uv run --locked --extra test python -m scripts.agent_isolation

このコマンドが2つのエミュレータコンテナの起動・待機・フィクスチャ投入・分離チェック・リソース削除まですべてを担う。


検証内容:「成功したリクエスト」でなく「保存された状態」を確認する

両環境はともにシアトルのdispatchスナップショット(BAY-2)からスタートする。環境AはBAY-7、環境BはBAY-8を書き込む。両方の書き込み完了後、それぞれの環境で独立したpoint readを行う:

stored = await container.read_item(
    "current-dispatch",
    partition_key="SEA-01",
)

さらに環境Aを削除した後も環境Bのデータが読み取れること、そして両コンテナが最終的にすべて消えていることをチェックする。検証項目は以下の通り:

チェック 結果
Aが自分のシアトル更新を読み取れる BAY-7 / agent-a-dispatch
Bが自分のシアトル更新を読み取れる BAY-8 / agent-b-dispatch
ポートランドのデータが両環境で変化しない Pass
A削除後もBが読み取れる Pass
クリーンアップ後に両コンテナが存在しない Pass


GitHub ActionsでのCI統合とfail-fast: falseの重要性

ローカルでの分離が確認できたら、次はCIへの組み込みだ。サンプルでは.github/workflows/agentic-ci.ymlにAgent task CIワークフローを追加している。matrixでdispatchとfleet-rulesの2タスクを並列実行し、それぞれが独立したUbuntuランナーとエミュレータを持つ:

strategy:
  fail-fast: false
  matrix:
    task: [dispatch, fleet-rules]

fail-fast: falseにしている点が重要だ。 GitHub Actionsのmatrixはデフォルトでいずれかのジョブが失敗すると残りを即座にキャンセルする(fail-fast: trueが既定値)。この設定をfalseにすることで、一方のタスクが失敗しても、もう一方のタスクは最後まで実行されてその診断情報が保持される。成果物のアップロードもif: always()で行うため、失敗後もログが確認できる。AIエージェントが複数タスクを並列実行する文脈では、あるタスクの失敗が別タスクのデバッグ情報を消してしまうことは特に避けたい。

同じチェックはローカルでも実行できる:

uv run --locked --extra test python -m scripts.agent_ci --task dispatch
uv run --locked --extra test python -m scripts.agent_ci --task fleet-rules

このワークフローは既存のCosmos emulator CIワークフローを置き換えるのではなく、並存させる設計になっている。個別タスクの狭いチェックはフィードバックであり、統合されたアプリケーション全体の検証をスキップする理由にはならない。


設計上の注意点

記事が強調するのは、SDKクライアントには必ず自分が所有する環境の設定を明示的に渡すことだ。共有の.envファイルを書き換えたり、プロセスグローバルな環境変数を変更したりする実装は、このパターンをコーディングエージェントに拡張する際に問題を引き起こす。各子プロセスには固有の設定を渡す必要がある。

Azure Cosmos DB Emulatorの詳細な設定オプションや起動パラメータについては公式ドキュメントも参照されたい。

詳細はAgentic CI/CD with Azure Cosmos DB: Isolated Environments for a Swarm of AI Agentsを参照していただきたい。