TECH 으로 돌아가기
TECH HACKER NEWS 오늘 7분 읽기 23 READS

Go 1.27, 고루틴 누수를 프로덕션에서 잡아내는 새 프로파일러

Go 1.27, 고루틴 누수를 프로덕션에서 잡아내는 새 프로파일러
SOURCE IMAGE · HACKER NEWS

동시성은 Go의 가장 큰 매력이지만 동시에 가장 흔한 실수의 근원이기도 하다. 채널, 뮤텍스, 웨이트그룹 같은 원시 요소로 고루틴들이 서로 신호를 주고받으며 블록되는 구조는 코드를 간결하게 만들지만, 블록을 풀어 줄 조건이 영원히 충족되지 않는 상황을 만들기도 쉽다. 이렇게 영구히 멈춘 고루틴이 바로 '고루틴 누수(goroutine leak)'다. Go 1.27에 새로 들어간 고루틴 누수 프로파일러는 이 문제를 실행 중인 프로그램, 특히 프로덕션 환경에서 정밀하게 찾아내는 것을 목표로 한다.

왜 기존 도구로는 부족했나

누수된 고루틴은 하나씩 보면 사소해 보여도 시간이 지나면 메모리를 갉아먹고, 그 고루틴이 참조하는 객체까지 붙잡아 두면서 가비지 컬렉터(GC)의 CPU 부담을 키운다. GOMEMLIMIT을 설정해 둔 경우 이 영향은 더 두드러진다. 문제는 탐지가 까다롭다는 점이다. 단위 테스트 수준에서는 오픈소스 라이브러리 goleak이 테스트 종료 후 살아 있는 고루틴을 의심 대상으로 표시해 주고, Go 1.25에 들어온 synctest 패키지는 동시 이벤트의 순서를 통제해 재현이 어려운 시나리오를 안정적으로 검증하도록 돕는다.

그러나 이 접근법들은 테스트가 예상하지 못한 방식으로 동작하는 대규모 프로덕션 시스템을 검사하지 못한다. 기존 고루틴 프로파일은 특정 지점에 몰린 고루틴을 보여 주긴 하지만, 실제로 누수된 것과 설계상 트래픽 증가로 일시적으로 대량 블록된 것을 구분하지 못한다. 반대로 소수만 누수되는 경우는 수년간 눈에 띄지 않고 남아 있기도 한다.

새 프로파일러의 동작과 사용법

Go 1.27의 프로파일러는 이 지점을 정면으로 겨냥한다. 오탐이 거의 없는 정밀한 탐지를 제공하는 대신, 대상 범위를 채널 연산과 sync 패키지 원시 요소에 영구 블록된 고루틴으로 한정한다. 여기에는 nil 채널을 포함한 채널 송수신, default가 없는 블로킹 select 문, 그리고 Mutex·RWMutex·WaitGroup·Cond가 포함된다. 파일이나 네트워크 IO, 시스템 콜, 스핀락 같은 사용자 정의 동시성에서의 블록은 대상에 들어가지 않는다. 다만 원문이 지적하듯 이 제한된 범위만으로도 현실의 누수 상당수가 커버된다.

사용법은 간단하다. 프로파일은 runtime/pprof 패키지에서 goroutineleak 타입으로 노출되며, net/http/pprof를 이미 붙여 둔 서비스라면 추가 작업 없이 /debug/pprof/goroutineleak 엔드포인트에서 바로 수집할 수 있다. 예컨대 결과를 받아 두는 언버퍼드 채널에서 워커들이 송신하려다 메인 고루틴이 에러로 조기 반환해 수신 루프가 끝나 버리면, 남은 송신 고루틴들이 영원히 블록된다. 원문은 이 패턴을 Uber 프로덕션 서비스에서도 발견된 전형적 실수로 소개하며, 프로파일이 문제의 송신 연산이 있는 정확한 줄을 짚어 준다고 설명한다. 해법은 대체로 채널에 버퍼를 주거나, 누락된 return을 채우거나, 다 쓴 채널을 close 하는 식으로 단순하다.

구현 원리와 한계

탐지의 핵심 아이디어는 '생존성(liveness)'이라는 귀납적 성질이다. 블록되지 않은 고루틴은 명백히 살아 있고, 그 고루틴이 참조하는 원시 요소에 블록된 고루틴도 언젠가 풀릴 수 있으니 살아 있다고 본다. 이 과정을 반복해 더 이상 새로운 생존 고루틴이 나오지 않을 때까지 추적하고, 남은 것을 누수로 판정한다. 여기서 필요한 '메모리 도달 가능성' 계산은 Go 런타임의 동시 삼색 마크-스윕 GC가 이미 수행하고 있어, GC를 전면 교체하지 않고 몇 가지만 손봐 목적에 맞췄다. 이 기능은 오르후스대학교, 워싱턴대학교 세인트루이스, Uber의 공동 연구(Saioc 외, ASPLOS 2025)에서 출발했다.

한계도 분명하다. 원시 요소가 전역 변수나 실행 가능한 고루틴을 통해 계속 도달 가능하면, 실제로는 다시 쓰이지 않더라도 누수로 잡히지 않는다(메모리 과도 도달). 또한 누수는 발생한 뒤에야 탐지되며 사전 예측은 불가능해, 산발적으로 재현되는 버그 진단은 여전히 과제로 남는다. 성능 면에서 메모리 부담은 미미하지만, 고루틴이 사슬처럼 서로를 블록하는 '데이지 체인' 같은 병리적 상황에서는 일반 GC보다 느려질 수 있다.

실무에 어떤 의미인가

한국의 백엔드·인프라 팀에게 이 기능의 매력은 '프로덕션에서 오탐 없이'라는 조합에 있다. 마이크로서비스 환경에서 메모리가 천천히 새는 원인을 특정하지 못해 재시작으로 버티던 팀이라면, net/http/pprof만 붙어 있으면 별도 계측 없이 누수 지점의 코드 줄까지 받아볼 수 있다. GC와 함께 동시 실행되고, 한 번 관측 가능한 누수는 이후 어느 시점에서도 관측되므로, 4시간마다 같은 식으로 프로파일링 주기를 늘려 오버헤드를 사실상 없애면서도 탐지력을 유지할 수 있다는 점은 운영 관점에서 특히 실용적이다. 다만 IO나 커스텀 락에 물린 블록은 잡지 못하므로, 이 프로파일러를 단위 테스트의 goleak·synctest와 겹쳐 여러 계층에서 함께 쓰는 것이 원문이 권하는 현실적인 운용 방식이다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://go.dev/blog/goroutine-leak-profiles
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기
처리 중...