ObjectMapper와 MapStruct의 성능차이
문제 상황
복잡한 비즈니스 로직을 가지고 있는 Service 로직에서, 9천건의 데이터 조회시 5초~7초 정도 소요되는 성능 이슈
원인 분석
성능 이슈의 위치를 정확히 파악하기 위해,
StopWatch를 성능 이슈가 있을만한 로직의 앞 뒤에 붙여줘서 확인했다.
StopWatch를 붙인 곳
- List의 객체 변환 로직
- 조회 쿼리
StopWatch를 활용한 처리 시간 체크 코드
import org.springframework.util.StopWatch;
// ...
final StopWatch firstWatch = new StopWatch("firstWatch");
firstWatch.start();
final List<UserAddress> userAddressList = userList.stream()
.map(user -> modelMapper.map(user, User.class))
.collect(Collectors.toList());
firstWatch.stop();
log.info("=== firstWatch : {}", firstWatch.prettyPrint());
ASIS - ModelMapper 사용 코드
import org.modelmapper.ModelMapper;
// ...
final List<UserAddress> userAddressList = userList.stream()
.map(user -> modelMapper.map(user, User.class))
.collect(Collectors.toList());
ModelMapper 사용 시 처리 시간
firstWatch : StopWatch 'firstWatch': running time (millis) = 3043
-----------------------------------------
ms % Task name
-----------------------------------------
03043 100%
성능 개선
여러 곳의 beanmark를 확인해 보면, MapStruct의 성능이 뛰어나단 걸 알 수 있다.
한번 적용해 보자.
TOBE - MapStruct 코드로 전환
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
User toDto(UserAddress userAddress);
}
final List<User> userList = userAdressList.stream()
.map(UserMapper.INSTANCE::toDto)
.collect(Collectors.toList());
MapStruct 사용 시 처리 시간
firstWatch : StopWatch 'firstWatch': running time (millis) = 147
-----------------------------------------
ms % Task name
-----------------------------------------
00147 100%
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
User toDto(UserAddress userAddress);
}
성능 개선 결과
3초가 걸리던 코드가 0.14초 걸리는 코드로 변화 하였다.
MapStruct가 빠른 이유
- ModelMapper는 런타임에 리플렉션 사용
- MapStruct는 컴파일 타임에 코드 생성
MapStruct의 성능이 ModelMapper보다 일반적으로 더 뛰어난 이유는 컴파일 타임에 코드를 생성하여 런타임 오버헤드를 최소화하기 때문이다. 또한 컴파일 시점에 생성된 코드는 타입 체크가 가능해서 런타임에 발생할 수 있는 타입 관련 오류를 사전에 방지할 수 있다.
작은 부분이지만, 어떤 라이브러리를 사용하느냐에 따라 느린 서비스를 제공하느냐, 빠른 서비스를 제공하느냐가 갈릴 수도 있다.
결론
프로그래머는 사용자 경험을 최적화하기 위해 가능한 가장 빠른 성능을 목표로 해야 한다.
프로젝트의 초기 단계에서 '규모가 작으니 어떤 도구를 사용해도 괜찮다'는 생각은, 시간이 지나면서 예상치 못한 복잡성과 성능 문제를 불러일으킬 수 있다.
아래 baeldung 블로그에 있는 benchmark를 참고해 보면 MapStruct의 성능이 월등히 뛰어난 것을 볼 수 있다.
- https://mapstruct.org/
- https://www.baeldung.com/java-performance-mapping-frameworks#1averagetime