랜덤 데이터 조회

213kky·2024년 12월 13일

commenting 프로젝트에서 기존에는 광역질문을 통해 해당 광역질문에 질문에 답을 남긴 사용자의 프로필을 타고 들어가는 방식을 사용하여 다른 사용자에게 질문을 남기는 방식으로 구현하기로 되어있었다.

하지만 광역질문을 타고 다른 사람의 프로필에 접근하는 방식에는 여러 문제가 있었다.

  1. 최초 회원가입을 하게되면 광역질문이 없고 따라서 가입을 했는데 다른 사용자에게 질문을 하지 못해 서비스를 사용하지 못하는 상황이 발생한다.
  2. 광역질문이 있더라도 다른 사람의 프로필에 접근하는게 비효율적이다.

이러한 이유로 다른 사람의 프로필에 접근할 수 있는 방식이 뭐가 있을까 생각하다가 가입한 유저들을 메인페이지에 랜덤으로 뿌려주면 좋을 것 같다고 생각했다.

마침 메인페이지가 활용되지 못하고 있어 메인페이지에 이러한 내용을 담기에 좋아보여 구현을 해보기로 하였다.


랜덤을 구현하기 위해 Random 라이브러리에 대해 검색하던 중 ThreadLocalRandom 라이브러리를 알게되어 ThreadLocalRandom를 사용하여 구현하였다.

Tip
Random 라이브러리를 사용하면 AtomicLong seed가 공유자원이기에 여러 스레드에서 동시에 읽고 변경할 수 있는 문제가 발생하기 때문에 Thread자체 변수인 Local값을 활용하는 ThreadLocalRandom 을 사용해 문제를 방지해야 한다고 한다.


@Transactional(readOnly = true)
public List<MemberInfoResponse> getRandomMembers() {
	long num = memberInfoRepository.count();

	// 주어진 범위에서 랜덤 값 10개 뽑기
    List<Long> randomValues = ThreadLocalRandom.current()
                    .longs(1, num + 1)  // 범위: 1 ~ num (num을 포함)
                    .limit(10)
                    .boxed()
                    .toList();

	List<MemberInfo> memberInfos = memberInfoRepository.findAllById(randomValues);

	return memberInfos.stream()
                .map(e -> new MemberInfoResponse(e.getId(), e.getAvatarPath()))
                .toList();
}

메인 페이지에 랜덤의 회원을 보여주기 위한 처음작성한 코드이다.
위 코드에는 아래와 같은 문제점이 있었다.

  1. randomValues에 값이 중복으로 들어간다.
    - randomValues에 중복으로 값이 들어가더라도 findAllById에서 in절을 사용하므로 중복된 값은 무시하고 반환된다. 하지만 회원의 수가 10명이 넘더라도 예상했던 랜덤값 10개가 아닌 중복된 만큼 줄어든 결과를 반환하게 된다.
  2. num값으로 회원 전체 범위를 만족하지 않는다.
    - 논리 삭제 또는 물리삭제가 있다면 해당되는 만큼 데이터의 수가 적어져 전체 회원에서 랜덤 조회가 아니게된다

값이 중복으로 반환되지 않도록 전체 회원의 id값을 조회하였다.(전체 회원의 전체 데이터 조회보다는 성능적으로 조금 낫다.)
이후 list값을 섞어 앞에서 10개를 뽑도록 하여 10명의 회원데이터를 조회하도록 아래와 같이 수정하였다.

@Transactional(readOnly = true)
public List<MemberInfoResponse> getRandomMembers() {
	List<Long> ids = memberInfoRepository.findAllIds();
    Collections.shuffle(ids, ThreadLocalRandom.current());

	ids = ids.stream()
    		.limit(10)
            .toList();

	List<MemberInfo> memberInfos = memberInfoRepository.findAllById(ids);

	return memberInfos.stream()
                        .map(MemberInfoResponse::from)
                        .toList();
}

사실 회원의 숫자가 매우 많다면 위 방식으로 진행하면 성능상에 문제가 있을 수 있다.
메인 페이지이기 때문에 가장 많이 조회가 되는 API인데 모든 회원의 정보를 매번 조회하기 때문에 캐싱을 이용하여 데이터를 조회하는 방식으로 변경해 봐야 할 것 같다.


참고
ThreadLocalRandom을 활용한 성능 개선기
Random 대신 ThreadLocalRandom을 써야 하는 이유

profile
since 2022

0개의 댓글