-
Spring - gRPC 적용 성능 최적화Spring 2024. 5. 1. 17:50
서론
Spring MSA 서버를 제작하며 서버 간 통신을 RestTemplate을 기반으로 하였다.
RestTemplate은 단순하게 HTTP 기반으로 통신을 하여 데이터를 주고 받는다. RestTemplate은 가장 일반적으로 서버 간에 통신에서 사용되는 방법이라고 생각한다.
Jmeter를 이용하여 부하테스트를 하였을 경우, TPS가 32.0/sec 정도로 생각보다 많이 낮게 나왔다. 하나의 서비스에서 TPS가 낮게 나오는 데에는 많은 이유가 있겠지만, 서버간 통신을 진행할때 유독 낮게 나와서 RestTemplate을 기반으로한 HTTP 통신의 근본적인 문제로 인해서 낮게 나오지 않을까 생각하였다.
서버간 통신기법에는 다양한 방법이 있지만, 빠른 직렬화를 바탕으로 데이터를 빠르게 전달할 수 있는 gRPC를 도입하여 문제를 해결하려고 하였다.
본문
gRPC는 다양한 특징을 가지고 있지만, 가장 큰 특징이라면 protobuf을 기반으로 하는 빠른 직렬화를 통해서 기존의 데이터를 낮은 용량으로 빠르게 전달할 수 있다는게 가장 큰 장점이라고 생각한다.
실제 직렬화 테스트를 통해서 성능을 비교해보자
@Test @DisplayName("Compare gRPC and REST") public void compare() throws IOException { OrderObject orderObject = OrderObject.newBuilder() .setOrderId("testOrderId") .setQty(1) .setTotalPrice(1000) .setUnitPrice(1000) .build(); ResponseOrder responseOrder = new ResponseOrder(); responseOrder.setId("testId"); responseOrder.setQty(1); responseOrder.setUnitPrice(1000); responseOrder.setTotalPrice(1000); Runnable json = () -> { try { byte[] bytes = objectMapper.writeValueAsBytes(responseOrder); ResponseOrder newResponseOrder = objectMapper.readValue(bytes,ResponseOrder.class); }catch (Exception e){ e.printStackTrace(); } }; Runnable proto = () -> { try{ byte[] bytes = orderObject.toByteArray(); OrderObject newOrderObject = OrderObject.parseFrom(bytes); }catch (Exception e){ e.printStackTrace(); } }; runPerformanceTest(json,"JSON"); runPerformanceTest(proto,"proto"); } private static void runPerformanceTest(Runnable runnable,String method){ long time1 = System.currentTimeMillis(); for (int i = 0; i < 2000; i++) { runnable.run(); } long time2 = System.currentTimeMillis(); System.out.println(method + ":" + (time2-time1) + "ms"); }
코드는 json, proto 객체 -> Bytes 배열 -> json,proto객체 로 바꾸는 로직을 나타낸다.
2000번을 해당 과정을 반복하였을 경우에
위와 같은 결과를 나타낸다.
약 2.5배 정도 proto가 직렬화 속도가 빨랐으며, 직렬화할 데이터가 많으면 많아질수록 proto에 비해 json이 고성능을 낸다는 것을 알 수 있다.
그렇다면 실제 controller에 적용하였을 때는 성능이 어떻게 나올까?
gPRC 를 통해 외부 서버에서 데이터를 받는 코드
@Override public UserDto getUserById(String id) { UserEntity userEntity = userRepository.findById(id); UserDto userDto = mapper.map(userEntity,UserDto.class); OrderRequest orderRequest = OrderRequest.newBuilder().setUserId(userDto.getId()).build(); OrderResponse orderResponse = blockingStub.getOrdersById(orderRequest); List<ResponseOrder> ResponseOrders = new ArrayList<>(); for (OrderObject orderObject : orderResponse.getOrderObjectsList()) { ResponseOrder responseOrder = new ResponseOrder(); responseOrder.setId(orderObject.getOrderId()); responseOrder.setQty(orderObject.getQty()); responseOrder.setUserId(orderObject.getUserId()); responseOrder.setUnitPrice(orderObject.getUnitPrice()); responseOrder.setProductId(orderObject.getProductId()); responseOrder.setTotalPrice(orderObject.getTotalPrice()); ResponseOrders.add(responseOrder); } userDto.setOrderList(ResponseOrders); return userDto; }
RestTemplate를 이용하여 외부에서 데이터를 받는 코드
@Override public UserDto getUserByIdRest(String id) { UserEntity userEntity = userRepository.findById(id); UserDto userDto = mapper.map(userEntity,UserDto.class); String url = "http://localhost:8082/orders/" + id; List<ResponseOrder> responseOrders = restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<List<ResponseOrder>>() { }).getBody(); userDto.setOrderList(responseOrders); return userDto; }
해당 코드를 기반으로 Jmeter를 이용하여 부하테스트를 진행하였다
Case1)
10명의 동시사용자가 100번의 요청을 보낼 경우RestTemplate
gRPC
각각 Throughput이 30.6/sec , 80.9/sec 으로
약 3배 정도의 tps 개선이 일어난 것을 확인 할 수 있다.
Case1)
10명의 동시사용자가 300번의 요청을 보낼 경우RestTemplate
gPRC
각각 Throughput이 31.5/sec , 84.6/sec 으로
마찬가지로, 약 3배 정도의 tps 개선이 일어난 것을 확인 할 수 있다.
결론
gRPC는 JSON과 비교하였을때 확연하게 빠르게 직렬화 한다는 것을 확인할 수 있었다.
이를 바탕으로 서버간 통신에 해당 방법들을 적용하였을때 속도가 3배 이상 차이 난다는 것을 확인할 수 있었다.
궁금한점
추후에 RestTemplate과 Webclient를 이용한 서버 간 통신도 비교하고 싶어졌다.
기본적으로 RestTemplate 같은 경우에는 동기적으로 처리하기 때문에 I/O 작업이 많을 수록 기다리는 시간이 많아지지만, WebClient 경우에는 비동기적으로 처리하기 때문에 좀 더 빠르게 처리할 수 있다.
후에 Spring Webflux를 기반으로 webclient를 적용하였을 때도 테스트를 진행하여 성능을 비교해봐야겠다.
'Spring' 카테고리의 다른 글
AssertJ 오픈소스 컨트리뷰트 하기 (0) 2024.08.05 지연로딩 , 즉시로딩 JPA 최적화 (1) 2024.07.01 Kafka를 이용한 채팅서버 개발 (0) 2024.03.15 서버 성능개선 - sql batchupdate (0) 2024.01.11