Go에서 context timeout 제대로 다루기
문제 상황
외부 결제 API 호출 시 3초 timeout을 설정했는데, 실제로는 30초 가까이 대기하는 현상이 발생했다. 모니터링 지표에서 P99 latency가 급증하고 있었다.
func (s *PaymentService) ProcessPayment(userID string) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
// 문제: HTTP client가 context를 무시하고 있었음
resp, err := http.Get(paymentURL)
// ...
}
원인
http.Get은 context를 받지 않는다. http.NewRequestWithContext를 사용해야 했다.
해결
func (s *PaymentService) ProcessPayment(ctx context.Context, userID string) error {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", paymentURL, nil)
if err != nil {
return err
}
resp, err := s.httpClient.Do(req)
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
return errors.New("payment timeout")
}
return err
}
defer resp.Body.Close()
// ...
}
배운 점
- context는 첫 번째 인자로 전달: Go 컨벤션을 따라 함수 시그니처를 수정했다
- context.Background() 남용 금지: 상위 레이어에서 받은 context를 전파해야 한다
- 취소 여부 확인:
ctx.Err()로 timeout인지 cancel인지 구분할 수 있다
외부 의존성이 있는 모든 HTTP, DB 호출에 동일한 패턴을 적용했고, P99가 3초 이하로 안정화되었다.