결론 부터 이야기 하면
(측정 Jmeter)
개선 전
모든 자료 불러옴 -> 해시태그 해당되는지 전부 검색 O(N)
개선 후
SQL 쿼리개선을 통해 카카오맵에서 선택한 위도 경도의 5km 이내의
해당데이터만 불러옴 -> 해시태그 O(1) 검색
MAX 값에서 2배가 차이 났고 50%의 성능 개선 효과를 얻을 수 있었다.
내가 진행했던 테오의 스프린트 15기
우리는 멋진 길거리 사진을 소개하는 프로젝트를 만들었다.
게시글 내용은 이런식으로 보이고 태그기능을 사용하여 게시물을 검색할 수 있었다.
API 동작 과정
1. 웹에서 카카오맵을 통해 위치 찍고 (위도 ,경도 기반 검색)
2. 보고싶은 태그를 선택하면 게시물이 보이게 됨
하지만 개발 기간은 스프린트 형식으로 진행되기 때문에 2일.. 짧은 기간동안 태그 기능을 간단히 구현하고 싶었다.
또한 백앤드는 한명..(본인)
그래서 비트마스킹으로 구현해보면 어떨까 라는 생각에 의견을 제시했고 결국 프론트분들을 설득시켜 성공하였다..
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
export const tagList: Tag[] = [
{ id: 0, name: "편안한" } as Tag,
{ id: 1, name: "조용한" } as Tag,
{ id: 2, name: "친근한" } as Tag,
{ id: 3, name: "활기찬" } as Tag,
{ id: 4, name: "새로운" } as Tag,
{ id: 5, name: "오래된" } as Tag,
{ id: 6, name: "이국적인" } as Tag,
{ id: 7, name: "숨은명소" } as Tag,
{ id: 8, name: "커피명소" } as Tag,
{ id: 9, name: "야경명소" } as Tag,
{ id: 10, name: "자연경관" } as Tag,
{ id: 11, name: "반려동물과 함께" } as Tag,
];
|
cs |
태그는 프론트에서 이런식으로 구현되어있고
당연히 11개의 값이라 11개의 비트를 사용해서 구현하였다!. 모든 비트가 켜져있으면 2^11 = 2048
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class UserPost extends BaseTimeEntity{
@Id
@Column(name="post_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ColumnDefault("0")
private int viewCount;
@Size(min = 2, max = 250,message = "내용은 2~500자 사이로 입력해주세요.")
private String content;
private String detailImageUrl;
private String thumbnailImageUrl;
private Double lat;
private Double lng;
private String address_name;
private String tags;
private Long tagsNum;
private String profileImage;
private String music_id;
}
|
cs |
데이터는 이렇게 구성되어있고 tags는 비트형식으로 String 으로 변환하여 프론트에게 전달된다.
만약 조용한 활기찬이 체크되어있다면 01010000000 (ID 1번과 3을 1로 표기 ,앞에서부터 체크)
tagsNum 은 켜져있는 비트를 정수로 전환한 값이다
예를 들어 00000000011 이면 정수값은 3 인것처럼 미리 계산해서 넣어놓았다.
@Operation(summary = "주변 게시물 찾기 API")
@GetMapping("/find-near-post/{lat}/{lng}/{tags}")
public Slice<PostsDto> getBoardListFromUserSearch(@PageableDefault(size = 6,sort = "createdDate",direction = Sort.Direction.DESC) @Parameter(hidden = true) Pageable pageable
, @PathVariable Double lat, @PathVariable Double lng, @PathVariable String tags) {
List<UserPost> userPostData= userPostService.getUserPostList();
List<UserPost> userPostList = new ArrayList<>();
Long tags_num=convertTags(tags);
for (int i=0;i<userPostData.size();i++){
if (getDistance(lat,lng,userPostData.get(i).getLat(),userPostData.get(i).getLng())<=5
&& ((tags_num&userPostData.get(i).getTagsNum()))==tags_num){
userPostList.add(userPostData.get(i));
}
}
return getUserPostsDto(pageable,userPostList);
}
사용자가 게시물을 찾을때 태그를 체크한거 & 연산을 통해 비트가 켜져있는지
확인하여 미리 정수로 계산해놓은 tagsNum 변수와 & 연산해서 tagsNum과 같으면
태그된 게시물을 불러 올수 있도록 하였다.
잘 작동되는 모습...
하지만!!
생각해보니 일단 모든 게시글을 불러오고 해시태그 관련 게시글을 찾는게 너무 불필요하다고 생각했다.
(무조건 전체 게시글을 가져와야하니)
그래서 데이터베이스 쿼리로 처리하면 더 빠르지 않을까? 라고 생각했다
찾아보니
ST_Distance_Sphere
라는 근처 게시글을 검색하는 SQL 함수가 있었고 이를 통해서 더욱 개선할수 있었다.
ST_DISTANCE_SPHERE(point(lon1, lat1), point(lon2, lat2))
두 지점 사이의 거리를 미터 단위로 반환한다.
@Query(value = "SELECT * FROM user_post u WHERE " +
"ST_Distance_Sphere(POINT(u.lng, u.lat), POINT(:lng, :lat)) <= 5000 AND " +
"(u.tags_num & :tagsNum) = :tagsNum",nativeQuery = true)
List<UserPost> findNearPosts(@Param("lng") Double lng,
@Param("lat") Double lat,
@Param("tagsNum") Long tagsNum);
리포지토리에 쿼리를 추가했다.
@Operation(summary = "주변 게시물 찾기 API")
@GetMapping("/find-near-post2/{lat}/{lng}/{tags}")
public Slice<PostsDto> getBoardListFromUserSearch(@PageableDefault(size = 6,sort = "createdDate",direction = Sort.Direction.DESC) @Parameter(hidden = true) Pageable pageable
, @PathVariable Double lat, @PathVariable Double lng, @PathVariable String tags) {
Long tagsNum = convertTags(tags);
List<UserPost> userPostData = userPostService.getNearPostList(lng, lat, tagsNum);
List<UserPost> userPostList = new ArrayList<>();
for (int i=0;i<userPostData.size();i++){
if (getDistance(lat,lng,userPostData.get(i).getLat(),userPostData.get(i).getLng())<=5
&& ((tagsNum&userPostData.get(i).getTagsNum()))==tagsNum){
userPostList.add(userPostData.get(i));
}
}
return getUserPostsDto(pageable,userPostList);
}
코드도 이런식으로 개선할수 있었다.
Jmeter를 통해서 테스트를 해보았다.
개선 전
모든 자료 불러옴 -> 해시태그 O(N) 검색
개선 후
SQL 쿼리개선을 통해 카카오맵에서 선택한 위도 경도의 5km 이내의
해당데이터만 불러옴 -> 해시태그 O(1) 검색
MAX 값에서 2배가 차이 났고 50%의 성능 개선 효과를 얻을 수 있었다.