Go 1.10 컨텍스트 타임아웃 처리 패턴

문제 상황

외부 결제 API를 호출하는 Go 서비스에서 간헐적으로 응답이 지연되면서 고루틴이 쌓이는 문제가 발생했다. 기존에는 select-case로 타임아웃을 처리했는데, 중첩 호출이 많아지면서 관리가 어려웠다.

Context 패턴 적용

Go 1.7부터 표준 라이브러리에 포함된 context 패키지를 활용했다.

func CallPaymentAPI(userID string) (*Payment, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    req, err := http.NewRequest("POST", apiURL, body)
    if err != nil {
        return nil, err
    }
    req = req.WithContext(ctx)

    resp, err := client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("payment api call failed: %w", err)
    }
    defer resp.Body.Close()
    
    // response handling...
}

장점

  1. 명시적 타임아웃: 함수 시작부에 타임아웃을 선언해 의도가 명확하다
  2. 자동 전파: HTTP 클라이언트가 context를 받아 내부적으로 타임아웃을 처리한다
  3. 리소스 정리: defer cancel()로 확실한 정리가 가능하다

주의사항

context.Background()를 매번 생성하지 않고, 상위 핸들러에서 전달받은 context를 사용하는 것이 더 좋다. 특히 gRPC 환경에서는 요청 전체의 타임아웃을 추적할 수 있다.

func Handler(ctx context.Context, req *Request) (*Response, error) {
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()
    // 이미 상위 context가 취소되면 즉시 반환됨
}

모니터링 결과 타임아웃 발생 시 고루틴이 정상적으로 종료되면서 메모리 누수가 해결되었다.