현재 저는 여행 서비스 서버를 만들고 있습니다.
주된 기능은 여행 계획을 저장하고, 추천하고, 최적화 해주는게 주요 기능인 서비스 인데, 그렇다 보니, 모든 서비스에서 Place
에 대한 정보를 자주 쿼리하는 특징이 있었습니다.
그래서 Place 서비스의 쿼리 성능이 전체 서비스의 성능에 매우 큰 영향을 미친다고 판단했습니다.
현재 저의 서비스에서는 외부 Place API를 활용하고 있었는데, 이것이 네트워크를 통하기 때문에 매우 비효율 적이라고 생각했습니다.
그래서 캐싱을 적용하고자 하였습니다. 캐싱이 적합하다고 생각한 근거는 다음과 같았습니다.
그래서 저는 mongoDb를 활용해서 캐싱을 하기로 하였습니다.
일단 여행지 정보는 매우 많기 때문에 확장성과 용량당 저렴한 비용이 필요했고, json의 스키마 변경에도 유연하게 대응할 수 있을 거라고 생각했습니다.
또한 수정이 적은 반복 쿼리에 mongodb의 빠른 쿼리 특성도 적합할 거라고 생각했습니다.
일단 저는 여행지를 표현하는 DTO를 통일하고, PlaceView
라는 이름으로 명명했습니다.(read only라는 것을 강조하고 싶어서)
그리고 캐싱 인터페이스를 정의했습니다.
public interface PlaceCacheService {
Optional<PlaceView> get(String placeId);
/**
* 주어진 placeIds 에 해당하는 장소를 조회합니다.
* 전부 있지 않은 경우, 있는 것만 반환합니다.
* @param placeIds 장소 id 목록
* @return 조회된 장소 목록, 없는 경우, Empty List
*/
List<PlaceView> getByIdIn(String[] placeIds);
void put(PlaceView placeView);
void putAll(List<PlaceView> placeViews);
}
이것에 대한 mongodb 구현체를 만들었습니다.
저는 AOP를 활용해서, Place 조회로직과 캐싱 로직을 독립적으로 분리했습니다.
@Slf4j
@Component
@Aspect
@RequiredArgsConstructor
public class PlaceRepositoryCacheAspect {
private final PlaceCacheService placeCacheService;
@SuppressWarnings("unchecked")
@Around("execution(java.util.Optional<click.porito.travel_core.place.dto.PlaceView> click.porito.travel_core.place.dao.PlaceRepository.getPlace(String)) && args(placeId)")
public Object aroundGetPlace(ProceedingJoinPoint joinPoint, String placeId) throws Throwable {
// check cache
Optional<PlaceView> placeView = placeCacheService.get(placeId);
if (placeView.isPresent()) {
log.debug("Cache Hit - Get Place From Cache : {}", placeId);
return placeView;
}
// cache miss
log.debug("Cache Miss - Get Place From : {}", joinPoint.getTarget().getClass().getSimpleName());
Optional<PlaceView> result = (Optional<PlaceView>) joinPoint.proceed();
// load to cache
if (result.isPresent()) {
// cache
log.debug("Cache Put - Put Place To Cache : {}", placeId);
placeCacheService.put(result.get());
}
return result;
}
물론 상황에 따라 달라질 수 있지만, 속도 향상이 있었습니다. (1164 -> 32)
물론 저의 로컬환경이 와이파이를 사용하기 때문에 더 크게 낫을 수도 있지만, 어쨌든 상당히 성능 향상폭이 큰것을 볼 수 있습니다.
이 장소 쿼리의 경우 서비스 전반에서 상당부분 의존하고 있는 만큼, 단순히 이 API의 개선이라기 보다는 전체 서비스의 성능에도 큰 영향을 미칠것으로 예상됩니다.