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 |