기존의 여행지 동기화 코드는 tourSpotItems 리스트를 돌면서 다음과 같은 구조를 가지고 있었습니다:
for (PlaceResponse.PlaceItem placeItem : tourSpotItems) {
// 1. contentId로 Place 조회
Optional<Place> findPlace = placeService.findByContentId(placeItem.getContentid());
if (findPlace.isPresent()) {
// 2. Place가 존재하면, AreaCode와 CityCode를 조회하여 변경사항 비교
AreaCode areaCode = areaCodeService.findByAreaCode(
cityCodeService.findAreaCodeByCityCode(
findPlace.get().getCityCode().getCityCode()
).orElseThrow(...)
).orElseThrow(...);
// 3. 변경된 내용이 있으면 업데이트
...
} else {
// 4. Place가 존재하지 않으면 AreaCode와 CityCode를 조회하여 새로 저장
AreaCode areaCode = areaCodeService.findByAreaCode(placeItem.getAreacode())
.orElseThrow(...);
CityCode cityCode = cityCodeService.findByCityCodeAndAreaCodeId(
placeItem.getSigungucode(), areaCode.getAreaCodeId()
).orElseThrow(...);
placeService.save(...);
}
}
}
placeItems 개수가 N개라고 할 때,
매 placeItem마다 3번의 select 쿼리가 발생한다.
따라서 전체 쿼리 수는 O(N) x O(3N) 이다.
하지만, 쿼리당 네트워크 I/O 및 DB 접근 시간이 고정으로 존재하기 때문에,
결국 전체 처리 시간은 선형적으로 오래 걸리게 된다.
현실적인 문제
예를 들어 총 50000개의 데이터를 저장해야된다면,
총 3번 × 50000 = 약 15만 번의 DB 왕복 쿼리 발생
실제로 처리 시간이 32분이나 소요되었다...
개선된 코드 주요 구조
Map<String, Place> existingPlaces = placeService.findAllByContentIdIn(contentIds)
.stream()
.collect(Collectors.toMap(Place::getContentId, Function.identity()));
Map<String, AreaCode> areaCodeMap = areaCodeService.findAll()
.stream()
.collect(Collectors.toMap(AreaCode::getAreaCode, Function.identity()));
Map<String, List<CityCode>> cityCodeMap = cityCodeService.findAll()
.stream()
.collect(Collectors.groupingBy(cityCode -> cityCode.getAreaCode().getAreaCode()));
이후 for문 구조
for (PlaceResponse.PlaceItem placeItem : tourSpotItems) {
Place existingPlace = existingPlaces.get(placeItem.getContentid());
AreaCode areaCode = areaCodeMap.get(placeItem.getAreacode());
CityCode cityCode = cityCodeMap.getOrDefault(placeItem.getAreacode(), List.of())
.stream()
.filter(c -> c.getCityCode().equals(placeItem.getSigungucode()))
.findFirst()
.orElse(null);
// 이후 저장 또는 수정
}
초기에 Place, AreaCode, CityCode 조회: 각각 1번의 select (총 3번)
for문 순회는 순수 메모리 연산
Map 조회는 O(1) 시간복잡도
따라서 전체 쿼리 수는 O(N) + O(3N) 이다.
전체 처리 시간: O(N) (메모리 접근만)
DB 왕복 쿼리: O(3) (초기 한번씩만)
기존: 32분 소요 (DB 왕복 15만 번)
개선: 약 2~3분 소요 (DB 왕복 3번)
데이터량이 많은 경우, DB에서 자주 읽지 말고 미리 캐싱하자.
Map을 이용하면 O(1)로 빠르게 데이터 접근이 가능하다.
Stream과 Collectors를 이용해 초기 메모리 세팅을 깔끔하게 할 수 있다.