Go 채널 버퍼 크기 선택 시행착오

문제 상황

재택근무 중 운영 중인 Go API 서버에서 특정 시간대에 응답 속도가 급격히 느려지는 현상이 발견됐다. 모니터링 결과 고루틴 수가 비정상적으로 증가하고 있었다.

로그 처리를 위해 버퍼 없는 채널을 사용하고 있었는데, 로그 처리 속도보다 생성 속도가 빠를 때 고루틴이 블로킹되는 것이 원인이었다.

// 기존 코드
logChan := make(chan LogEntry)

go func() {
    for entry := range logChan {
        writeToFile(entry) // 느린 I/O 작업
    }
}()

해결 과정

pprof로 프로파일링한 결과 채널 송신 부분에서 대기 시간이 과도하게 발생했다. 버퍼를 추가하되 적절한 크기를 결정해야 했다.

피크 시간대 로그 생성률을 측정한 결과 초당 약 2000개였고, 파일 쓰기는 초당 1500개 정도 처리 가능했다. 버퍼를 5000으로 설정해 3초 정도의 버스트를 흡수할 수 있도록 했다.

logChan := make(chan LogEntry, 5000)

// 워커 수 증가
for i := 0; i < 3; i++ {
    go func() {
        for entry := range logChan {
            writeToFile(entry)
        }
    }()
}

결과

버퍼 추가와 워커 수 조정 후 응답 속도가 정상화됐다. 다만 버퍼가 가득 차는 경우를 대비해 채널이 80% 찼을 때 경고를 보내는 모니터링도 추가했다.

채널 버퍼 크기는 무조건 크다고 좋은 게 아니라, 실제 처리량과 허용 가능한 지연 시간을 기준으로 산정해야 한다는 걸 배웠다.

Go 채널 버퍼 크기 선택 시행착오