읽기에 앞서

  • 프로젝트를 진행하는 도중 무작위 랜덤 조회 기능이 필요하여 어떤 방식으로 사용하는지에 대해 공부한 내용을 정리를 하고자 합니다.

SQL에서의 랜덤 조회 방식

  • 이 포스팅에서의 원하는 내용이 아니므로 간단하게 정리하고 넘어간다.
  • 현재 DB로 사용하고 있는 MariaDB의 경우, 랜덤으로 필드를 조회하는 쿼리는 아래와 같다.
  • 이는 인덱스를 사용하지 못하여 데이터가 방대해질 수록 성능의 저하가 심한 듯 하다.
# 참고 블로그 [https://creds.tistory.com/158]

# 랜덤 데이터 조회
SELECT * FROM [TABLE] ORDER BY RAND();

# 랜덤으로 N개의 데이터 조회
SELECT * FROM [TABLE] ORDER BY RAND() LIMIT 1;

# ORDER BY 2 사용 
# DESC 먼저 수행 , 중복 데이터  하나를 랜덤으로 뽑아서 사용
SELECT * FROM [TABLE] ORDER BY COLUMN1 DESC, RAND() LIMIT 1;

JPA에서는?

그렇다면 방법이 없을까..?

  • ORDER BY RAND()의 성능 이슈 문제도 있고, 네이티브 쿼리를 사용하고 싶지 않아 방법을 계속 찾던 중, 유사한 방식의 조회법이 있다는 것을 아래의 게시글을 통해 알게 되었다.
  • StackOverFlow 참고
  • 위의 게시글을 참고하면 JPA의 페이징 기법을 활용하여 마치 랜덤으로 조회하는 듯한 효과를 낼 수 있다!


페이징을 활용하여 랜덤 조회를 구현해보자

  • 위의 게시글은 JPA를 바탕으로 구현돠었으나, 난 프로젝트의 환경에 맞게 QueryDSL을 활용하여 필요한 기능을 구현하고자 한다.
  • (페이징에 관한 포스팅은 추후에 작성 예정)
  • 구현하고자 하는 기능 : 특정 범위 내의 7개 매장 정보를 랜덤으로 조회

Repository 구현체

/*
	특정 범위 상위 7개 조회
*/
@Override
public List<SimpleSearchStoreDTO> searchTop7Random(Polygon<G2D> polygon, Pageable pageable) {
    final int dist = 3;
    
    return queryFactory
                .select(Projections.constructor(SimpleSearchStoreDTO.class,
                        store.id, store.name, store.lon, store.lat, store.phoneNumber,
                        store.status, store.businessTime, store.address, store.ratingTotal, store.file.uploadImageUrl
                )).distinct()
                .from(store)
                .where(stContains(polygon), stDistance(polygon).loe(dist))
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();
}

/*
	특정 범위 개수 조회
*/
@Override
public Long countStoreInPolygon(Polygon<G2D> polygon) {
    return queryFactory
        .select(store.count())
        .from(store)
        .where(store.file.uploadImageUrl.isNotNull())
        .where(stContains(polygon), stDistance(polygon).loe(3))
        .fetchOne();
}

  • QueryDSL의 사용법은 별도로 설명하지 않는다.

Service

public List<SimpleSearchStoreDTO> searchTop7Random(double lat, double lon, double dist) {
    final int size = 7; 			// 한 페이지 당 데이터 개수
    
    /*
    	범위 생성
    */ 
    Polygon<G2D> polygon = getPolygon(lat, lon, dist);

    /*
    	특정 범위 개수 조회
    */ 
    Long count = storeRepository.countStoreInPolygon(polygon);

    /*
    	참고 블로그 - https://mine-it-record.tistory.com/141
    	Math.random()을 활용하여 난수를 생성
   		+1을 해주면 0 ~ (count / n)까지의 값이 선택됨
   		아니면 0 ~ (count / n) - 1까지의 값이 선택됨
    */
    int pageNum = count % n != 0
                    ? (int) (Math.random() * (count / size + 1))
                    : (int) (Math.random() * (count / size));

    /*
    	페이징 정보를 넘겨줌
    	idx쪽의 n개 데이터를 세팅함
    */
    PageRequest pageRequest = PageRequest.of(pageNum, size);

    return storeRepository.searchTop7Random(polygon, pageRequest);
}
  • 다소 헷갈릴 수 있는 부분이 있는데 바로 idx의 값의 연산이다.
  • count가 n으로 나누어 떨어지지 않는 경우, 나머지가 존재한다는 의미이다.
  • 나머지 데이터들 또한 하나의 페이지에 담아줘야 하므로 페이지가 하나 더 필요해진다. 따라서 +1을 하여 난수 생성 범위를 1 늘려준다.

어떤 방식인가?

  • 전체 동작 방식은 아래와 같다
    1. 전체 데이터의 개수를 구하고 이를 원하는 데이터 크기만큼 나누어 전체 페이지 개수를 구함.
    2. Math.random()을 활용한 난수 생성을 이용하여 하나의 페이지 넘버를 구함.
    3. 이를 PageRequest에 사이즈와 함께 담아서 넘겨줌.

결론

  • 흡사 책을 들고 아무런 페이지나 열어보는 경우와 비슷합니다.
  • 데이터를 랜덤 조회한다기 보단 추출한 데이터들의 페이지를 랜덤 조회하는 것입니다..
  • 현재 제 프로젝트에선 완전한 랜덤 조회 기능은 필요없기에 위의 방식에 만족하고 알맞은 용도로 잘 사용하고 있으나, 100% 완벽하게 랜덤 데이터를 조회하는 방법이 아닙니다.
  • 현재로써 그 방법을 원하면 네이티브 쿼리나 jdbcTemplate을 사용해야 하는 것 같습니다.

댓글남기기