TIL - 20251119

juni·2025년 11월 18일

TIL

목록 보기
182/316

1119 Spring Boot 성능 최적화: JVM, Connection Pool, Logging


✅ 1. JVM 튜닝의 기초: 힙 메모리 설정

  • 어제 학습한 것처럼, Major GC(Full GC)는 "Stop-the-World"를 유발하여 애플리-케이션의 응답성에 큰 영향을 줍니다. JVM 튜닝의 가장 기본적인 목표는 애플리-케이션의 특성에 맞게 힙(Heap) 메모리 크기를 조절하여, Full GC의 발생 빈도를 줄이고 실행 시간을 최소화하는 것입니다.

  • 핵심 원리:

    • Young Generation의 크기를 늘리면, 객체들이 Old Generation으로 넘어가는 시점을 늦출 수 있어 Minor GC는 더 자주 발생하지만, Major GC의 빈도는 줄어듭니다.
    • 반대로 Young Generation의 크기를 줄이면, 객체들이 더 빨리 Old Generation으로 넘어가 Major GC가 더 자주 발생할 수 있습니다.

➕ 주요 JVM 힙 메모리 설정 옵션

  • 이 옵션들은 Spring Boot 애플리-케이션을 JAR 파일로 실행할 때 -D-X 플래그를 통해 전달합니다.
옵션설명예시
-Xms<size>JVM 시작 시 할당받는 최소 힙 메모리 크기 (Initial Heap Size)-Xms256m (256MB)
-Xmx<size>JVM이 사용할 수 있는 최대 힙 메모리 크기 (Maximum Heap Size)-Xmx1024m (1GB)
-Xmn<size>Young Generation의 크기를 지정합니다.-Xmn128m (128MB)
  • 운영 환경에서의 모범 사례:
    • -Xms-Xmx를 동일한 값으로 설정하는 것이 일반적입니다. 이는 런타임 중에 JVM이 힙 크기를 동적으로 조절(resizing)하면서 발생하는 불필요한 오버헤드와 Full GC를 방지하기 위함입니다.
    • 예시: java -Xms512m -Xmx512m -jar my-app.jar

✅ 2. 커넥션 풀 (Connection Pool) 관리

  • 문제점: 데이터베이스에 접근해야 할 때마다 매번 커넥션을 새로 생성(TCP/IP 연결, DB 인증 등)하고, 작업이 끝나면 해제하는 것은 매우 비용이 비싼 작업입니다. 동시 요청이 많은 웹 애플리-케이션에서는 이 과정이 성능의 심각한 병목이 됩니다.

  • 커넥션 풀: 애플리-케이션이 시작될 때, 미리 일정 개수의 데이터베이스 커넥션을 생성하여 "풀(Pool)"에 보관해두고, 필요할 때마다 이 풀에서 커넥션을 빌려 쓰고, 사용이 끝나면 다시 반납하는 기술입니다.

➕ HikariCP와 주요 설정

  • Spring Boot 2.0 이상부터는 HikariCP가 기본 JDBC 커넥션 풀 구현체로 사용됩니다. HikariCP는 매우 빠르고 안정적인 성능으로 유명합니다.
  • application.yml에서 다음과 같은 주요 속성을 튜닝하여 성능을 최적화할 수 있습니다.
속성설명권장 사항
maximum-pool-size커넥션 풀이 가질 수 있는 최대 커넥션 개수.(코어 수 * 2) + (스핀들 수) 공식이 있지만, 부하 테스트를 통해 최적값을 찾아야 함. 너무 크면 오히려 DB에 부하.
minimum-idle풀에서 유지하는 최소 유휴(idle) 커넥션 개수.maximum-pool-size와 동일하게 설정하여, 런타임 중 커넥션 생성/제거 오버헤드를 줄이는 것이 좋음.
connection-timeout풀에서 커넥션을 얻기 위해 대기하는 최대 시간 (밀리초).너무 길면 사용자가 오래 기다리게 됨. (e.g., 30000ms)

✅ 3. 스레드 풀 (Thread Pool) 관리

  • 문제점: 클라이언트로부터 요청이 들어올 때마다 매번 새로운 스레드를 생성하는 것은, 스레드 생성 및 컨텍스트 스위칭 비용 때문에 비효율적입니다.

  • 내장 웹 서버의 스레드 풀: Spring Boot의 내장 웹 서버(Tomcat 등)는 스레드 풀을 사용하여 이 문제를 해결합니다. 미리 일정 개수의 스레드를 생성해두고, 요청이 들어오면 풀에 있는 스레드를 할당하여 작업을 처리하고, 작업이 끝나면 스레드를 다시 풀에 반납합니다.

➕ Tomcat 스레드 풀 주요 설정

  • application.yml에서 내장 Tomcat의 스레드 풀 동작을 제어할 수 있습니다.
속성설명
server.tomcat.threads.max스레드 풀이 가질 수 있는 최대 스레드 개수. (기본값: 200)
이 개수를 초과하는 요청은 큐에서 대기.
server.tomcat.threads.min-spare항상 활성 상태로 유지하는 최소 스레드 개수. (기본값: 10)
server.tomcat.accept-count최대 스레드가 모두 사용 중일 때, 들어오는 연결 요청을 대기 큐(Queue)에 쌓아둘 수 있는 최대 개수. (기본값: 100)
  • 성능 튜닝: max-threads를 무작정 늘리는 것이 능사는 아닙니다. CPU 코어 수, I/O 작업의 비중, 외부 시스템의 응답 시간 등을 고려하여, 부하 테스트(nGrinder)를 통해 최적의 값을 찾아야 합니다.

✅ 4. 로깅 (Logging) 전략

  • 문제점: System.out.println()을 사용하여 로그를 출력하는 것은 여러 단점이 있습니다.

    • 로그 레벨(INFO, DEBUG, ERROR)을 구분할 수 없습니다.
    • 파일 출력, 형식 지정 등 부가 기능이 없습니다.
    • 동기(Synchronous) 방식으로 동작하여 I/O 병목을 유발할 수 있습니다.
  • SLF4J와 Logback: Spring Boot는 기본적으로 SLF4J(Simple Logging Facade for Java)로깅 추상체(Facade)로, Logback구현체로 사용합니다.

➕ 운영 환경을 위한 로깅 모범 사례

  1. 로그 레벨 설정:

    • application-prod.yml에서 기본 로그 레벨을 INFO로 설정하여, 불필요한 DEBUG 로그가 운영 환경의 성능에 영향을 주지 않도록 합니다.
    • 특정 패키지에 대해서만 DEBUG 레벨을 활성화하여 문제 추적을 용이하게 할 수 있습니다.
  2. 비동기 로거 (Async Appender):

    • Logback의 AsyncAppender를 사용하면, 로그 이벤트를 별도의 큐(Queue)에 저장하고, 다른 스레드가 이 큐에서 로그를 가져와 파일에 쓰는 비동기 방식으로 동작합니다.
    • 이를 통해 로깅으로 인한 I/O 대기 시간을 애플리-케이션의 메인 스레드로부터 분리하여, 애플리-케이션의 응답성을 향상시킬 수 있습니다.

📌 요약

  • JVM 튜닝의 기본은 -Xms-Xmx 옵션을 동일하게 설정하여 불필요한 Full GC를 방지하는 것입니다.
  • 커넥션 풀(HikariCP)스레드 풀(Tomcat)은 미리 자원(커넥션, 스레드)을 생성해두고 재사용함으로써, 리소스 생성 비용을 줄여 애플리-케이션의 핵심 성능을 좌우합니다.
  • 운영 환경에서는 로그 레벨을 INFO로 조정하고, 비동기 로거를 사용하여 로깅으로 인한 성능 저하를 최소화해야 합니다.
  • 이 모든 설정의 최적값은 정해진 공식이 없으며, 반드시 nGrinder와 같은 부하 테스트 도구를 통해 실제 환경과 유사한 조건에서 측정하고 분석하여 찾아내야 합니다.

0개의 댓글