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...
}
장점
- 명시적 타임아웃: 함수 시작부에 타임아웃을 선언해 의도가 명확하다
- 자동 전파: HTTP 클라이언트가 context를 받아 내부적으로 타임아웃을 처리한다
- 리소스 정리: 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가 취소되면 즉시 반환됨
}
모니터링 결과 타임아웃 발생 시 고루틴이 정상적으로 종료되면서 메모리 누수가 해결되었다.