본문 바로가기
공부/성능개선

[ParkNav] 검색 성능 개선 기록

by 얼빵이 2023. 4. 19.
반응형

성능 개선 결과

최초검색 : 13724 ms
조회로직 변경 : 7847ms
Fetch Join 변경 : 319ms
조회 쿼리 변경 : 256ms

기본 검색 기능

기본적인 주차장 검색기능을 구현했다.

검색어로 검색

  • 카카오맵 API로 사용자 검색어에 대한 결과를 호출
    • 검색 결과가 있는경우 : 검색결과 0번으로 좌표를 반환
    • 검색 결과가 없는경우 : 초기위치 ( 서울역 ) 반환
  • 좌표 주변의 주차장 검색
    • 검색 결과가 있는경우 : 검색 결과를 반환
    • 검색 결과가 없는경우 : 초기위치 ( 서울역 ) 반환

현위치로 검색

  • 현위치 정보 동의 여부
    • 동의 : 현위치 좌표 주변 DB검색
    • 비동의 : 검색실패 위치동의 알림 창 출력
  • 검색결과가 있는지 여부
    • 검색 결과가 있는 경우 : 주변 주차장 리스트 반환
    • 검색 결과가 없는 경우 : 초기위치 ( 서울역 ) 반환

키워드와 일치하는 결과 반환

  • API에서 정확히 검색어가 일치 하는경우 해당 좌표를 반환하게 변경
  • 해당 좌표 주변의 주차장 결과만 반환을 하다보니 검색 중심 위치가 어디인지 확인이 되지않음
    • 검색 중심 위치의 좌표를 같이 반환하여 해당 문제 해결

검색어로 검색

  • 카카오맵 API로 사용자 검색어에 대한 결과를 호출
    • 검색 결과가 있는경우 : 검색결과 0번으로 좌표를 반환
      • 주차장 명이 정확히 일치한다면 해당 좌표 반환
    • 검색 결과가 없는경우 : 초기위치 ( 서울역 ) 반환
  • 좌표 주변의 주차장 검색
    • 검색 결과가 있는경우 : 검색 결과를 반환 + 검색 위치 좌표 반환
    • 검색 결과가 없는경우 : 초기위치 ( 서울역 ) 반환

현위치로 검색

  • 현위치 정보 동의 여부
    • 동의 : 현위치 좌표 주변 DB검색
    • 비동의 : 검색실패 위치동의 알림 창 출력
  • 검색결과가 있는지 여부
    • 검색 결과가 있는 경우 : 주변 주차장 리스트 반환 + 검색 위치 좌표 반환
    • 검색 결과가 없는 경우 : 초기위치 ( 서울역 ) 반환

키워드 유사도 도입

문제점

  • 카카오 API에 검색한 결과만 검색이 가능하다
  • DB에 저장되어있는 주차장명은 검색이 불가능하다
  • 주차장명으로 검색 할 경우 엉뚱한 결과를 반환하기도 한다
  • 문제점
    • 아래와 같이 서귀포 스타벅스를 검색

  • 검색해서 나온 결과인 스타벅스 서귀포DT 주차장을 재검색

  • 엉뚱한 결과인 스타벅스 제주성산DT점 주차장이 나오게됨
  • 스타벅스 서귀포 DT주차장으로 검색했을때 0번째 리턴받은 JSON
  • { "address_name":"제주특별자치도 서귀포시 성산읍 고성리 238", "category_group_code":"PK6", "category_group_name":"주차장", "category_name":"교통,수송 \u003e 교통시설 \u003e 주차장", "distance":"", "id":"1648733158", "phone":"", "place_name":"스타벅스 제주성산DT점 주차장", "place_url":"http://place.map.kakao.com/1648733158", "road_address_name":"", "x":"126.920615213766", "y":"33.4497082943452" }

해결방법

  • 검색어 유사도를 도입해 유사도가 가장 높은 항목의 좌표를 반환
  • 키워드 검색 시 기존에 카카오 API만 검색하던 부분을 DB의 주차장 명칭도 포함
  • 해결
    • 코드에 키워드 유사도 로직 추가
    • if (parkSearchRequestDto.getKeyword() != null) { //카카오 검색 API호출 KakaoSearchDto kakaoSearchDto = kakaoMapService.getKakaoSearch(parkSearchRequestDto.getKeyword()); if (kakaoSearchDto.getMeta().getTotal_count() > 0) { List<KakaoSearchDocumentsDto> kakaoSearchDocumentsDto = kakaoSearchDto.getDocuments(); lo = kakaoSearchDocumentsDto.get(0).getX(); la = kakaoSearchDocumentsDto.get(0).getY(); placeName = kakaoSearchDocumentsDto.get(0).getPlace_name(); // 0번 결과로 유사도 초기화 similarScore = similarKeyword(parkSearchRequestDto.getKeyword(), kakaoSearchDocumentsDto.get(0).getPlace_name()); for (KakaoSearchDocumentsDto kakao : kakaoSearchDocumentsDto) { // 검색결과 별 유사도 비교 searchSimilarScore = similarKeyword(parkSearchRequestDto.getKeyword(), kakao.getPlace_name()); if (searchSimilarScore < similarScore) { lo = kakao.getX(); la = kakao.getY(); placeName = kakao.getPlace_name(); similarScore = searchSimilarScore; } } } // DB에서 Like검색 List<ParkInfo> parkInfos = parkInfoRepository.findByNameContains(parkSearchRequestDto.getKeyword()); for (ParkInfo parkInfo : parkInfos) { // 검색결과 별 유사도 비교 searchSimilarScore = similarKeyword(parkSearchRequestDto.getKeyword(), parkInfo.getName()); if (searchSimilarScore < similarScore) { la = parkInfo.getLa(); lo = parkInfo.getLo(); placeName = parkInfo.getName(); similarScore = searchSimilarScore; } } //결과가 없을경우 리턴 if (placeName == null) { return parkSearchResponseDto; } public static int similarKeyword(String userKeyword, String keyword) { return LevenshteinDistance.getDefaultInstance().apply(userKeyword, keyword); }
    • 스타벅스 서귀포 검색 시 결과
- 스타벅스 서귀포DT 주차장 검색 결과

![](https://velog.velcdn.com/images/clickyour/post/de365ff6-35eb-486a-b02f-8677cf705224/image.png)


- 사용자 화면에서도 정상적으로 확인이 된다

![](https://velog.velcdn.com/images/clickyour/post/7a00d586-1425-44d3-bb03-1f8555a87a8c/image.png)

검색 시간이 오래 걸리는 이슈

  • 기존에는 검색 속도가 매우 빨랐으나 서비스 로직을 추가하며 어느순간부터 오랜시간이 걸림
  • 서울특별시청을 검색할경우 검색 결과가 약 13초 후에 출력이 됨

문제점

  • 검색 시 수많은 쿼리가 발생됨 ( 검색지역에 주차장이 많을수록 비례 )
  • 많은 쿼리가 발생되는 지점을 분석해본 결과 새롭게 추가 된 현재 예약가능 구획 수 기능에서 수많은 쿼리를 발생시킴
  • result = parkInfoRepository.findParkInfoWithOperInfoAndTypeQueryDsl(lo, la, 2, ParkType.fromValue(parkSearchRequestDto.getType())); for (ParkOperInfo park : result) { String available; // 현재 운영여부 확인 if (OperationChecking.checkOperation(LocalDateTime.now(), park)) { // 현재 주차 가능 대수 = 주차 가능 대수 - 출차시간이 없는 현황 수(주차중인 경우) available = (park.getCmprtCo() - parkMgtInfoRepository.countByParkInfoIdAndExitTimeIsNull(park.getParkInfo().getId())) + "대"; } else { // 운영중이 아니라면 메시지 출력 available = MsgType.NOT_OPEN_NOW.getMsg(); } ParkOperInfoDto parkOperInfoDto = ParkOperInfoDto.of(park, ParkingFeeCalculator.calculateParkingFee(parkSearchRequestDto.getParktime() * 60L, park), available); if (parkOperInfoDto.getTotCharge() <= parkSearchRequestDto.getCharge()) { parkOperInfoDtos.add(parkOperInfoDto); } }
  • 주변 주차장을 조회하는 쿼리에서 Join이 되어있어 주차장 갯수에 비례한 쿼리가 생성됨
  • @Override public List<ParkOperInfo> findParkInfoWithOperInfoAndTypeQueryDsl(String x, String y, double distance, String type) { Double longitude = Double.parseDouble(x); Double latitude = Double.parseDouble(y); BooleanBuilder builder = new BooleanBuilder(); builder.and(Expressions.booleanTemplate("ST_Distance_Sphere(Point({0}, {1}), Point({2}, {3})) < {4}", longitude, latitude, qParkInfo.lo, qParkInfo.la, 2000)); if (!type.equals("전체")) { builder.and(qParkOperInfo.parkCtgy.eq(type)); } return jpaQueryFactory.selectFrom(qParkOperInfo) .join(qParkOperInfo.parkInfo, qParkInfo) .where(builder) .orderBy(Expressions.stringTemplate("ST_Distance_Sphere(Point({0}, {1}), Point({2}, {3}))", longitude, latitude, qParkInfo.lo, qParkInfo.la).asc()) .fetch(); }

해결

  • 현재 예약가능 구획 수
    • 로직을 따로 API를 분리하여 주차장을 눌렀을때 호출하게 변경하여 사용자가 확인하는 주차장만 조회하게 변경
    • 적용 후 약 7.8초 소요되게 변경

  • 주변 주차장 조회 쿼리
    • 기존로직에 Fetch Join을 적용하여 여러번 조회하던 부분을 한번에 확인가능하게 변경
    • 적용 후 약 0.3초 소요되게 변경

  • 추가 작업
    • 기존 쿼리는 ST_Distance_Sphere를 이용하여 거리를 계산했는데 176ms가 소요되었다
- 거리 계산 공식을 적용하여 측정했을때는 84ms로 50%정도 더 빠른 효율을 보여 QueryDSL에 해당 쿼리를 적용시켰다

 

결과

  • 변경 전 서울특별시청을 검색했을때 소요 시간 : 13724ms
  • 변경 후 서울특별시청을 검색했을때 소요 시간 : 256ms
  • 성능 개선률 : 98.13%

반응형

댓글


TOP

TEL. 02.1234.5678 / 경기 성남시 분당구 판교역로