-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BE] Feat/#548 공간 인덱스 구현 #584
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
설명이 필요한 부분 코멘트 남겨두었습니다.
+ " * cos( radians( l.coordinate.longitude ) - radians(:#{#current_coordinate.longitude}) ) " | ||
+ " + sin( radians(:#{#current_coordinate.latitude}) ) " | ||
+ " * sin( radians( l.coordinate.latitude ) ) ) ) <= :distance" | ||
+ "WHERE ST_Contains(ST_Buffer(:coordinate, :distance), l.coordinate.coordinate)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ST_Contains(ST_Buffer(:coordinate, :distance), l.coordinate.coordinate)
이 부분 설명드릴게요.
ST_Buffer(A,B) 함수는 다음과 같습니다.
A는 특정 좌표를 나타내고, B는 거리를 나타냅니다. 이때 단위는 Meter이빈다.
A의 좌표로부터 B 거리 내에 존재하는 MBR을 반환해주는데요.
A의 좌표로부터 B 거리 내를 나타내는 것은 원이겠죠 ?
MBR은 이를 감싸는 사각형이라고 생각하시면 됩니다.
ST_Contains(A, B) 함수는 A라는 MBR(Minimum Bounding Rectangle)안에 B가 존재하는지 여부를 판단합니다.
즉, 위에서 이야기한 MBR 내에 존재하는 좌표 값들을 조회한다고 생각하시면 됩니다.
두 함수 모두 MySQL에서 지원하는 함수입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네 맞아요. 그래서 명확한 계산을 하기 위해서는 ST_DISTANCE
함수를 활용해야하는데, MySQL에서는 해당 함수를 사용할 때, 인덱싱을 처리해주지 않아요.
�
우리 서비스가 엄청 정확한 거리계산을 필요로하지 않는다는 점과, 실제 테스트해보지 않았지만 찾아본 결과 20배 가량의 성능 향상 결과가 나타난다는 점에서 위와 같은 결정을 내리게 되었어요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
멋집니다~!
mySQLContainer.start(); | ||
} | ||
|
||
@DynamicPropertySource |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동적으로 설정파일을 수정해줍니다 ~
컨테이너가 뜰때마다 포트번호가 다르다보니, url이 달라지잖아요 ?
그래서 사용합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지렸네용
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@Target(ElementType.TYPE) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Import(JpaConfig.class) | ||
@DataJpaTest | ||
@AutoConfigureTestDatabase(replace = Replace.NONE) | ||
public @interface RepositoryTest { | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
테스트 컨테이너를 사용하는데, @DataJpaTest
를 사용하면 내부적으로 InMemory H2 데이터베이스를 사용합니다.
이로 인해, mysql 에서 지원해주는 함수를 사용함으로 인해 예외가 발생합니다.
그래서 데이터베이스를 바꾸지 않는다는 어노테이션(@AutoConfigureTestDatabase(replace = Replace.NONE)
을 사용해야 하는데요.
모든 테스트에서 이러한 작업을 해주기보단, Repository 계층 테스트를 위한 어노테이션을 구현했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오호 그렇군요ㅎ 고생하셨네용
private Coordinate(double latitude, double longitude) { | ||
this.latitude = latitude; | ||
this.longitude = longitude; | ||
this.coordinate = geometryFactory.createPoint(new org.locationtech.jts.geom.Coordinate(longitude, latitude)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ클래스명이랑 겹쳐서 org.xxxx 나오는데.. 이거 어떻게하죠 ㅠ_ㅠ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 그냥 놔두는 거 한 표요!
패키지명이 나와서 지저분해보이긴 하지만 .. 오히려 저게 없으면, 코드를 읽을 때 어떤 Coordinate의 생성자인지 헷갈릴 것 같아서요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
쥬니짱 너어어무우우 고생하셨어요~!
단 코멘트 외에 질문이 하나 있어요!
모든 테스트에 TestDatabaseContainer 를 붙이신 이유가 있을까용??
제가 AtlasCommandService 에서 상속을 지워보니까 테스트가 안터져서용!
backend/build.gradle
Outdated
@@ -29,6 +29,7 @@ dependencies { | |||
implementation 'org.springframework.boot:spring-boot-starter-validation' | |||
implementation 'org.springframework.boot:spring-boot-starter-webflux' | |||
implementation group: 'com.github.maricn', name: 'logback-slack-appender', version: '1.6.1' | |||
implementation group: 'org.hibernate', name: 'hibernate-spatial', version:'6.2.5.Final' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
허허허허 logback 도 적용이 안되어 있긴 하지만 org.hibernate:hibernate-spatial:6.2.5.Final
이런 식으로 명시하여 다른 implementation 과 형식을 동일하게 가져가면 어떨까용?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
반영했어요~!
@@ -44,7 +44,7 @@ public List<TopicResponse> findNearbyTopicsSortedByPinCount( | |||
) { | |||
Coordinate coordinate = Coordinate.of(latitude, longitude); | |||
List<Location> nearLocation = locationRepository.findAllByCoordinateAndDistanceInMeters( | |||
coordinate, | |||
coordinate.getCoordinate(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
간지나네용
/* | ||
* 4326은 데이터베이스에서 사용하는 여러 SRID 값 중, 일반적인 GPS기반의 위/경도 좌표를 저장할 때 쓰이는 값입니다. | ||
* */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오호 그래서 SRID 값이 4326 이었던 거군용
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일종의 식별코드 같은 것이로군요
@@ -14,7 +14,7 @@ | |||
public class DatabaseCleanup implements InitializingBean { | |||
|
|||
private static final String TRUNCATE_SQL_MESSAGE = "TRUNCATE TABLE %s"; | |||
private static final String SET_REFERENTIAL_INTEGRITY_SQL_MESSAGE = "SET REFERENTIAL_INTEGRITY %s"; | |||
private static final String SET_REFERENTIAL_INTEGRITY_SQL_MESSAGE = "SET FOREIGN_KEY_CHECKS = %s"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 MYSQL 로 변경되어서 그런건가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넹 H2랑 문법이 다르니까여
mySQLContainer.start(); | ||
} | ||
|
||
@DynamicPropertySource |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지렸네용
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
쥬니 공간 인덱스 멋지게 잘 구현해주셨네요! 수고하셨습니당
저도 궁금했던 점들을 매튜가 질문 잘 달아주셔서,
저는 간단한 변경 제안 몇 개 달았는데요. (P1 1개, P2 1개)
반영해주실 걸 알고 미리 Approve 드립니다 ^.^
@Column(columnDefinition = "Decimal(18,15)") | ||
private double longitude; | ||
@Column(columnDefinition = "geometry SRID 4326", nullable = false) | ||
private Point coordinate; | ||
|
||
private Coordinate(double latitude, double longitude) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private Coordinate(double latitude, double longitude) { | |
private Coordinate(final Point point) { | |
this.coordinate = point; | |
} | |
public static Coordinate of(double latitude, double longitude) { | |
validateRange(latitude, longitude); | |
final Point point = geometryFactory.createPoint(new org.locationtech.jts.geom.Coordinate(longitude, latitude)); | |
return new Coordinate(point); | |
} |
P2. 이렇게 해서 생성 로직을 정적 팩터리 메서드에서 확인할 수 있는 것은 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(file change 부분이 아닌 라인이 있어서 코멘트 위치가 애매하게 달아졌어요)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
맞네여.. 뇌 빼고 했어요 죄송합니다
); | ||
@Param("coordinate") Point coordinate, | ||
@Param("distance") double distance); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1. 개행!!
mySQLContainer.start(); | ||
} | ||
|
||
@DynamicPropertySource |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -44,6 +45,8 @@ dependencies { | |||
testImplementation 'io.rest-assured:rest-assured' | |||
testImplementation 'io.rest-assured:spring-mock-mvc' | |||
testImplementation 'org.assertj:assertj-core:3.19.0' | |||
testImplementation 'org.testcontainers:mysql:1.17.2' | |||
testImplementation 'org.testcontainers:junit-jupiter:1.17.2' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오홍 testContainer 전용으로 junit 의존성도 추가로 필요한가보군요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
예쓰 !! junit에서 제공해주는 것 같더라구요
/* | ||
* 4326은 데이터베이스에서 사용하는 여러 SRID 값 중, 일반적인 GPS기반의 위/경도 좌표를 저장할 때 쓰이는 값입니다. | ||
* */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일종의 식별코드 같은 것이로군요
public double calculateDistanceInMeters(Coordinate otherCoordinate) { | ||
double earthRadius = 6_371_000; | ||
public double getLatitude() { | ||
return coordinate.getY(); | ||
} | ||
|
||
return acos(sin(toRadians(otherCoordinate.latitude)) * sin(toRadians(this.latitude)) + ( | ||
cos(toRadians(otherCoordinate.latitude)) * cos(toRadians(this.latitude)) | ||
* cos(toRadians(otherCoordinate.longitude - this.longitude)) | ||
)) * earthRadius; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
실제로 쓰이지 않고 있는 메서드를 삭제하고 테스트도 함께 지워주셨군요!
확인했습니다 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kpeel5839 근데, 일종의 통일성을 맞추기 위함과 실제 우리 서비스가 MySQL 환경에서 돌아간다는 점에서 일괄적으로 적용했어요. |
작업 대상
📄 작업 내용
핀을 저장할 때, 데이터를 효율적으로(?) 저장하기 위해, 특정 반경내에 좌표가 존재하는지 확인합니다.
기존에는 함수를 활용했기 때문에, 인덱스를 사용할 수 없었습니다.
MySql에서 제공하는 함수를 통해, 공간 인덱스를 적용하였습니다.
어느정도의 성능 이점을 가져오는지는 블로그 글 작성시에 남겨두도록 하겠습니다.
JPA상에서는 적용해두었으나, 실제 개발 및 운영 환경의 DB에는 공간 인덱스가 적용되지 않은 상태입니다.
이에따라, 추가적으로 필요한 작업은 아래와 같습니다.
create spatial index location_idx01 on location(coordinate);
longitude(double)
,lattitude(double)
->coordinate(geometry)
로 변경테이블 자체에 SRID 값이 0으로 자동 설정될 수 있는데, 이는 명시적으로
4326
값을 사용하도록 수정해야함.🙋🏻 주의 사항
LocationRepository에서 JPQL을 mysql에서 지원해주는 함수로 변경하였는데요.
이에따라, 기존 H2 DB에서 mode=MySQL로 설정하더라도 동작하지 않는 이슈가 있었습니다.
이에따라, 테스트를 수행할 때 Dokcer를 실행시킨 뒤에 TestContainer를 띄워야합니다.
스크린샷
📎 관련 이슈
closed #548
레퍼런스