문제상황
Monew 프로젝트 메인 화면에서는 뉴스기사 목록을 조회한다.

이 화면은 사용자가 자주 접근할 가능성이 높은 진입 지점이라고 생각했고, 목록 조회 성능이 전체 사용자 경험에 직접적인 영향을 줄 수 있다고 판단했다.
메일화면의 기본 조회조건은 화면에서 볼 수 있듯이 다음과 같다.
정렬기준: published_at
정렬 방향: DESC
출처: NAVER
조회 개수: 10개 + hasNext 확인용 1개
WHERE na.is_deleted = false
AND na.source = 'NAVER'
ORDER BY
na.published_at DESC,
na.created_at DESC
FETCH FIRST 11 ROWS ONLY
기존 조회 구조
LEFT JOIN article_interests ai
ON ai.news_article_id = na.id
LEFT JOIN comments c
ON c.article_id = na.id
AND c.is_deleted = false
LEFT JOIN article_views av
ON av.news_article_id = na.id
AND av.user_id = ...
기존 뉴스 기사 목록 조회는 기사 정보, 댓글 수, 사용자 조회 여부를 한번의 쿼리에서 모두 처리하고 있었다.
이후 댓글 수 집계를 위해 COUNT(DISTINCT c.id)와 GROUP BY를 수행했다.
문제는 메인 화면에서는 관심사 필터가 없음에도 article_interests 조인이 항상 수행된다는 점이었다.
즉, 최종적으로 필요한 데이터는 11개뿐인데 DB에서는 수만건의 조인과 집계, 정렬을 먼저 수행하고 있었다.
실행 계획 분석
실행 계획을 확인해보니 다음과 같은 문제가 있었다.
- NAVER 기사 약 45,700 rows 조회
- article_interests 약 79,000 rows 조인
- 중간 결과 약 72,000 rows 생성
- 이후 GROUP BY, COUNT(DISTINCT id), ORDER BY 수행
특히 정렬 단계에서는 external merge가 발생했다.
Sort Method: external merge
temp read/write 발생
정렬 대상 데이터가 메모리에 모두 올라가지 못해 디스크 기반 정렬이 발생했다는 의미 ..
결국 최종 결과는 11개 뿐인데도 DB는 그 11개를 얻기위해 수만건의 중간 결과를 처리하고 있던것이다.
개선 방향
문제의 핵심은 11개의 row를 뽑기위해 너무 많은 작업을 수행한다는 점.
1. news_articles에서 기사 11개를 먼저 조회
2. 조회된 기사 ID에 대해서만 댓글 수 조회
3. 조회된 기사 ID에 대해서만 사용자 조회 여부 조회
4. java에서 DTO 조립
먼저 JOIN과 GROUP BY, ORDER BY로 준비를 해놓고 조회하는방식이 아닌 11개를 조회하고 JOIN을 하는 식으로 변경했다.
개선 후 구조
SELECT
na.id,
na.source,
na.original_link,
na.title,
na.published_at,
na.summary,
na.view_count,
na.created_at
FROM news_articles na
WHERE na.is_deleted = false
AND na.source = 'NAVER'
ORDER BY
na.published_at DESC,
na.created_at DESC
FETCH FIRST 11 ROWS ONLY;
먼저 11개를 조회한 후, 댓글 수 와 조회 여부는 현제 페이지의 기사 ID만 대상으로 별도 조회하는식으로 변경한것이다.
성능 비교
NAVER 뉴스기사 약 45000건 기준
기존 JOIN/GROUP BY 구조: 평균 실행시간은 614.825 ms
개선 구조는 20.215 ms
기사 목록조회: 20.067 ms
댓글 수 집계: 0.111ms
사용자 조회 여부 조회: 0.038ms
개선 후 전체 DB 실행 시간 기준 약 30배 수준의 성능 개선을 확인할 수 있었다.
'트러블 슈팅' 카테고리의 다른 글
| Monew 프로젝트: 뉴스기사 목록조회 PostgreSQL 복합 인덱스로 뉴스 목록 조회 성능 개선 (0) | 2026.05.17 |
|---|