gRPC 서비스 간 통신에서 Deadline 설정 삽질기
문제 상황
결제 서비스에서 재고 서비스로 gRPC 호출을 하는데, 재고 서비스가 느려지면서 결제 서비스까지 연쇄적으로 타임아웃이 발생했다. gRPC 클라이언트에서 deadline을 설정하지 않아 기본값으로 동작하고 있었다.
Deadline 설정
gRPC에서는 HTTP/2의 타임아웃과 다르게 deadline 개념을 사용한다. 절대 시간을 기준으로 동작하며, 서비스 체인 전체에 전파된다.
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
res, err := client.CheckInventory(ctx, &pb.CheckRequest{
ProductId: productId,
Quantity: quantity,
})
if err != nil {
if status.Code(err) == codes.DeadlineExceeded {
// 타임아웃 처리
return ErrInventoryTimeout
}
return err
}
서비스별 Deadline 전략
- API Gateway: 5초 (클라이언트 응답 기준)
- 결제 서비스 → 재고 서비스: 3초
- 재고 서비스 → DB: 2초
상위 서비스의 deadline이 하위보다 길어야 하며, 여유를 두고 설정해야 한다.
에러 핸들링
func handleGRPCError(err error) error {
st, ok := status.FromError(err)
if !ok {
return err
}
switch st.Code() {
case codes.DeadlineExceeded:
return ErrTimeout
case codes.Unavailable:
return ErrServiceUnavailable
case codes.Internal:
return ErrInternal
default:
return err
}
}
교훈
gRPC를 사용할 때는 처음부터 deadline을 명시적으로 설정해야 한다. 특히 마이크로서비스 환경에서는 한 서비스의 지연이 전체로 전파되기 쉽다. Retry 정책도 함께 고려해야 하는데, 이건 다음에 정리할 예정이다.