여행지 동기화 코드 성능 개선기: 빅오 분석과 Map 캐싱 최적화

Noah-wilson·2025년 4월 27일

개인프로젝트

목록 보기
4/4

1. 기존 코드 문제점

기존의 여행지 동기화 코드는 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(...);
    }
}

}

빅오(Big-O) 분석

placeItems 개수가 N개라고 할 때,

매 placeItem마다 3번의 select 쿼리가 발생한다.

따라서 전체 쿼리 수는 O(N) x O(3N) 이다.

하지만, 쿼리당 네트워크 I/O 및 DB 접근 시간이 고정으로 존재하기 때문에,
결국 전체 처리 시간은 선형적으로 오래 걸리게 된다.

현실적인 문제

예를 들어 총 50000개의 데이터를 저장해야된다면,

총 3번 × 50000 = 약 15만 번의 DB 왕복 쿼리 발생

실제로 처리 시간이 32분이나 소요되었다...

2. 개선 방법

개선 목표

  • DB 왕복 횟수를 극적으로 줄인다.
  • 메모리에서 빠른 연산이 가능한 구조를 만든다.

개선 아이디어

  • Place, AreaCode, CityCode를 초기에 모두 한번에 조회해서 메모리에 올린다.
  • 검색할 때마다 Map을 활용해 O(1) 시간복잡도로 빠르게 찾는다.

개선된 코드 주요 구조

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);
    // 이후 저장 또는 수정
}

빅오(Big-O) 분석

초기에 Place, AreaCode, CityCode 조회: 각각 1번의 select (총 3번)

for문 순회는 순수 메모리 연산

Map 조회는 O(1) 시간복잡도
따라서 전체 쿼리 수는 O(N) + O(3N) 이다.

최종 빅오

전체 처리 시간: O(N) (메모리 접근만)

DB 왕복 쿼리: O(3) (초기 한번씩만)

3. 결과

기존: 32분 소요 (DB 왕복 15만 번)

개선: 약 2~3분 소요 (DB 왕복 3번)

4. 결론

데이터량이 많은 경우, DB에서 자주 읽지 말고 미리 캐싱하자.

Map을 이용하면 O(1)로 빠르게 데이터 접근이 가능하다.

Stream과 Collectors를 이용해 초기 메모리 세팅을 깔끔하게 할 수 있다.

0개의 댓글