Fivefy 프로젝트/트러블슈팅

Top N 제한을 DB로 이동하며 조회 성능 개선한 경험

sudaruuu 2026. 4. 28. 19:37

문제 상황

인기 차트 생성 로직을 구현한 이후,
차트가 정상적으로 생성되고 조회까지 되는지 확인하는 과정에서 구조적인 아쉬움을 느꼈다.

PR 과정에서 CodeRabbit 리뷰를 통해 Top100 제한이 비효율적인 방식으로 처리되고 있다는 점을 확인하게 되었다.


코드 리뷰 피드백

CodeRabbit는 다음과 같은 문제를 지적했다.

  • Top100 제한을 DB에서 처리하지 않고
  • 서비스 로직(애플리케이션 레벨)에서 후처리로 적용하고 있는 구조


원인 분석

기존 구현에서는 다음과 같이 Top100 제한을 적용하고 있었다.

List<TrackPlayCountProjection> results =
    playbackRepository.countWeeklyValidPlayByTrack(
        startDateTime,
        snapshotDateTime,
        MINIMUM_VALID_PLAY_SECONDS
    );

List<TrackPlayCountProjection> top100 = results.stream()
    .limit(TOP_CHART_LIMIT)
    .toList();

 

이 구조에서는

  • DB는 전체 데이터를 반환하고
  • 애플리케이션이 결과를 잘라내는 방식이었다

즉, 조회 범위 제어를 DB가 아닌 서비스 코드가 담당하고 있었다.


문제점

  • 불필요한 데이터 조회 발생
  • DB → 애플리케이션 전송 비용 증가
  • 메모리 사용 증가
  • 데이터 규모 증가 시 성능 저하

데이터가 많아질수록 비효율이 커지는 구조였다.


해결 과정

Top N 제한은 DB 쿼리 단계에서 처리하도록 구조를 변경했다.

List<TrackPlayCountProjection> results =
    playbackRepository.countWeeklyValidPlayByTrack(
        startDateTime,
        snapshotDateTime,
        MINIMUM_VALID_PLAY_SECONDS,
        TOP_CHART_LIMIT
    );
@Query("""
    SELECT p.trackId AS trackId, COUNT(p) AS playCount
    FROM Playback p
    WHERE p.startedAt >= :startDateTime
      AND p.startedAt < :endDateTime
      AND p.playedDuration >= :minimumSeconds
    GROUP BY p.trackId
    ORDER BY COUNT(p) DESC
    LIMIT :limit
""")
List<TrackPlayCountProjection> countWeeklyValidPlayByTrack(
    LocalDateTime startDateTime,
    LocalDateTime endDateTime,
    int minimumSeconds,
    int limit
);

 

쿼리 단계에서 결과를 제한하도록 변경했다.


적용 결과

  • Top100 제한을 DB에서 처리 → 성능 개선
  • 불필요한 데이터 조회 제거
  • 데이터 전송량 감소
  • 메모리 사용 감소

정리

Top N 제한은 애플리케이션이 아닌 DB에서 처리해야 한다.

조회 범위 제어를 DB에서 수행함으로써 성능과 효율성을 동시에 개선할 수 있었다.


느낀 점

이번 개선을 통해 단순히 기능을 구현하는 것을 넘어서

  • 조회 성능을 고려한 쿼리 설계의 중요성
  • 처리 책임을 어디에 둘 것인지에 대한 설계 기준

을 고민하는 것이 필요하다는 것을 느꼈다.

 

결과적으로 동작하는 코드보다, 효율적으로 동작하는 구조를 만드는 것이 중요하다는 점을 체감할 수 있었다.