TestForge Blog

Spring Boot 성능 튜닝 방법 — 응답 시간 50% 줄이기

Spring Boot 애플리케이션의 응답 시간을 줄이는 실전 튜닝 방법. DB 커넥션 풀, JPA 최적화, 캐시, JVM 설정까지 단계별 가이드.

TestForge Team ·

성능 측정부터 시작

튜닝 전 반드시 **기준선(baseline)**을 측정하세요. 느낌이 아닌 숫자로.

# Spring Boot Actuator 활성화
management.endpoints.web.exposure.include=health,metrics,prometheus
# k6로 부하 테스트
k6 run --vus 50 --duration 60s script.js

1. DB 커넥션 풀 최적화 (HikariCP)

Spring Boot 기본 커넥션 풀은 HikariCP입니다. 기본값이 생각보다 작습니다.

spring:
  datasource:
    hikari:
      maximum-pool-size: 20       # 기본값 10 → 서버 코어수 × 2~4
      minimum-idle: 5
      connection-timeout: 3000    # 3초 이내 커넥션 획득 실패 시 예외
      idle-timeout: 600000        # 10분 미사용 커넥션 반환
      max-lifetime: 1800000       # 30분 최대 수명
      leak-detection-threshold: 5000  # 5초 이상 반환 안 되면 경고

주의: maximum-pool-size를 무작정 늘리면 오히려 DB 과부하.
DB 서버 max_connections 의 80%를 넘지 않도록 합니다.

2. JPA N+1 문제 제거

가장 흔한 성능 저하 원인입니다.

// 문제: Order 1개 조회 후 items 쿼리 N번 추가 실행
List<Order> orders = orderRepository.findAll();
orders.forEach(o -> o.getItems().size());  // N+1!

// 해결 1: Fetch Join
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.userId = :userId")
List<Order> findWithItems(@Param("userId") Long userId);

// 해결 2: @EntityGraph
@EntityGraph(attributePaths = {"items", "user"})
List<Order> findByStatus(String status);

// 해결 3: Batch Size (컬렉션 IN절로 묶기)
@BatchSize(size = 100)
@OneToMany(mappedBy = "order")
private List<OrderItem> items;

3. 캐시 레이어 추가

// Spring Cache + Redis
@EnableCaching
@Configuration
public class CacheConfig {
    @Bean
    public RedisCacheConfiguration cacheConfig() {
        return RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(10))
            .serializeValuesWith(
                RedisSerializationContext.SerializationPair
                    .fromSerializer(new GenericJackson2JsonRedisSerializer())
            );
    }
}

// 사용
@Cacheable(value = "product", key = "#id")
public ProductDto getProduct(Long id) {
    return productRepository.findById(id)
        .map(ProductDto::from)
        .orElseThrow();
}

@CacheEvict(value = "product", key = "#id")
public void updateProduct(Long id, UpdateRequest req) { ... }

4. 비동기 처리

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        return executor;
    }
}

// 이메일 발송, 알림 등 비크리티컬 작업 비동기화
@Async
public CompletableFuture<Void> sendNotification(Long userId, String message) {
    notificationService.send(userId, message);
    return CompletableFuture.completedFuture(null);
}

5. 응답 압축

server:
  compression:
    enabled: true
    mime-types: application/json,application/xml,text/html
    min-response-size: 1024  # 1KB 이상만 압축

JSON 응답 기준 40~70% 용량 감소 효과.

6. JVM 튜닝

java -jar app.jar \
  -Xms512m -Xmx1g \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=100 \
  -XX:G1HeapRegionSize=16m \
  -XX:+ParallelRefProcEnabled \
  -XX:+DisableExplicitGC

7. Slow Query 자동 감지

spring:
  jpa:
    properties:
      hibernate:
        generate_statistics: true
        session.events.log.LOG_QUERIES_SLOWER_THAN_MS: 100  # 100ms 이상 쿼리 로깅
logging:
  level:
    org.hibernate.stat: DEBUG

튜닝 우선순위

  1. DB 쿼리 최적화 (N+1, 인덱스) — 효과 최대
  2. 커넥션 풀 조정 — 즉각 효과
  3. 캐시 도입 — 반복 조회 제거
  4. 비동기 처리 — 응답 시간 단축
  5. 응답 압축 — 네트워크 비용 절감
  6. JVM 튜닝 — 미세 조정

대부분의 성능 문제는 DB 레이어에서 발생합니다.
JVM 튜닝은 1~5를 모두 적용한 후 마지막에 합니다.