gRPC 서비스에서 Deadlines 설정하기
문제 상황
결제 서비스에서 외부 API 지연으로 인해 전체 시스템이 느려지는 현상이 발생했다. gRPC로 연결된 주문 서비스가 결제 서비스 응답을 무한정 기다리면서 connection pool이 고갈되었다.
gRPC Deadline
gRPC는 HTTP/2 기반이라 자체적으로 timeout 메커니즘이 없다. 클라이언트에서 deadline을 명시적으로 설정해야 한다.
Go 클라이언트 예시
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
resp, err := client.ProcessPayment(ctx, &pb.PaymentRequest{
OrderId: orderId,
Amount: amount,
})
if err != nil {
if status.Code(err) == codes.DeadlineExceeded {
log.Error("payment timeout")
return ErrTimeout
}
return err
}
Node.js 클라이언트 예시
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 3);
client.processPayment(request, { deadline }, (err, response) => {
if (err) {
if (err.code === grpc.status.DEADLINE_EXCEEDED) {
console.error('payment timeout');
}
return callback(err);
}
callback(null, response);
});
서버 측 처리
서버에서도 context의 deadline을 확인해 불필요한 작업을 중단할 수 있다.
func (s *PaymentService) ProcessPayment(ctx context.Context, req *pb.PaymentRequest) (*pb.PaymentResponse, error) {
select {
case <-ctx.Done():
return nil, status.Error(codes.DeadlineExceeded, "context canceled")
default:
}
// 실제 처리 로직
result, err := s.externalAPI.Call(ctx, req)
if err != nil {
return nil, err
}
return &pb.PaymentResponse{Status: result}, nil
}
적용 결과
- 장애 전파 차단: 외부 API 장애 시 3초 후 타임아웃 처리
- 리소스 효율화: connection pool 고갈 방지
- 모니터링 개선: deadline exceeded 에러로 지연 구간 특정 가능
서비스별 SLA에 따라 deadline을 차등 적용했다. critical한 API는 1초, 일반 조회는 5초로 설정해 운영 중이다.