이 글은 Andrew Poydence의 Go: Avoiding Bare Channels를 번역한 글입니다.

tl;dr

'bare channel'은 select 문 밖에서 쓰인 채널입니다. 'bare channel'은 제대로 사용되지 않을 때, 고루틴이 블록되는 결과를 초래할 수 있습니다.

Loggregator

제가 처음 Cloud FoundryLoggregator(Cloud Foundry를 위한 로깅 및 메트릭 시스템)를 개발하기 시작했을 때, 이 프로젝트는 거의 모든 곳에서 채널을 사용하고 있었습니다. Loggregator는 로그를 위한 'dumb pipe'를 의도했기 때문에, 채널의 사용은 자연스러운 것이었습니다. 하지만, 저희는 계속 고루틴 누수 문제에 부닥쳤습니다.

고루틴 누수 문제를 한 번도 겪어보지 않은 고퍼분들은, 운이 좋으신 겁니다. 고루틴 누수로 인한 증상은 심각했습니다. 서버의 CPU와 메모리 자원은 완전히 고갈되었고, 시스템은 성능 저하 상태에 빠졌습니다. 저희가 문제를 파고들려 했을 때, 저희는 계속해서 같은 코드 발견했습니다.

func (u User) WriteLog(l Log) {
    u.logs <- l
}

문제가 없어 보이나요? 아닙니다. 사용자가 연결을 끊고 logs채널 에서 값을 더 이상 꺼내오지 않을 때 어떤 일이 발생할까요? 결국 logs채널은 블록되기 시작할 것입니다. 그래서 WriteLog()를 호출하는 것 또한 블록될 것이고요.

이것은 분명 잘못됐습니다. 원래는 어떻게 동작해야 할까요? 간단한 테스트에선 잘못됐다는 것이 드러나지 않았을까요? 누군가 WriteLog를 고루틴으로 호출하는 코드를 작성하고 테스트했을 때, 테스트는 성공했습니다.

go u.WriteLog(1)

좋은 해결책은 아니죠? 이 방법은 결국 고루틴의 발을 묶고, u를 잡고있어 가비지 컬렉터가 동작하지 못하게 만듭니다.

저희는 이러한 채널들을 'bare channels'라고 부르기 시작했습니다. select문 밖에서 쓰인 채널을 말이죠. 기본적으로, 당신은 일정 시간 뒤에 채널에 쓰는 작업에서 빠져나올 수 있기를 바랄 것입니다. Loggregator가 처음 시작할 때 Cloud Foundry 어플리케이션에서 지연시키는 대신 의도적으로 몇개의 로그를 버리기로 결정했습니다. 그래서 저희는 코드를 이렇게 바꾸기 시작했습니다.

func (u User) WriteLog(l Log) {
    select {
    case u.logs <- l:
    default:
    }
}

이 코드가 조금은 용서할 수 없어 보이지만, 채널이 충분히 큰 버퍼를 갖고있는 이상 로그를 버리는 일이 자주 일어나지는 않을 것입니다. 이 코드는 Loggregator가 멈추고 시스템 자원을 먹어치우는 대신 로그를 버리게 만듭니다. 저희는 얼마 뒤에, 채널이 꽉 찼을 때 새로운 로그를 대신하여 오래된 로그들을 버리기로 결정했고, Diode를 사용하기 시작했습니다. (Diode를 여기서 다루진 않겠습니다.)

하지만 몇 몇 경우에선 로그를 버리지 않는게 좋을 것 같았습니다. 그렇다면, 작업이 중단되게 내버려둬야 할까요? 물론 아닙니다! 이럴때 countext가 빛을 발합니다.

func (u User) WriteLog(ctx context.Context, l Log) {
    select {
    case u.logs <- l:
    case <-ctx.Done():
      //cancelled
    }
}

이 코드는 로그를 버리는 대신 logs채널이 데이터를 소비하거나 context가 취소될 때 까지 기다릴 것입니다.

Conclusion

코드를 짤 때, 채널은 직접 교류하는 곳 넘어로도 영향을 미칠 수 있다는 것을 항상 염두해 두셔야 합니다. 만약 소비자 고루틴이 죽거나 느려진다면(e.g., 네트워크 I/O와 함께 채널을 사용하는 경우), 하위의 고루틴은 예기치 못한 방법으로 고통받을 수 있습니다.

Go는 select와 같은 메커니즘을 여럿 제공하지만, 개발자들은 보통 거기까지 생각하지 않기도 합니다.

Is this real code?

사실, 실제 코드는 아닙니다. 위의 예제들은 아이디어를 간결하게 전달하기 위해 엄청나게 단순화한 것입니다. 만약 이 문제가 당신을 흥분하게 만들거나, 실제 코드가 어떤지 보고싶으시다면, Loggregator 레포지터리를 보시면 됩니다!