Go에서 context.Context 타임아웃 처리 패턴
문제 상황
결제 서비스에서 외부 PG사 API를 호출하는 부분이 있었는데, 간헐적으로 응답이 30초 이상 걸리면서 고루틴이 쌓이는 현상이 발생했다. 타임아웃 설정이 없어서 HTTP 클라이언트가 무한정 대기하고 있었다.
해결 방법
context.WithTimeout을 사용해 타임아웃을 명시적으로 설정했다.
func callExternalAPI(paymentID string) (*Response, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", apiURL, body)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
return nil, fmt.Errorf("API timeout: %w", err)
}
return nil, err
}
defer resp.Body.Close()
return parseResponse(resp)
}
주의사항
defer cancel()을 반드시 호출해야 한다. 타임아웃이 발생하지 않더라도 리소스를 정리하기 위해 필요하다.
DB 쿼리에도 동일한 패턴을 적용했다:
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, query, args...)
결과
- 평균 응답 시간: 2.3초 → 1.8초
- 타임아웃 발생 시 즉시 에러 반환으로 사용자 경험 개선
- 고루틴 누수 문제 해결
타임아웃 값은 모니터링 지표를 보면서 조정했다. 너무 짧으면 정상 요청도 실패하고, 너무 길면 의미가 없어서 P95 응답 시간의 1.5배 정도로 설정했다.