TIO 개선 소요 파악 - 성능 최적화

2025. 7. 29. 04:02·Jungle

웹 애플리케이션의 성능 병목을 해결하기 위한 6가지 핵심 최적화 전략을 정리했습니다.

1. 데이터베이스 액세스 최적화

1.1 N+1 쿼리 문제 해결

문제: 연관 엔티티 조회 시 추가 쿼리가 N번 발생

// ❌ 기존: N+1 쿼리 발생 (1 + 100번)
return productRepository.findTop100ByDeletedFalseOrderByWishlistCountDesc()
    .stream()
    .map(product -> new ProductResponseDto(product, 
        product.getCategory().getCategoryName())) // 각 상품마다 카테고리 쿼리 발생
// ✅ 개선: Fetch Join 사용
@Query("SELECT p FROM Product p JOIN FETCH p.category c WHERE p.deleted = false " +
       "ORDER BY p.wishlistCount DESC")
List<Product> findTop100WithCategory(Pageable pageable);

// 사용
return productRepository.findTop100WithCategory(PageRequest.of(0, 100))
    .stream()
    .map(product -> new ProductResponseDto(product, liked))

💡 효과: 101번 쿼리 → 1번 쿼리로 단축

1.2 배치 처리 최적화

문제: 대량 INSERT 시 개별 쿼리 전송으로 성능 저하

// ❌ 기존: 개별 쿼리 전송
for (OrderItem item : orderItems) {
    orderItemRepository.save(item); // 각각 개별 INSERT
}
// ✅ 개선: 배치 처리 설정
# application.properties
spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true

// 배치 단위로 flush & clear
for (int i = 0; i < orderItems.size(); i += 50) {
    // 50개씩 처리
    entityManager.flush();
    entityManager.clear();
}

💡 효과: 네트워크 통신 횟수 대폭 감소

2. 캐싱 전략 구현

2.1 Redis 캐싱 적용

문제: 반복 조회되는 데이터를 매번 DB에서 조회

// ❌ 기존: 매번 DB 조회
public ProductDetailResponseDto getProductDetail(Long productId) {
    return productRepository.findById(productId); // 매번 DB 접근
}
// ✅ 개선: Redis 캐싱 적용
@Cacheable(value = "productDetail", key = "#productId + '_' + #userId")
public ProductDetailResponseDto getProductDetail(Long userId, Long productId) {
    return productRepository.findById(productId); // Cache Miss 시에만 실행
}

// 캐시 설정
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(10));
    return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();
}

💡 효과: DB 부하 감소, 응답 시간 단축

2.2 캐시 무효화 전략

// ✅ 데이터 변경 시 캐시 삭제
@CacheEvict(value = "productDetail", key = "#product.id + '_*'")
public void updateProduct(Product product) {
    productRepository.save(product);
}

// 주기적 캐시 초기화
@Scheduled(fixedRate = 3600000) // 1시간마다
@CacheEvict(value = "productList", allEntries = true)
public void clearProductListCache() {
    log.info("상품 목록 캐시 초기화");
}

3. 비동기 처리 및 병렬화

3.1 비동기 이벤트 처리

문제: 부가 작업으로 인한 응답 지연

// ❌ 기존: 동기 처리로 응답 지연
public OrderResponseDto createOrder(OrderRequestDto requestDto) {
    Order order = orderRepository.save(order);
    
    // 주문 완료 후 부가 작업들이 동기적으로 실행
    recommendBehaviorLogService.logUserAction(userId, productId, BUY); // 응답 지연 발생
    
    return new OrderResponseDto(order);
}
// ✅ 개선: 비동기 이벤트 처리
@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        return executor;
    }
}

// 주문 생성 (핵심 로직만)
public OrderResponseDto createOrder(OrderRequestDto requestDto) {
    Order order = orderRepository.save(order);
    orderEventPublisher.publishOrderCreatedEvent(order); // 이벤트 발행만
    return new OrderResponseDto(order); // 즉시 응답
}

// 비동기 처리
@Async("taskExecutor")
@EventListener
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
    recommendBehaviorLogService.logUserAction(userId, productId, BUY); // 별도 스레드에서 처리
}

💡 효과: 사용자 응답 시간 단축, 시스템 처리량 증가

3.2 병렬 데이터 조회

문제: 독립적인 데이터를 순차 조회하여 대기 시간 발생

// ❌ 기존: 순차 조회 (총 2.5초)
List<ProductResponseDto> recommendations = 
    productService.getPersonalizedRecommendations(userId); // 1초
List<ProductResponseDto> topRanked = 
    productService.getTopRankedProducts(userId); // 1.5초
// ✅ 개선: 병렬 조회 (총 1.5초)
CompletableFuture<List<ProductResponseDto>> recommendationsFuture =
    CompletableFuture.supplyAsync(() -> 
        productService.getPersonalizedRecommendations(userId));

CompletableFuture<List<ProductResponseDto>> topRankedFuture =
    CompletableFuture.supplyAsync(() -> 
        productService.getTopRankedProducts(userId));

// 모든 작업 완료 대기
CompletableFuture.allOf(recommendationsFuture, topRankedFuture).join();

List<ProductResponseDto> recommendations = recommendationsFuture.get();
List<ProductResponseDto> topRanked = topRankedFuture.get();

💡 효과: 응답 시간 40% 단축

4. 응답 데이터 최적화

4.1 DTO 경량화 & 프로젝션 쿼리

문제: 불필요한 데이터 전송으로 네트워크 부하 증가

// ❌ 기존: 무거운 DTO 사용
public class ProductResponseDto {
    private String description; // 목록에서 불필요한 긴 텍스트
    private List<String> images; // 목록에서 불필요한 모든 이미지
    private CategoryDto category; // 객체 전체 포함
}

// 전체 엔티티 조회 후 변환
@Query("SELECT p FROM Product p WHERE p.deleted = false")
List<Product> findAllProducts();
// ✅ 개선: 목록용 경량 DTO
public class ProductSummaryDto {
    private Long id;
    private String name;
    private String thumbnail; // 대표 이미지만
    private String categoryName; // 카테고리 이름만
}

// 프로젝션 쿼리로 필요한 데이터만 조회
@Query("SELECT new com.tryiton.dto.ProductSummaryDto(p.id, p.productName, p.img1, c.categoryName) " +
       "FROM Product p LEFT JOIN p.category c WHERE p.deleted = false")
List<ProductSummaryDto> findProductSummaries();

💡 효과: 네트워크 트래픽 60% 감소

5. JVM 및 서버 최적화

5.1 JVM 옵션 최적화

# ✅ 최적화된 JVM 설정
java -Xms4g -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -jar app.jar

5.2 Tomcat 설정 최적화

# application.properties
server.tomcat.threads.max=400
server.tomcat.accept-count=500
server.tomcat.max-connections=10000

5.3 데이터베이스 커넥션 풀 최적화

# HikariCP 설정
spring.datasource.hikari.maximum-pool-size=400
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.connection-timeout=30000

💡 효과: 동시 처리 능력 대폭 향상

6. 프론트엔드 최적화

6.1 API 요청 최적화

문제: 중복 요청으로 서버 부하 증가

// ❌ 기존: 중복 요청 방지 없음
const fetchData = () => {
  return axios.get('/api/products'); // 매번 새로운 요청
};
// ✅ 개선: 요청 캐싱 적용
const requestCache = new Map<string, Promise<any>>();

const axiosWithCache = axios.create();
axiosWithCache.interceptors.request.use((config) => {
  if (config.method === 'get') {
    const cacheKey = `${config.url}:${JSON.stringify(config.params)}`;
    const cachedRequest = requestCache.get(cacheKey);
    
    if (cachedRequest) {
      return { ...config, adapter: () => cachedRequest };
    }
    
    const request = axios(config);
    requestCache.set(cacheKey, request);
    setTimeout(() => requestCache.delete(cacheKey), 5000); // 5초 TTL
  }
  return config;
});

6.2 데이터 프리페칭

// ✅ 사용자 행동 예측하여 미리 데이터 로드
useEffect(() => {
  if (mainProducts?.categories) {
    const firstCategory = mainProducts.categories[0];
    // 첫 번째 카테고리 상품 미리 로드
    queryClient.prefetchQuery({
      queryKey: ['categoryProducts', firstCategory.id],
      queryFn: () => fetchCategoryProducts(firstCategory.id),
    });
  }
}, [mainProducts]);

💡 효과: 사용자 체감 응답 속도 향상

성능 개선 결과 요약

최적화 항목 개선 전 개선 후 개선율
DB 쿼리 수 101회 1회 99% ↓
응답 시간 2.5초 1.5초 40% ↓
네트워크 트래픽 100% 40% 60% ↓
동시 처리 능력 200 TPS 400+ TPS 100% ↑

이러한 최적화를 통해 사용자 경험을 크게 개선하고 서버 리소스를 효율적으로 활용할 수 있습니다.

728x90

'Jungle' 카테고리의 다른 글

TIO 성능 테스트: EC2 업그레이드로 144 TPS 달성하기  (0) 2025.07.29
TIO 성능 테스트 결과 분석 (40명 동시 사용자)  (0) 2025.07.29
상세이미지 버킷 링크 노출  (0) 2025.07.24
성능 분석 실패기 : Grafana K6, N+1 문제를 폭로하다  (0) 2025.07.24
17일? 못 기다려! 크롤러 성능 개선 삽질기 (Selenium → Requests)  (0) 2025.07.23
'Jungle' 카테고리의 다른 글
  • TIO 성능 테스트: EC2 업그레이드로 144 TPS 달성하기
  • TIO 성능 테스트 결과 분석 (40명 동시 사용자)
  • 상세이미지 버킷 링크 노출
  • 성능 분석 실패기 : Grafana K6, N+1 문제를 폭로하다
ahpicl64
ahpicl64
in the clouds
  • ahpicl64
    구름
    ahpicl64
  • 전체
    오늘
    어제
    • 분류 전체보기 (95)
      • WIL (4)
      • Jungle (36)
      • AWS (2)
      • SQL (2)
      • CS:APP (17)
      • Algorithm (10)
      • K8s (7)
      • 자료 구조 (10)
      • Spring (4)
      • React (0)
      • 운영체제 (1)
      • 기타등등 (2)
      • 이야기 (0)
  • 블로그 메뉴

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

    • github
  • 공지사항

  • 인기 글

  • 태그

    EC2
    github actions
    Spring boot
    queue
    python
    k8s
    S3
    자료구조
    CloudFront
    부하테스트
    트러블슈팅
    컴퓨터시스템
    Spring
    CSAPP
    AWS
    DB
    어셈블리
    IAM
    DevOps
    알고리즘
  • 02-21 08:19
  • hELLO· Designed By정상우.v4.10.3
ahpicl64
TIO 개선 소요 파악 - 성능 최적화
상단으로

티스토리툴바