카테고리 없음

[스프링 부트] 비트 마스킹 사용해서 데이터 처리하기

컴공코딩러 2024. 11. 23. 13:11

 

 

결론 부터 이야기 하면

 

(측정 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: 0name"편안한" } as Tag,
  { id: 1name"조용한" } as Tag,
  { id: 2name"친근한" } as Tag,
  { id: 3name"활기찬" } as Tag,
  { id: 4name"새로운" } as Tag,
  { id: 5name"오래된" } as Tag,
  { id: 6name"이국적인" } as Tag,
  { id: 7name"숨은명소" } as Tag,
  { id: 8name"커피명소" } as Tag,
  { id: 9name"야경명소" } as Tag,
  { id: 10name"자연경관" } as Tag,
  { id: 11name"반려동물과 함께" } 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%의 성능 개선 효과를 얻을 수 있었다.