https://github.com/Gseungmin/playlist/pull/5
<aside>
플레이리스트에는 노래가 들어가야 한다.
하지만 플레이리스트에 노래를 직접 넣으면 같은 노래라도 플레이리스트마다 관리하 필요하다.
따라서 플레이리스트와 노래는 N : M 관계를 가지기에 이를 해소시켜줄 플레이리스트 아이템이라는 객체가 필요하다
따라서 플레이리스트 아이템을 생성해서 플레이 리스트와 음악과 연관관계를 맺워줘야 하는데 총 5번의 쿼리가 나간다.
public Music getMusic(
Long musicId
) {
Optional<Music> optional =
musicRepository.findById(musicId);
if (optional.isEmpty()) {
throw new PlayListException(
MUSIC_NOT_EXIST.getCode(),
MUSIC_NOT_EXIST.getErrorMessage()
);
}
return optional.get();
}
사용자가 보낸 음악아이디를 통해 해당 음악이 존재하는지 판단해야 한다.
만약 음악이 없을 경우 예외를 터뜨린다.
사용자가 저장하려고 하는 플레이리스트가 실제 존재하는지 확인해야 한다.
만약 플레이리스트가 없을 경우 예외를 터뜨린다.
또한 플레이리스트와 이를 수정하려는 회원 정보가 동일해야 한다.
@Query("""
select pl
from PlayList pl
where pl.id = :playListId
and pl.member.id = :memberId
""")
Optional<PlayList> findPlayListByPlayListId(Long playListId, Long memberId);
플레이리스트의 최대 카운트 수는 1000이다.
즉, 플레이리스트 속 음악의 수가 1000일 경우 더이상 추가하면 안된다.
따라서 count 쿼리를 통해 조회한다.
@Query("""
select count(pli)
from PlayListItem pli
where pli.playList.id = :playListId
and pli.playList.member.id = :memberId
""")
Long countItems(Long playListId, Long memberId);
플레이리스트의 최대 순서 값을 가지고 와야 한다.
플레이리스트의 각 음악은 순서 정보를 가지고 있고, 이때 마지막 순서의 순서 값이 필요하다.
이 순서값을 기반으로 이후 순서를 지정할 것이기 때문이다.
플레이리스트의 순서는 Gap Based Ordering 방식을 사용하므로 이에 대한 내용은 이 글에서 참고한다.
@Query("""
select coalesce(max(pli.position), 0)
from PlayListItem pli
where pli.playList.id = :playListId
and pli.playList.member.id = :memberId
""")
Long findMaxPosition(Long playListId, Long memberId);
이제 이 정보들을 토대로 만들어진 플레이리스트 아이템을 플레이리스트로 저장한다.
PlayListItem playListItem = new PlayListItem(
lastOrder + GAP,
playListStat.getPlayList(),
music
);
// 5️⃣ 플레이리스트 아이템 저장
playListItemRepository.save(playListItem);
</aside>
<aside>
2️⃣ 3️⃣ 4️⃣ 쿼리를 하나로 만들어보자
즉, 플레이리스트를 조회하면서 플레이리스트의 아이템의 수를 가지고 오고, 또한 플레이리스트 카운트도 한번에 가지고 오는 것이다.
여기서 핵심은 쿼리 자체가 조금 무거워지더라도 성능에서 효과적으로 가지고 올 수 있는지 체크하는 것이다.
@Query("""
select pl as playList,
count(pi) as count,
coalesce(max(pi.position), 0) as lastOrder
from PlayList pl
left join PlayListItem pi on pi.playList = pl
where pl.id = :playListId
and pl.member.id = :memberId
group by pl
""")
Optional<PlayListStatProjection> findPlayListWithStat(
Long playListId,
Long memberId
);
우리가 원하는 것을 쿼리에 명시한다.
기본적으로 지금같이 쿼리를 날리면 플레이리스트 아이템의 수만큼 플레이리스트 로우가 생성된다.
따라서 우리가 원하는 건 플레이리스트 한개 이므로, Group 화 시킨다.
</aside>
<aside>
플레이리스트가 10000개가 있고 각 플레이리스트 별 총 1000개의 음악을 가지고 있다고 가정하자
즉, 천만개의 데이터 셋에서 효과를 체크한다.
사용자는 5분간 5000명으로 점차 증가시키며 계속 요청을 보낸다. 또한 추가적으로 5분동안 5000명은 유지한다. 각 요청당 0.5ms 의 요청 제한을 둔다.
</aside>
<aside>
| 항목 | 값 |
|---|---|
| 총 요청 수 | 2,536,315 건 |
| 총 테스트 시간 | 10분 (601.1 초) |
| 평균 처리량 (Throughput) | 4,219.51 req/s |
| 평균 HTTP 요청 응답 시간 | 388.03 ms |
| 요청 실패율 | 0% (실패 없음) |
| </aside> |