はじめに
メグリのプロダクトは、社内で開発している共通基盤(以下、コア)を、案件ごとにフォークしたテナントリポジトリで利用する構成になっています。Androidの場合、コアはAARとして配布していますが、テナント側ではAARだけではカバーできない範囲もあり、コアのソース相当を取り込んでカスタマイズしているレイヤーがあります。そのため、コアが新しいバージョンをリリースするたびに、各テナントリポジトリへ差分を反映する必要があります。社内ではこの運用を「コアアップデート」と呼んでいて、Androidだけで100超のテナントリポジトリが対象になります。
この運用は当初、エンジニアが手作業で進めていましたが、テナント数の増加とともに属人化や工数の見通しづらさが目立つようになりました。本記事では、その手作業の状態から、patch運用、GitHub Actions連携、AIエージェントの活用までを段階的に進めた経緯と、AI導入後に観測できた効果を紹介します。
なお、以前公開したHub型AI Reviewerの記事はPRレビューの自動化の話でしたが、本記事はその後段にあたる「コアの変更をテナントへ取り込む」運用の話となります。
コアとテナントの二層構造
メグリのプロダクトは、コアとテナントの二層構造になっています。
- コア: 全テナント共通の基盤コード。AndroidではAARとしてバイナリ配布
- テナント: 案件別のフォークソース。100超のリポジトリがそれぞれ独自カスタマイズを持つ
Androidでは、全テナント共通の基盤コードをAARとして配布しています。AAR配布は共通機能の更新を各テナントへ届けやすい反面、バイナリとして取り込むため、テナント側で案件ごとの都合に合わせて部分的に書き換えることはできません。
そのため、AARとして配布している共通基盤とは別に、テナントごとの差し替えや拡張を前提としたソース取り込み層があり、そこに対してコア側の変更を反映しています。コアが新バージョンを出すたびに、各テナントは独自実装を維持しながら、このソース取り込み層へ新しいコアの差分を取り込む必要があります。これが、社内で「コアアップデート」と呼んでいる運用です。
このような上流に取り込まれない独自改修を下流で長期保守する運用は、AOSPに対するOEMカスタムや、OSSのライブラリを自社用にフォークして独自拡張しつつ、本家のバージョンアップのたびに手元の変更と競合しないようマージし続けることと同じような取り組みになると思います。
ただし、これらが「1フォーク×大規模差分×多人数」で運用されているのに対し、メグリのケースは「100超フォーク×比較的小さい差分×数名」という構造になっています。スケールが逆方向に大きく、これが後述のAI活用を必要とした背景になっています。
弊社のような多数のフォークを数名で見るとなると、人が個別の改修内容を覚えておくのは現実的ではなく、改修の知識をコードや運用記録の側に残しておく必要が出てきます。本記事の仕組みの主眼も、この知識を機械側に蓄積しつつ、回せる範囲を広げることを目指すことにあります。
手作業時代の課題
最初期は、コア側で行われた変更をエンジニアが一つずつ確認し、テナント側に手作業で同じ変更を加える運用をしていました。テナントの数が少ないうちはエンジニアの作業負担はあるものの、そこまで大きな問題にはなっていませんでした。しかし、テナントが増えてくると以下のような課題が出てきました。
- 担当者の経験に依存し、属人化する
- 作業記録が残りにくく、後から振り返れない
- 工数が読みにくく、計画が立てにくい(更新するコアバージョンが大きく乖離している場合は特に)
これらを定量的に把握するため、コンフリクトが発生したPRを過去にさかのぼって分析しました。対象は約50リポジトリの200件で、検出されたコンフリクトを分類したところ、おおむね7割が「行ずれによってpatchが当たらなくなった失敗」、残り3割が「真のコンフリクト」という結果になりました。
つまり、7割は本質的な競合ではなく、コード追加で他の行番号がずれてpatchが当たらなくなる類のものでした。この調査結果が、後の「機械的に解消できる余地がある」というアイデアにつながります。
仕組み化の段階的な進化
手作業から仕組み化までを段階的に進めてきたので、フェーズごとに整理します。
フェーズ1: 手動反映
エンジニアがコア側の変更を読み、テナント側に手で適用していました。属人化と記録不足が課題でした。
フェーズ2: patch導入
コア側の差分をgit format-patch相当の形で取り出し、git applyでテナント側に当てるようにしました。具体的には、コアの2つのバージョン間の差分をpatchファイルとして書き出し、各テナントリポジトリで適用する流れです。テナント側に独自のカスタマイズがあると、patch対象の行がずれていたり、同じ箇所に別の改変が入っていたりして、.rejが多く発生しました。手作業時代と比べてpatchが「当たる範囲」は機械的に処理できるようになりましたが、当たらなかった部分の解消は結局のところ人手に戻ってしまうことが多く、属人化の解消には至りませんでした。
フェーズ3: 実行スクリプト整備
patchの適用手順をスクリプト化しました。ここで、スクリプト自身を更新する経路がないという鶏卵問題が発生しました。スクリプトを各リポジトリに配ったあとに、そのスクリプトを更新したいとき、結局すべてのリポジトリへ手で配り直すことになります。現実解として、最初の暫定版だけ手で全リポジトリへ配り、それ以降はスクリプト自身が次のバージョンを取り込めるよう自己更新経路を組み込みました。
運用していくうちに、もう一つ別の問題も見えてきました。Androidの開発メンバーはMac, Windows, Linuxとそれぞれ異なる環境で作業していて、同じスクリプトを動かしても、シェルコマンドの実装系列(MacのBSD系とLinuxのGNU系)の差や、そもそもWindowsでは前提となるコマンド構成が異なることから、オプションの解釈や出力に細かな差が出て、結果が一致しないケースがありました。スクリプト側を頑張って書き分けるよりも、実行環境そのものを揃えてしまったほうが筋が良いという判断になり、これが次のフェーズでGitHub Actionsに寄せていく動機になりました。
フェーズ4: GitHub Actions連携
スクリプトの実行環境をGitHub Actionsに統一し、合わせてスクリプト実行からPR作成までを自動化しました。実行環境が揃ったことで、前フェーズで起きていた「人によって結果が違う」問題が解消され、再現性のあるログを残せるようになりました。
もう一つの利点として、並列実行性が大きく上がりました。ローカルで作業していた時期は、PCのリソースの都合もあり、実質的に1リポジトリずつ順番に進める形になっていました。実行をクラウド側に寄せたことで、複数リポジトリのコアアップデートを並列で走らせられるようになり、担当人数が同じでも、1人あたりが回せるテナント数が増えました。
このとき、patchが当たらなかった.rejファイルをそのままPRにコミットして残すようにしました。.rejはgit apply --rejectの出力で、適用に失敗したhunkだけが切り出されたものです。これを通常のソースファイルと同様にPRにコミットしておくと、レビュー時に「どのファイルのどこで、何のpatchが当たらなかったか」をコードと並べて見られるようになります。各テナントごとに残ったログをまとめて眺めると、テナント間で共通して失敗するパターンや、特定のテナントだけで起きる固有のコンフリクトが切り分けられて、後続のフェーズ5の分析にもつながっていきました。
フェーズ5: 分析と予防設計
蓄積された.rejをリポジトリ横断で集計したところ、改変が集中する特定ファイルや、繰り返し発生するパターンが見えてきました。例えば、テナントごとにブランディング設定や初期画面のレイアウト周りに改変が集中しがちで、コア側で同じファイルに手を入れるとどのテナントでも.rejになりやすい、という傾向が見えてきました。
これを元に、コア側で改変集中ファイルは構造を分離して、テナント側の差分が当たりにくいように設計し直したり、テナント側で改変が想定される領域はlinterで「この箇所を触る場合は別ファイルに切り出す」というガイドラインを敷いたりしました。繰り返し発生する種類のコンフリクトについては、patch適用の自動化コマンド側で専用処理を入れて、.rejそのものが発生しないようにしました。.rejを残す運用が、次の打ち手の根拠として効いてきている形です。
フェーズ6: AI活用
ここまでで自動化と予防は進みましたが、それでも残る.rejは人手で解消していました。残るコンフリクトの内訳を見ると、その多くは前述の「行ずれ起因」だったため、ここをAIに任せられないか検討して導入しました。詳細は次の章で説明します。
AI活用の具体
AIエージェントとしてDevin(リモートAPI型のコーディングエージェント)を採用して、GitHub Actionsと組み合わせて以下のループを組みました。
設計上、いくつか割り切ったポイントがあります。
ループ回数を最大2回までに制限しているのは、無制限ループは時間とコストを消費する割に成功率が伸びないためです。3回目以降は「AIでは解けない種類のコンフリクト」と判断して人間に渡したほうが結果的に早い、という割り切りです。
知識ベースは3層に分けて持たせています。仕様レイヤー(各機能の仕様ドキュメント)、パターンレイヤー(過去の共通解消パターン)、個別解消例(.rejと修正例のペア)の3層で、タスクの種類に応じて参照する層を切り替えるようにしました。
例えば、.rejの内容が「ある機能の初期化処理に対するhunkが当たらなかった」というケースであれば、まず仕様レイヤーから該当機能のドキュメントを読ませて挙動の前提を揃え、続いてパターンレイヤーから類似の解消パターンを渡し、最後に個別解消例として過去の似た.rejと修正例のペアを参照させる、というように層を組み合わせます。一方で、行ずれだけが原因の単純なケースであれば、個別解消例だけで足りることもあります。3層をいつも全部使うわけではなく、ケースに応じて必要な層を選ぶ運用にしています。
ローカル支援型ではなくリモートAPI型を採用したのは、100リポジトリの日次更新を回す必要があり、並列実行性とコスト管理を優先したかったためです。ローカル支援型だと「人がPCを開いている時間」が事実上の上限になります。
Human in the Loop
完全自動化は目指さず、人間の判断を3か所に残しています。
- AIが解けなかった
.rejの解消。構造的な差分や複数ファイルにまたがる変更は、現状ではAIが苦手な領域です。 - PRの最終レビュー。AIが提示する修正は、もっともらしいが間違っていることがあるため、最終的なレビューは人間が担当しています。よくあるのは、コア側で関数のシグネチャが変わったときに、AIが古いシグネチャに合わせて辻褄を取った修正を出してきて、ビルドは通るものの意図とずれている、というケースです。
- マージ責任。文脈が一致しない箇所の最終修正と、マージするかどうかの判断は、責任の所在を明確化するために人間に残しています。
AIから人間に引き渡すかどうかの判断基準は、運用上は以下のいずれかに該当した時点を目安にしています。
- CIが2回連続で通らなかった(前述のループ上限に達した)
-
.rejが複数ファイルにまたがっていて、構造的な変更が含まれていそうだとAIから返ってきた - 知識ベースに該当する仕様やパターンがなく、AIが「不確実」「情報不足」と返してきた
こうした場合、AIに無理を続けさせるよりも、人間がドメイン知識を踏まえて判断したほうが結果的に早いと割り切っています。
総じて、速度は自動化で稼ぎ、品質と責任は人間が担保する、という分け方になっています。
効果の測定
AI導入の効果は、作業時間の短縮だけで語ると見落としが出てしまいそうだったので、もう少し広めにPRメタデータを観測しました。
対象は約90リポジトリ、324件のmerged PRです。AIによる.rej解消が支配的になった月を境界として、AI導入前とAI導入後に分けて比較しました。
単純作業の置き換え
.rejが発生したPRに対して、誰が解消したか(人間か、AIか)を集計しました。
| 期間 | 手動解消 | AI一次解消 | AI一次比率 |
|---|---|---|---|
| AI導入前 | 71 | 1 | 1% |
| AI導入後 | 3 | 77 | 96% |
99%を人間が読み解いていた状態から、96%についてはAIが一次解消を担当する状態に変わりました。
スループットの変化
月あたりのmerged PR数は10.5から17.1へ増えました(+63%)。担当人数は以前と変わらず、増員なしで処理量が1.6倍になった計算です。コンフリクト解消のボトルネックがAI側に移り、人間がレビューと最終判断に集中できるようになったことが寄与していると見ています。
人間側の作業内容も変わっていて、以前はpatchが当たらないテナントを1リポジトリずつ開いて手で解消していたのが、現在はAIが提案した修正をPR上でレビューする形が主体になっています。1 PRあたりの作業時間そのものより、複数PRを並行してレビューできる体勢になったことが、月間スループットの増加に効いていると感じています。
行動の変化
これは導入前に予測していなかった効果でしたが、PRのバージョン番号差分を解析した結果、以下のような変化が出ていました。
| 指標 | AI導入前 | AI導入後 | 変化 |
|---|---|---|---|
| 1 PRで上げたminorバージョン数の平均(同じmajor内) | 1.38 | 2.41 | +75% |
| minorを3つ以上まとめて上げたPR数 | 18 | 57 | +217% |
| minorは据え置きでpatchだけ上げたPR数 | 38 | 22 | -42% |
AI導入前は1 minorずつ慎重に上げるPRが多かったのが、AI導入後は2〜3 minor一気に上げるPRが普通になっていました。これは「AIが代わりに作業してくれる」効果というより、「人間の意思決定の幅が広がった」効果に近いと考えています。コンフリクトを警戒して保守的に振る舞っていたのが、AIが安全網として機能することで躊躇しなくなった、という解釈です。
実務的にも、以前は「1 minorずつ慎重に進めるのが安全」とされていた判断が、「まとめて上げてもAIが.rejを解消してくれる」という前提に変わったことで、より大胆な選択を取れるようになったと感じています。
現場の体感と限界
PRのメタデータ(作成日時、ラベル、コミット、変更ファイル、バージョン番号差分など)は誰が記入したかに依らずGitHub側に残っているので、後から振り返っても集計の前提が動かないという利点があります。一方で、PRメタデータからは「実際に何分かかったか」のような体感は読み取れないので、観測できる範囲には限りがあります。
客観指標を主軸にしつつ、定性的な裏付けとして、実際に対応しているエンジニアにもヒアリングしました。本人の体感としては、AI導入前後で作業量が1/10から1/100程度まで減ったとのことでした。いまの作業はAIが提示してきた修正内容を確認するのが中心で、以前のような煩雑なpatch解消作業はほぼ発生していない、というコメントでした。客観指標(月間スループット+63%、AI解消比率96%)と方向は一致しており、現場の体感としても同じ方向の変化として見えています。
また、この集計期間に主力担当者の世代交代もありましたが、それでも月間スループットは+63%を維持できています。以前は新しい担当者がコアアップデート運用に習熟するまで相応の時間がかかっていましたが、運用知識が.rejの履歴や知識ベースとして仕組みの中に残るようになったことで、運用上の体感としても立ち上がりまでの期間が短くなっています。
限界として、AIが解いた96%の中身の正しさはPRレビューとCIで確認していることと、AI導入前にメジャー版移行(3.x→4.x)が含まれるため、変化の100%をAI効果と断定はできないことを補足しておきます。
まとめ
メグリのコアアップデート運用を、手作業からpatch運用、GitHub Actions連携、AIエージェント活用まで段階的に仕組み化してきた経緯と、AI導入後の効果を紹介しました。
本取り組みはAIで完全自動化した話というよりは、人間が見るべき箇所に作業を寄せた話、と整理できそうです。コンフリクト解消のような機械的に解ける作業はAIに任せ、文脈判断とマージ責任は人間に残す。この分担が、属人作業を仕組み化するうえで効いてきたポイントだったと感じています。