1135 words in content
12 minutes for read
MySQL의 공간 데이터를 사용한 조회 최적화

인식한 상황#

기존에는 walking_loads와 locations 테이블이 1대 N 관계로 구성되어 있었다. 산책로에 대응하는 location 수가 많아질 경우, 특정 지역(예: 현재 위치) 주변 1km 내 산책로를 조회하는 로직에서 다음과 같은 문제가 발생했다.

  1. 과도한 데이터 로드: 모든 산책로를 조회한 뒤 자바 코드상에서 거리 계산을 수행함으로써, 불필요한 엔티티를 대량으로 불러오는 비효율이 있었다.

  2. 성능 저하: 1000개의 더미 데이터 기준으로 API 응답 시간이 약 2초에 달할 정도로 비효율적이었다.

해결 과정#

이런 문제들을 해결하기 위해 공간형 데이터를 고려하게 되었고, MySQL에서 제공하는 Spatial 기능을 활용하기로 하였다.

JPA와 공간형 데이터 활용 이슈 극복#

MySQL의 Multi Point 타입을 이용해 산책로 위치를 효율적으로 관리하고자 했으나, JPA는 기본적으로 Point 형은 지원하나 Multi Point 형은 지원하지 않는다는 점이 문제가 되었다. 이를 해결하기 위해 hibernate-spatial 라이브러리를 도입했다. 위·경도 데이터를 Coordinate로 변환한 뒤 Geometry Factory를 사용하여 Point 객체를 생성한다. 이로써 Multi Point를 JPA 상에서 매핑할 수 있게 되어, DB 상의 공간형 컬럼과 엔티티 간 매핑 문제를 해결했다.

거리 계산 로직 DB 레벨로 이전#

MySQL이 제공하는 ST_Distance_Sphere 함수를 통해 특정 좌표로부터 반경 1km 내의 데이터만 필터링하려고 했다. 이를 통해 불필요한 엔티티 로드를 줄일 수 있었고, 초기 성능 개선을 이뤄 약 2초에서 200ms대로 단축하는 효과를 보았다.

기존에는 약 200줄에 달하는 자바 로직에서 거리 계산을 수행했다. 하지만 위를 DB 레벨로 옮겨서 성능을 개선하였다.

결과#

초기 개선만으로 약 2초에서 200ms로 단축되었다. 추가로 ST_Buffer와 ST_Contains를 통해 인덱스를 활용한다면, 더 많은 데이터 규모에서도 안정적인 성능을 유지할 수 있다. 기존 200줄에 달했던 로직을 DB 레벨의 GIS 함수 활용으로 단축, 유지보수성을 향상했다.

하지만 추가로 검색해 보니 ST_Distance_Sphere 함수는 Spatial Index를 지원하지 않고 Full Scan을 한다고 한다… 시간이 없어서 해보지는 못했지만 같은 방법을 사용할 수 있을 거 같다는 생각이 들었다. 만약 10만개의 데이터라면 더 유효한 결과가 나오지 않을까? 지금도 사실상 90% 정도 속도를 올릴 수 있었는데, 아래 방법을 추가로 적용하면 더 빨라지지 않을까 의견을 내본다…

  1. ST_Buffer:
    주어진 좌표(Point)와 반지름을 사용해 원형(Polygon) 영역을 생성한다.
    예: ST_Buffer(ST_GeomFromText(:standard_point,4326), 5000)
    이 로직은 주어진 좌표를 중심으로 반경 1km의 원형 영역(Polygon)을 만든다.

  2. ST_Contains:
    ST_Contains 함수를 통해 ST_Buffer로 생성한 Polygon 내부에 위치한 Point를 가진 Walking Load 엔티티만 필터링할 수 있다.
    예: ST_CONTAINS((ST_Buffer(ST_GeomFromText(:standard_point,4326), 5000)), walking_load_point)
    이를 통해 Polygon 영역 내에 포함되는 산책로만 조회하게 된다.

주의사항 해당 방법을 사용하기 위해서는 SRID가 Column에 적용 되어 있어야 한다!!