Go의 context 패키지로 HTTP 요청 타임아웃 처리하기
문제 상황
외부 결제 API를 호출하는 서비스에서 간헐적으로 응답 시간이 30초 이상 걸리는 현상이 발생했다. 해당 요청들이 쌓이면서 고루틴이 계속 증가하고, 결국 서버 전체가 느려지는 문제가 발생했다.
Context를 활용한 타임아웃
Go 1.7부터 표준 라이브러리에 포함된 context 패키지를 사용하면 깔끔하게 해결할 수 있다.
func callPaymentAPI(userID string) (*PaymentResult, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*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, err
}
defer resp.Body.Close()
// ... 응답 처리
}
주의할 점
defer cancel()을 반드시 호출해야 한다. context가 타임아웃 전에 완료되더라도 내부 타이머를 정리해야 메모리 누수가 발생하지 않는다.
또한 데이터베이스 쿼리에도 동일하게 적용할 수 있다. database/sql 패키지의 QueryContext, ExecContext 메서드를 사용하면 된다.
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE id = ?", userID)
결과
타임아웃을 5초로 설정한 후, 장애 상황에서도 고루틴 수가 일정 수준 이상 증가하지 않았다. 모니터링 결과 평균 응답 시간도 크게 개선되었다.
context는 단순 타임아웃뿐 아니라 취소 신호 전파, 요청 범위 값 전달 등 다양하게 활용할 수 있어서 Go 서버 개발에서 필수적인 패턴이다.