BACK TO RESUME

AI Code Review System

GITHUB

2025.08 - 2025.11

1인 개발 (기여도 100%)

예산 0원, 개인 시간만으로 사내 AI 코드 리뷰 시스템을 설계 및 구축하여 팀 리뷰 병목을 해소하고 사내 공식 성과로 인정받았습니다. 퇴사 후에도 시스템의 구조적 한계를 직접 개선한 과정까지 기술합니다.

KotlinSpring Boot 3Google GeminiGitHub AppGitLabn8nLM StudioCoroutine

Technical Insights & Record

1. Zero-Budget Automation (Phase 1: 2025.08 - 2025.09)

The Challenge (Situation)

차세대 의약품 배송 서비스 개발에서 임시 팀장을 맡으며, 4인 팀의 유일한 머지 마스터가 되었습니다. 나머지 팀원 모두 저보다 직급이 낮아 모든 MR의 리뷰를 제가 직접 리뷰해야 했고, 실무와 매니징을 병행하는 상황에서 코드 리뷰에 쏟을 리소스가 턱없이 부족했습니다. 신입 팀원과의 코드 품질 편차를 줄이려면 교육적 리뷰가 필요했지만, 유료 도구 도입은 예산 지원이 없어 불가능한 상황이었습니다.

The Strategy (Action)

업무 외 개인 시간을 써서 사내 인프라만으로 파이프라인을 직접 설계했습니다. GitLab Webhook + n8n + LM Studio를 연동해 외부망 노출 없이 동작하는 구조로 만들었습니다.
GitLab Pipeline Workflow (Phase 1)Click to Enlarge
GitLab Request Flow

Prompt Engineering Rigor

프롬프트 최적화에는 당시 최신 모델인 Gemini 2.5 Pro를 활용했습니다. System Prompt를 작성한 뒤 LM Studio에서 실제 코드와 함께 실행해 결과를 직접 확인하고, 문제가 있는 응답과 그 원인을 Google AI Studio에 다시 제공해 다음 버전의 프롬프트를 만드는 사이클을 100회 이상 반복했습니다. 파이프라인을 구현하는 것보다 이 과정이 더 오래 걸렸습니다.

  • Git Diff를 Hunk 단위로 분해하고 Import 구문 등 추론에 불필요한 노이즈를 제거해 프롬프트 토큰 사용량 20% 절감
  • n8n의 한계를 인식하고 추후 Kotlin 서버로 확장 가능한 데이터 구조로 설계
  • AI 모든 질의는 사내 인프라(LM Studio)에서 완결해 외부망 노출 없이 보안 가이드라인 준수

2. Personal Refactoring (Phase 2: 2025.10 - 2025.11)

초기 n8n 기반 파이프라인은 데이터 가공이 불편하고 확장에 한계가 있었습니다. 퇴사 후 개인 시간을 써서 Kotlin + Spring Boot 3 기반 전용 서버로 직접 다시 만들었습니다.

Facade Pattern

PR 요약과 코드 리뷰 두 가지 이벤트 유형이 생기면서 단일 진입점에서 명확하게 라우팅할 필요가 생겼고, Facade로 이를 정리했습니다.

Producer-Consumer

GitHub/Gemini API의 Rate Limit을 안정적으로 관리하기 위해 Coroutine Channel 기반 큐로 요청 흐름을 제어했습니다.
Refactored System Architecture (Phase 2)Click to Enlarge
AI Code Review System Architecture

3. Engineering Highlights

01.

경량 비동기 이벤트 처리 (Coroutine Channel Queue)

별도의 메시지 브로커 없이 Coroutine Channel 기반 인메모리 큐로 Webhook 이벤트를 비동기 처리합니다. 단일 인스턴스 환경에서 Kafka나 RabbitMQ 같은 외부 브로커는 오버스펙이라 판단했고, Kotlin 코루틴과 자연스럽게 맞아 이 구조를 택했습니다.
kotlin
// Webhook 이벤트 비동기 처리를 위한 Channel 기반 큐
private val channel = Channel<ReviewJob>(capacity = Channel.BUFFERED)

@PostConstruct
fun startWorkers() {
    repeat(workerCount) { idx ->
        scope.launch {
            for (job in channel) {
                runCatching { 
                    codeReviewService.review(job) 
                }.onFailure { logger.error("Review failed", it) }
            }
        }
    }
}
02.

전역 Rate-Limited Scatter-Gather 최적화

AS-IS병렬 호출 동시성 미제어
PR을 청크로 나눠 async로 병렬 호출할 때 동시성 제어가 없었습니다. 10개 이상의 코루틴이 Gemini API를 동시에 호출하면서 429 Too Many Requests 에러가 반복됐습니다. 또한 PR 요약과 코드 리뷰가 각각 별도로 API를 호출하는 구조라, 두 서비스가 동시에 실행될 경우 호출량이 합산되어 Rate Limit을 초과하는 상황을 막을 방법이 없었습니다.
TO-BE서비스 레벨 Semaphore 제어
CodeReviewService 안에 Semaphore를 직접 선언해 Gemini API 호출을 서비스 레벨에서 통합 관리합니다. 청크가 아무리 많이 생성돼도 동시 호출 수를 MAX_CONCURRENCY로 고정해 Rate Limit을 넘지 않도록 했습니다.
kotlin
// CodeReviewService 내 동시성 제어
class CodeReviewService {
    companion object {
        private const val MAX_CONCURRENCY = 3
    }
    private val semaphore = Semaphore(MAX_CONCURRENCY)

    suspend fun review() {
        ..
        tasks.mapIndexed { index, task ->
            async {
                semaphore.withPermit {
                    processOne(task, model, index + 1, tasks.size)
                }
            }
        }.awaitAll()
    }
}

4. Outcomes & Impact

Infra Cost

₩0

Zero-Budget

429 Error Rate

0건

Rate Limit 완전 제어

Prompt Iterations

100+

Google AI Studio 반복 검증

  • Coroutine ChannelSupervisorJob을 활용해 특정 작업이 실패해도 전체 시스템으로 전파되지 않도록 격리하고, 상세 에러 로깅으로 트러블슈팅이 가능한 구조로 설계
  • Semaphore로 동시 호출 수를 제한하고 호출 간 강제 쿨다운을 추가해 Gemini API 429 에러를 0건으로 유지
  • Phase 1에서 구축한 Zero-Budget 자동화 시스템이 사내 공식 성과로 인정받았습니다

5. Lessons Learned

n8n을 처음 사용했음에도 빠르게 워크플로우를 만들어 코드 리뷰 시스템을 구축할 수 있었습니다. 하지만 오래 운영하고 개선해야 하는 경우, 처음부터 유지보수 가능한 환경으로 설계하는 게 결국 더 빠르다는 걸 직접 겪으면서 알았습니다.

코드 리뷰 과정에서 병목을 느껴서 상용 툴 도입을 먼저 물어봤습니다. 저희 팀만 쓰는 도구에 예산을 쓰기 어렵다는 답이 돌아왔고, 다른 팀까지 설득하기도 쉽지 않아 결국 직접 만들었습니다. 출근해서 커피 마시다 대표님께 만들고 있다고 말씀드렸더니, 프롬프트 설계에 대해 기술적인 조언을 받을 수 있었습니다. 공식 경로가 막혔을 때 직접 만들면 된다는 생각을 처음으로 행동으로 옮긴 프로젝트였습니다. 이후로 불편한 게 생기면 직접 만드는 방식으로 이어가고 있습니다.