Spring Boot 성능 최적화 1

2025. 10. 25. 23:48·TIL

DB 조회 성능 최적화

DB 조회 성능은 대부분의 서버 애플리케이션에서 가장 중요한 최적화 포인트다.
API 응답이 느린 원인의 상당수가 DB 접근에 있기 때문에, 조회 성능을 개선하는 것만으로도 전체 서비스의 체감 속도를 크게 끌어올릴 수 있다.
이 글에서는 Spring Boot + JPA + MySQL 환경을 기준으로 실전에서 많이 사용하는 DB 조회 성능 최적화 방법들을 정리한다.


#인덱스(Index) 설계

인덱스는 조회 성능 최적화의 가장 기본적이면서도 가장 효과적인 방법이다.
DB가 특정 조건을 빠르게 찾기 위해 사용하는 정렬된 자료구조라고 이해하면 된다.

언제 인덱스를 사용해야 할까?

  • WHERE 조건으로 자주 조회하는 컬럼
  • ORDER BY / GROUP BY에 자주 사용되는 컬럼
  • 조인에 사용되는 외래키 컬럼
  • 날짜 기반 필터링이 잦은 created_at, updated_at

실수하기 쉬운 부분

  • 너무 많은 인덱스는 쓰기 성능을 떨어뜨린다
  • 문자열 컬럼의 인덱스는 비용이 크므로 주의
  • 카디널리티(값 다양성)가 낮은 컬럼에 인덱스는 효과가 약하다

#N+1 문제와 Fetch Join

JPA를 사용하면 가장 먼저 마주치는 성능 이슈가 바로 N+1 문제다.

N+1은 한 번의 조회로 끝날 것 같은 쿼리가, 연관된 데이터를 불러오느라 추가로 N번의 쿼리가 더 실행되는 상황을 의미한다.

해결 방법

  • Fetch Join
  • EntityGraph
  • Batch Size 설정

예시: Fetch Join

@Query("select p from Post p join fetch p.comments where p.id = :id")
Optional<Post> findPostWithComments(Long id);

Fetch Join은 강력한 해결책이지만, 남용하면 페이징이 정상적으로 동작하지 않는 문제가 생길 수 있기 때문에 상황에 따라 선택해야 한다.


#Projection으로 필요한 데이터만 조회하기

Entity 전체를 조회하면 실제로 필요하지 않은 컬럼까지 모두 가져오게 된다.
조회 성능을 높이고 네트워크 비용까지 줄이려면 필요한 컬럼만 선택적으로 가져오는 방식이 효과적이다.

적용 방식

  • 인터페이스 기반 프로젝션
  • DTO 기반 프로젝션
  • QueryDSL로 필요한 컬럼만 select

예시: DTO 프로젝션

select new com.example.PostSummary(p.id, p.title)
from Post p
where p.status = 'ACTIVE'

특히 목록 조회 API에서 성능 체감이 크다.


#페이징 최적화

대부분의 리스트 API는 페이징을 사용한다.
문제는 count(*)가 붙은 카운트 쿼리가 테이블이 커질수록 비용이 커진다는 점이다.

최적화 전략

  • countQuery 분리
  • 커서 기반 페이징(cursor pagination) 사용
  • 단순 정렬 기준 + 인덱스로 성능 극대화

카운트 쿼리 분리 예시

@Query(
  value = "select p from Post p where p.status = :status",
  countQuery = "select count(p.id) from Post p where p.status = :status"
)
Page<Post> findAllActive(Pageable pageable);
카운트 쿼리에서 불필요한 조인을 제거하면 성능 차이가 크게 난다.

#읽기 전용 트랜잭션(readOnly = true)

조회용 메서드에 readOnly = true를 적용하면 JPA의 Dirty Checking이 비활성화되어 성능이 향상된다.

@Transactional(readOnly = true)
public List<Post> getPosts() {
    return postRepository.findAll();
}
읽기 비중이 높은 서비스에서 효과적이다.

#QueryDSL · Native Query 선택 기준

JPA는 생산성이 높지만, 복잡한 조회 쿼리는 성능적으로 불리할 수 있다.
이때 QueryDSL이나 Native Query로 전환하는 것도 중요한 선택 기준이다.

QueryDSL이 유리한 경우

  • 동적 쿼리가 많을 때
  • 복잡한 조건 조합이 필요할 때
  • 타입 안전한 코드가 필요할 때

Native Query가 유리한 경우

  • 조인 수가 많아지거나 JPA 최적화가 어려울 때
  • DB에 특화된 함수나 쿼리를 사용해야 할 때
  • 성능이 최우선인 상황

#캐시(Cache)로 DB 조회 자체 줄이기

DB 조회를 아무리 최적화해도, 결국 가장 빠른 조회는 DB를 조회하지 않는 것이다.
따라서 캐시는 성능 최적화에서 매우 효과적인 수단이다.

활용 전략

  • 자주 조회되지만 잘 변경되지 않는 데이터 캐싱
  • 목록 조회 캐싱
  • id 기반 조회 캐싱
  • TTL을 이용한 자동 만료

캐시는 별도 글에서 더 깊게 다룰 예정이다.


마치며

DB 조회 성능 최적화는 단일 기술로 해결되는 문제가 아니다.
인덱스, JPA 전략, 페이징, 캐싱, 쿼리 구조까지 조합하여 전체적인 흐름을 최적화해야 효과가 나타난다.
이 글에서는 Spring Boot 서비스에서 가장 자주 사용되는 핵심 전략을 중심으로 정리했다.

다음 글에서는 JPA 자체 최적화(영속성 컨텍스트, Batch Size, Dirty Checking 최적화 등)를 다룰 예정이다.

'TIL' 카테고리의 다른 글

Spring Boot 성능 최적화 3  (0) 2025.10.28
Spring Boot 성능 최적화 2  (0) 2025.10.27
10/24  (0) 2025.10.24
10/23  (1) 2025.10.23
10/20  (0) 2025.10.20
'TIL' 카테고리의 다른 글
  • Spring Boot 성능 최적화 3
  • Spring Boot 성능 최적화 2
  • 10/24
  • 10/23
twodc
twodc
매일의 배움과 고민, 그리고 작은 성장을 담은 개발자의 성장일지입니다.
  • twodc
    The Answer is 42
    twodc
  • 전체
    오늘
    어제
    • 분류 전체보기 (122)
      • TIL (122)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Github
  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
twodc
Spring Boot 성능 최적화 1
상단으로

티스토리툴바