왜 PR 폭주가 문제인가
Renovate를 기본 설정으로 켜면 의존성 하나당 PR이 하나씩 생성된다. 모노레포나 프론트엔드 프로젝트처럼 패키지 수가 수백 개인 환경에서는 매주 수십 개의 PR이 쏟아진다. 문제는 단순한 양이 아니다. PR마다 CI가 돌면서 러너 비용이 늘고, 리뷰어는 사소한 patch 업데이트를 일일이 승인하느라 정작 중요한 major 업그레이드를 놓친다. 결국 대부분의 PR이 방치되고, 의존성은 오히려 더 오래되는 역설이 생긴다.
핵심 해결책: 연관 패키지 그룹화
해결의 출발점은 "함께 움직이는 패키지는 하나의 PR로 묶는다"는 원칙이다. React 계열, ESLint 플러그인, AWS SDK, 테스트 도구처럼 버전이 서로 맞물리는 패키지들은 개별 PR로 나눌 이유가 없다. Renovate의 packageRules와 groupName을 쓰면 정규식·매니저·범위 기준으로 묶을 수 있다.
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"packageRules": [
{
"matchPackagePatterns": ["^@aws-sdk/"],
"groupName": "aws-sdk"
},
{
"matchPackagePatterns": ["eslint"],
"groupName": "linters"
},
{
"matchPackageNames": ["react", "react-dom"],
"matchPackagePatterns": ["^@types/react"],
"groupName": "react"
}
]
}
업데이트 유형별로 정책 나누기
모든 패키지를 무조건 묶는 것도 정답은 아니다. major 업그레이드는 breaking change 가능성이 있어 개별 검토가 필요하고, patch/minor는 묶어도 위험이 낮다. matchUpdateTypes로 유형을 구분해 "patch·minor는 하나로 묶고, major는 개별 PR로 분리"하는 전략이 실무에서 가장 안정적이다.
{
"packageRules": [
{
"matchUpdateTypes": ["patch", "minor"],
"matchCurrentVersion": "!/^0/",
"groupName": "all non-major dependencies",
"groupSlug": "all-minor-patch"
},
{
"matchUpdateTypes": ["major"],
"dependencyDashboardApproval": true
}
]
}
여기서 matchCurrentVersion으로 0.x 패키지를 제외한 이유는, semver상 0.x의 minor 업데이트가 사실상 breaking change일 수 있기 때문이다.
스케줄과 동시 실행 제한
그룹화로 PR 개수를 줄였다면, 다음은 타이밍 제어다. schedule로 업데이트를 특정 시간대에만 열고, prConcurrentLimit·prHourlyLimit으로 한 번에 열리는 PR 수를 제한한다. 이렇게 하면 CI 부하가 분산되고 리뷰 큐가 관리 가능한 크기로 유지된다.
{
"schedule": ["after 9am and before 6pm every weekday"],
"prConcurrentLimit": 5,
"prHourlyLimit": 2,
"dependencyDashboard": true
}
전략 비교
| 전략 | PR 수 | 리뷰 부담 | 적합한 경우 |
|---|---|---|---|
| 기본(패키지별) | 매우 많음 | 높음 | 소규모 프로젝트 |
| 유형별 그룹화 | 중간 | 낮음 | 대부분의 팀 |
| 전체 통합 그룹 | 적음 | 낮으나 실패 원인 추적 어려움 | 안정된 CI 보유 팀 |
Dependency Dashboard와 자동 병합
dependencyDashboard: true는 GitHub 이슈 하나에 대기 중인 모든 업데이트를 모아 보여준다. major 업데이트에 dependencyApproval을 걸어두면, 대시보드에서 체크박스로 승인할 때만 PR이 열린다. 여기에 신뢰도가 높은 patch 업데이트는 automerge로 넘겨 사람 손을 완전히 떼는 방식이 효과적이다.
{
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"matchCurrentVersion": "!/^0/",
"automerge": true,
"automergeType": "branch"
}
]
}
automergeType: "branch"는 CI가 통과하면 PR을 열지 않고 브랜치에서 바로 병합해, PR 개수 자체를 추가로 줄여준다.
주의점
그룹을 너무 크게 묶으면 한 패키지의 테스트 실패가 그룹 전체를 막아, 어떤 의존성이 원인인지 파악하기 어려워진다. 그룹 크기는 "함께 실패해도 원인을 추적할 수 있는 범위"로 제한하는 게 좋다. 또한 automerge는 CI 커버리지를 신뢰할 수 있을 때만 켜야 한다. 테스트가 얇은 상태에서 자동 병합을 켜면 조용히 회귀가 배포될 수 있다. 마지막으로 설정 변경 후에는 반드시 Dependency Dashboard로 실제 그룹 구성과 PR 생성 패턴을 검증하고, 예상과 다르면 matchPackagePatterns의 정규식 범위부터 다시 점검하라.