본문으로 건너뛰기

© 2026 Molayo

Qiita헤드라인2026. 05. 27. 22:25

100개가 넘는 포크(Fork) Android 프로젝트에서 공통 기반 업데이트 반영을 AI로 자동화하기

요약

100개가 넘는 Android 테넌트 리포지토리에 코어 업데이트를 반영하는 수작업 프로세스를 AI 에이전트를 활용해 자동화한 사례를 소개합니다. 개인의 역량에 의존하던 기존 방식에서 벗어나 GitHub Actions와 AI를 연동하여 운영 효율을 높이는 과정을 다룹니다.

핵심 포인트

  • 100개 이상의 포크 프로젝트 관리 시 발생하는 운영 부담 해결
  • 수작업 기반의 코어 업데이트를 AI 에이전트로 자동화
  • GitHub Actions 연동을 통한 단계적 자동화 프로세스 구축
  • 개인 역량 의존(Siloization) 문제 해결 및 운영 지식의 기계화

메구리(Meguri)의 프로덕트는 사내에서 개발하고 있는 공통 기반(이하, 코어(Core))을 프로젝트별로 포크(Fork)한 테넌트 리포지토리(Tenant Repository)에서 이용하는 구성으로 되어 있습니다. Android의 경우, 코어는 AAR로서 배포하고 있지만, 테넌트 측에서는 AAR만으로는 커버할 수 없는 범위도 있어 코어의 소스에 상당하는 부분을 가져와 커스터마이징(Customizing)하고 있는 레이어가 있습니다. 그 때문에 코어가 새로운 버전을 릴리스할 때마다 각 테넌트 리포지토리로 차분(Diff)을 반영해야 합니다. 사내에서는 이 운용을 「코어 업데이트(Core Update)」라고 부르고 있으며, Android만으로도 100개가 넘는 테넌트 리포지토리가 대상이 됩니다.

이 운용은 당초 엔지니어가 수작업으로 진행해 왔으나, 테넌트 수가 증가함에 따라 개인의 역량에 의존하는 현상(属人化, Siloization)과 공수(Man-hour) 예측의 어려움이 눈에 띄게 나타나기 시작했습니다. 본 기사에서는 그 수작업 상태에서 패치(Patch) 운용, GitHub Actions 연동, AI 에이전트(AI Agent) 활용까지 단계적으로 진행한 경위와 AI 도입 후 관측할 수 있었던 효과를 소개합니다.

참고로, 이전에 공개한 Hub형 AI Reviewer 기사는 PR(Pull Request) 리뷰 자동화에 관한 이야기였으나, 본 기사는 그 다음 단계인 「코어의 변경 사항을 테넌트로 가져오는」 운용에 관한 이야기입니다.

메구리의 프로덕트는 코어와 테넌트의 2층 구조로 되어 있습니다.

코어(Core): 모든 테넌트 공통의 기반 코드. Android에서는 AAR로서 바이너리(Binary) 배포
테넌트(Tenant): 프로젝트별 포크 소스. 100개가 넘는 리포지토리가 각각 독자적인 커스터마이징을 보유

Android에서는 모든 테넌트 공통의 기반 코드를 AAR로서 배포하고 있습니다. AAR 배포는 공통 기능의 업데이트를 각 테넌트에 전달하기 쉽다는 장점이 있는 반면, 바이너리로 가져오기 때문에 테넌트 측에서 프로젝트별 사정에 맞춰 부분적으로 다시 쓸 수는 없습니다.

그렇기 때문에 AAR로서 배포하고 있는 공통 기반과는 별개로, 테넌트별 교체나 확장을 전제로 한 소스 가져오기 층이 있으며, 그곳에 코어 측의 변경 사항을 반영하고 있습니다. 코어가 새 버전을 낼 때마다 각 테넌트는 독자적인 구현을 유지하면서 이 소스 가져오기 층에 새로운 코어의 차분을 가져와야 합니다. 이것이 사내에서 「코어 업데이트」라고 부르는 운용입니다.

이러한 상류(Upstream)에 반영되지 않는 독자적인 개수를 하류(Downstream)에서 장기 보수하는 운용은, AOSP에 대한 OEM 커스텀이나, OSS 라이브러리를 자사용으로 포크하여 독자적으로 확장하면서 본래 버전의 업데이트 때마다 수동의 변경 사항과 충돌하지 않도록 계속해서 머지(Merge)하는 작업과 유사한 노력이라고 생각됩니다.

다만, 이것들이 「1개 포크 × 대규모 차분 × 다수 인원」으로 운용되는 것과 달리, 메구리의 케이스는 「100개 이상의 포크 × 비교적 작은 차분 × 소수 인원」이라는 구조를 가지고 있습니다. 스케일이 반대 방향으로 크게 다르며, 이것이 후술할 AI 활용을 필요로 하게 된 배경이 되었습니다.

저희 회사처럼 다수의 포크를 소수의 인원이 관리하게 되면, 사람이 개별적인 개수 내용을 모두 기억하는 것은 현실적이지 않으며, 개수 지식을 코드나 운용 기록 측에 남겨둘 필요가 생깁니다. 본 기사 메커니즘의 주안점도 이 지식을 기계 측에 축적하면서 운영할 수 있는 범위를 넓히는 데 있습니다.

최초기에는 코어 측에서 이루어진 변경을 엔지니어가 하나씩 확인하고 테넌트 측에 수작업으로 동일한 변경을 가하는 운용을 했습니다. 테넌트 수가 적을 때는 엔지니어의 작업 부담은 있지만 그렇게 큰 문제는 아니었습니다. 하지만 테넌트가 늘어나면서 다음과 같은 과제가 나타났습니다.

  • 담당자의 경험에 의존하여 개인화(属人化)됨
  • 작업 기록이 남기 어려워 나중에 되돌아볼 수 없음
  • 공수를 예측하기 어렵고 계획을 세우기 힘듦 (업데이트할 코어 버전이 크게 괴리되어 있는 경우 특히)

이를 정량적으로 파악하기 위해 컨플릭트(Conflict)가 발생한 PR을 과거로 거슬러 올라가 분석했습니다. 대상은 약 50개 리포지토리의 200건으로, 검출된 컨플릭트를 분류한 결과 대략 7할이 「행 밀림으로 인해 패치가 적용되지 않은 실패」였으며, 나머지 3할이 「진정한 컨플릭트」라는 결과가 나왔습니다.

즉, 7할은 본질적인 충돌이 아니라 코드 추가로 인해 다른 행 번호가 밀리면서 패치가 적용되지 않게 되는 종류의 것이었습니다. 이 조사 결과가 이후의 「기계적으로 해소할 여지가 있다」는 아이디어로 이어집니다.

수작업에서 메커니즘화까지 단계적으로 진행해 왔으므로 페이즈(Phase)별로 정리합니다.

**Phase 1: 수작업 (Manual)
**엔지니어가 코어 측의 변경을 읽고 테넌트 측에 수동으로 적용했습니다. 개인화와 기록 부족이 과제였습니다.

**Phase 2: 패치 운용 (Patch)
코어 측의 차분을 git format-patch에 상당하는 형태로 추출하여, git apply...

테넌트(Tenant) 측에 적용하도록 했습니다. 구체적으로는 코어(Core)의 두 버전 간 차분을 패치(patch) 파일로 내보내고, 각 테넌트 리포지토리(Repository)에서 적용하는 흐름입니다. 테넌트 측에 독자적인 커스텀(Customization)이 있으면, 패치 대상 행이 어긋나 있거나 동일한 위치에 다른 수정 사항이 들어가 있어 .rej 파일이 많이 발생했습니다.

수작업 시절과 비교하면 패치가 "적용되는 범위"는 기계적으로 처리할 수 있게 되었지만, 적용되지 않은 부분을 해결하는 것은 결국 다시 인력의 몫으로 돌아오는 경우가 많아, 숙련자 의존성(Bus factor) 해소에는 이르지 못했습니다.

패치 적용 절차를 스크립트(Script)로 만들었습니다. 여기서 스크립트 자체를 업데이트할 경로가 없다는 "닭과 달걀" 문제가 발생했습니다. 스크립트를 각 리포지토리에 배포한 뒤, 그 스크립트를 업데이트하고 싶을 때는 결국 모든 리포지토리에 일일이 다시 배포해야 합니다. 현실적인 해결책으로서, 최초의 잠정 버전만 수동으로 모든 리포지토리에 배포하고, 그 이후에는 스크립트 스스로가 다음 버전을 받아들일 수 있도록 자기 업데이트(Self-update) 경로를 구축했습니다.

운영을 해나가면서 또 다른 문제가 보이기 시작했습니다. Android 개발 멤버들은 Mac, Windows, Linux 등 각기 다른 환경에서 작업하고 있었는데, 동일한 스크립트를 실행하더라도 셸 커맨드(Shell command)의 구현 계열(Mac의 BSD 계열과 Linux의 GNU 계열) 차이나, 애초에 Windows에서는 전제되는 커맨드 구성이 다르다는 점 때문에 옵션 해석이나 출력에서 미세한 차이가 발생하여 결과가 일치하지 않는 케이스가 있었습니다. 스크립트 측을 열심히 나누어 작성하기보다는 실행 환경 자체를 통일하는 것이 더 타당하다는 판단을 내렸고, 이것이 다음 단계에서 GitHub Actions로 옮겨가게 된 동기가 되었습니다.

스크립트 실행 환경을 GitHub Actions로 통일하고, 이와 함께 스크립트 실행부터 PR(Pull Request) 생성까지 자동화했습니다. 실행 환경이 통일됨으로써 이전 단계에서 발생했던 "사람마다 결과가 다른" 문제가 해결되었고, 재현 가능한 로그를 남길 수 있게 되었습니다.

또 다른 장점으로는 병렬 실행성(Parallelism)이 크게 향상되었습니다. 로컬에서 작업하던 시기에는 PC 리소스 문제로 인해 실질적으로 리포지토리를 하나씩 순차적으로 진행하는 형태였습니다. 실행을 클라우드 측으로 옮김으로써 여러 리포지토리의 코어 업데이트를 병렬로 실행할 수 있게 되었고, 담당 인원이 동일하더라도 1인당 처리할 수 있는 테넌트 수가 늘어났습니다.

이때, 패치가 적용되지 않은 .rej 파일을 그대로 PR에 커밋하여 남기도록 했습니다. .rejgit apply --reject의 출력 결과로, 적용에 실패한 험크(Hunk)만 따로 추출된 것입니다. 이를 일반 소스 파일과 마찬가지로 PR에 커밋해 두면, 리뷰 시에 "어느 파일의 어느 부분에서 어떤 패치가 적용되지 않았는지"를 코드와 나란히 볼 수 있습니다. 각 테넌트별로 남은 로그를 모아서 살펴보면, 테넌트 간에 공통적으로 실패하는 패턴이나 특정 테넌트에서만 발생하는 고유한 컨플릭트(Conflict)를 구분할 수 있게 되어, 후속 단계인 단계 5의 분석으로 이어졌습니다.

축적된 .rej를 리포지토리 전반에 걸쳐 집계한 결과, 수정이 집중되는 특정 파일이나 반복적으로 발생하는 패턴이 보였습니다. 예를 들어, 테넌트마다 브랜딩(Branding) 설정이나 초기 화면 레이아웃 주변에 수정이 집중되는 경향이 있어, 코어 측에서 동일한 파일을 수정하면 어떤 테넌트에서도 .rej가 발생하기 쉽다는 흐름을 파악할 수 있었습니다.

이를 바탕으로, 코어 측에서 수정이 집중되는 파일은 구조를 분리하여 테넌트 측의 차분이 잘 적용되도록 재설계하거나, 테넌트 측에서 수정이 예상되는 영역은 린터(Linter)를 통해 "이 부분을 수정할 경우 별도 파일로 분리할 것"이라는 가이드라인을 세웠습니다. 반복적으로 발생하는 종류의 컨플릭트에 대해서는 패치 적용 자동화 커맨드 측에 전용 처리를 넣어 .rej 자체가 발생하지 않도록 했습니다. .rej를 남기는 운영 방식이 다음 조치를 위한 근거로서 효과를 발휘하고 있는 형태입니다.

여기까지 자동화와 예방은 진행되었지만, 그럼에도 남는 .rej는 수작업으로 해결하고 있었습니다. 남은 컨플릭트의 내역을 살펴보면 그 대부분이 앞서 언급한 "행 어긋남(Line shift)에 기인한 것"이었기에, 이 부분을 AI에게 맡길 수 없을지 검토하여 도입했습니다. 자세한 내용은 다음 장에서 설명하겠습니다.

AI 에이전트(Agent)로서 Devin(원격 API형 코딩 에이전트)을 채택하여, GitHub Actions와 조합해 다음과 같은 루프(Loop)를 구축했습니다.

설계상 몇 가지 타협한 포인트가 있습니다.

루프(Loop) 횟수를 최대 2회로 제한하고 있는 이유는, 무제한 루프는 시간과 비용을 소비하는 것에 비해 성공률이 크게 늘지 않기 때문입니다. 3회차 이후부터는 "AI로는 해결할 수 없는 종류의 충돌(Conflict)"이라고 판단하여 인간에게 넘기는 것이 결과적으로 더 빠르다는 결단입니다.

지식 베이스(Knowledge Base)는 3개의 계층으로 나누어 구성했습니다. 사양 레이어(Specification Layer, 각 기능의 사양 문서), 패턴 레이어(Pattern Layer, 과거의 공통 해결 패턴), 개별 해결 사례(.rej 파일과 수정 예시의 쌍)의 3개 계층으로 나누어, 태스크의 종류에 따라 참조하는 계층을 전환하도록 했습니다.

예를 들어, .rej 파일의 내용이 "어떤 기능의 초기화 처리에 대한 험크(Hunk)가 적용되지 않았다"는 케이스라면, 먼저 사양 레이어에서 해당 기능의 문서를 읽게 하여 동작의 전제를 맞추고, 이어서 패턴 레이어에서 유사한 해결 패턴을 전달하며, 마지막으로 개별 해결 사례로서 과거의 유사한 .rej 파일과 수정 예시의 쌍을 참조하게 하는 방식으로 계층을 조합합니다. 반면, 행(Line) 어긋남만이 원인인 단순한 케이스라면 개별 해결 사례만으로도 충분할 수 있습니다. 3개 계층을 항상 전부 사용하는 것이 아니라, 케이스에 따라 필요한 계층을 선택하는 방식으로 운영하고 있습니다.

로컬 지원형이 아닌 리모트 API(Remote API) 형을 채택한 이유는, 100개 리포지토리의 일일 업데이트를 돌려야 했기에 병렬 실행성과 비용 관리를 우선시하고 싶었기 때문입니다. 로컬 지원형이라면 "사람이 PC를 켜놓은 시간"이 사실상의 상한선이 됩니다.

완전 자동화를 목표로 하지 않고, 인간의 판단을 3곳에 남겨두었습니다.

  • AI가 해결하지 못한 .rej 파일의 해결. 구조적인 차분(Diff)이나 여러 파일에 걸친 변경은 현재로서는 AI가 어려워하는 영역입니다.
  • PR의 최종 리뷰. AI가 제시하는 수정은 그럴듯해 보이지만 틀릴 때가 있기 때문에, 최종 리뷰는 인간이 담당합니다. 자주 발생하는 사례는 코어(Core) 측에서 함수의 시그니처(Signature)가 변경되었을 때, AI가 오래된 시그니처에 맞춰 앞뒤를 맞춘 수정을 내놓아 빌드는 통과하지만 의도와 어긋나는 경우입니다.
  • 머지(Merge) 책임. 문맥이 일치하지 않는 부분의 최종 수정과 머지 여부에 대한 판단은 책임 소재를 명확히 하기 위해 인간에게 남겨두었습니다.

AI에서 인간으로 넘길지 여부에 대한 판단 기준은, 운영상 다음과 같은 어느 하나에 해당하는 시점을 기준으로 삼고 있습니다.

  • CI가 2회 연속으로 통과하지 못했을 때 (앞서 언급한 루프 상한에 도달했을 때)
  • .rej 파일이 여러 파일에 걸쳐 있으며, 구조적인 변경이 포함되어 있을 것 같다고 AI가 반환했을 때
  • 지식 베이스에 해당하는 사양이나 패턴이 없어, AI가 "불확실" 또는 "정보 부족"이라고 반환했을 때

이런 경우, AI에게 무리하게 계속 시키는 것보다 인간이 도메인 지식(Domain Knowledge)을 바탕으로 판단하는 것이 결과적으로 더 빠르다고 판단하고 있습니다.

종합하면, 속도는 자동화로 확보하고 품질과 책임은 인간이 담보하는 방식으로 나누어져 있습니다.

AI 도입 효과를 작업 시간 단축만으로 이야기하면 놓치는 부분이 있을 것 같아, 조금 더 넓은 범위에서 PR 메타데이터를 관측했습니다.

대상은 약 90개 리포지토리, 324건의 머지된(Merged) PR입니다. AI에 의한 .rej 파일 해결이 지배적이 된 달을 경계로, AI 도입 전과 도입 후로 나누어 비교했습니다.

.rej 파일이 발생한 PR에 대해 누가 해결했는지(인간인지, AI인지)를 집계했습니다.

기간수동 해결AI 1차 해결AI 1차 비중
AI 도입 전7111%
AI 도입 후37796%

99%를 인간이 읽고 해석하던 상태에서, 96%는 AI가 1차 해결을 담당하는 상태로 바뀌었습니다.

월간 머지된 PR 수는 10.5개에서 17.1개로 증가했습니다(+63%). 담당 인원은 이전과 동일하며, 증원 없이 처리량이 1.6배가 된 계산입니다. 충돌 해결의 병목(Bottleneck)이 AI 측으로 옮겨가면서, 인간이 리뷰와 최종 판단에 집중할 수 있게 된 것이 기여했다고 보고 있습니다.

인간 측의 작업 내용도 변하여, 이전에는 패치(Patch)가 적용되지 않는 테넌트(Tenant)를 리포지토리별로 하나씩 열어 수동으로 해결하던 것이, 현재는 AI가 제안한 수정을 PR 상에서 리뷰하는 형태가 주를 이루고 있습니다. 1 PR당 작업 시간 그 자체보다, 여러 PR을 병렬로 리뷰할 수 있는 체제가 된 것이 월간 처리량(Throughput) 증가에 효과가 있다고 느끼고 있습니다.

이는 도입 전에 예측하지 못했던 효과였으나, PR의 버전 번호 차분을 분석한 결과 다음과 같은 변화가 나타났습니다.

지표AI 도입 전AI 도입 후변화
1 PR에서 올린 minor 버전 수의 평균 (동일 major 내)1.382.41+75%
...

AI 도입 전에는 1 minor씩 신중하게 올리는 PR이 많았으나, AI 도입 후에는 2~3 minor를 한꺼번에 올리는 PR이 일반적이 되었습니다. 이는 'AI가 대신 작업해 준다'는 효과라기보다, '인간의 의사결정 범위가 넓어졌다'는 효과에 가깝다고 생각합니다. 충돌(Conflict)을 경계하여 보수적으로 행동하던 것이, AI가 안전망으로서 기능함에 따라 주저하지 않게 되었다는 해석입니다.

실무적으로도 이전에는 '1 minor씩 신중하게 진행하는 것이 안전하다'고 여겨졌던 판단이, '한꺼번에 올려도 AI가 .rej 파일을 해결해 준다'는 전제로 바뀌면서 더욱 대담한 선택을 할 수 있게 되었다고 느낍니다.

PR의 메타데이터(작성 일시, 라벨, 커밋, 변경 파일, 버전 번호 차분 등)는 누가 작성했는지와 관계없이 GitHub 측에 남기 때문에, 나중에 되돌아보더라도 집계의 전제가 흔들리지 않는다는 장점이 있습니다. 반면, PR 메타데이터만으로는 '실제로 몇 분이 걸렸는가'와 같은 체감 수치는 읽어낼 수 없으므로 관측 가능한 범위에는 한계가 있습니다.

객관적 지표를 주축으로 하되, 정성적인 뒷받침으로서 실제 대응하고 있는 엔지니어에게도 인터뷰를 진행했습니다. 본인의 체감으로는 AI 도입 전후로 작업량이 1/10에서 1/100 정도까지 줄었다고 합니다. 현재의 작업은 AI가 제시한 수정 내용을 확인하는 것이 중심이며, 이전과 같은 번거로운 패치(Patch) 해결 작업은 거의 발생하지 않고 있다는 코멘트였습니다. 이는 객관적 지표(월간 처리량(Throughput) +63%, AI 해결 비율 96%)와 방향이 일치하며, 현장의 체감으로서도 동일한 방향의 변화로 나타나고 있습니다.

또한, 이 집계 기간에 주력 담당자의 세대교체도 있었으나, 그럼에도 월간 처리량은 +63%를 유지할 수 있었습니다. 이전에는 새로운 담당자가 코어 업데이트(Core Update) 운영에 숙련되기까지 상당한 시간이 걸렸으나, 운영 지식이 .rej 파일의 이력이나 지식 베이스(Knowledge Base)로서 시스템 내에 남게 됨에 따라, 운영상의 체감으로도 업무 적응 기간이 짧아지고 있습니다.

한계점으로서, AI가 해결한 96%의 내용이 올바른지는 PR 리뷰와 CI를 통해 확인하고 있다는 점과, AI 도입 전에 메이저 버전 전환(3.x→4.x)이 포함되어 있어 변화의 100%를 AI 효과라고 단정할 수는 없음을 보충해 둡니다.

Meguri의 코어 업데이트 운영을 수동 작업에서 패치(Patch) 운영, GitHub Actions 연동, AI 에이전트 활용까지 단계적으로 시스템화해 온 경위와 AI 도입 후의 효과를 소개했습니다.

본 시도는 AI로 완전히 자동화했다는 이야기라기보다, 인간이 확인해야 할 부분으로 작업을 모았다는 이야기로 정리할 수 있을 것 같습니다. 충돌 해결과 같이 기계적으로 풀 수 있는 작업은 AI에게 맡기고, 문맥 판단과 머지(Merge) 책임은 인간에게 남깁니다. 이러한 역할 분담이 개인의 의존적인 작업을 시스템화하는 데 있어 핵심적인 포인트였다고 느낍니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0