대규모 시스템 성능 최적화 리드/아키텍트 기술면접
새 면접Q. 5000 TPS를 처리하는 API 서버 10대가 있고, 각 서버의 DB 커넥션 풀 크기가 100으로 설정되어 있습니다. 모니터링 결과 평균 응답시간은 200ms인데, P99는 5초를 초과하고 있으며 DB 서버의 CPU는 30% 수준입니다. 반면 애플리케이션 서버에서는 'Connection pool exhausted' 로그가 간헐적으로 발생합니다. 이 상황을 진단하고 해결하기 위한 접근 방법을 단계별로 설명하고, 적정 커넥션 풀 크기를 결정하는 공식과 고려사항을 제시하세요.
커넥션 풀 크기가 크다고 성능이 좋아지는 것은 아니며, DB와 애플리케이션 양쪽의 메트릭을 함께 분석해야 합니다.
먼저 애플리케이션 로그에서 슬로우 쿼리와 긴 트랜잭션을 식별하고, DB 세션 모니터링으로 대기 중인 커넥션 수와 활성 쿼리 수를 확인합니다. P99 지연이 높은 것은 일부 긴 트랜잭션이 커넥션을 오래 점유하여 다른 요청들이 대기하기 때문입니다. 적정 커넥션 풀 크기는 'connections = ((core_count * 2) + effective_spindle_count)' 공식을 기준으로 하되, 실제로는 평균 쿼리 실행시간과 TPS를 고려하여 'pool_size = TPS * average_query_time + buffer'로 계산합니다. 커넥션 타임아웃과 유휴 커넥션 제거 정책을 설정하고, 긴 트랜잭션은 별도 풀로 분리하거나 Read Replica로 라우팅합니다. HikariCP의 경우 leakDetectionThreshold를 설정하여 커넥션 누수를 탐지하고, maxLifetime을 DB의 wait_timeout보다 짧게 설정하여 끊긴 커넥션 사용을 방지합니다.
- • 긴 트랜잭션이 커넥션을 점유하여 P99 지연 발생
- • 적정 커넥션 풀 크기는 TPS와 평균 쿼리 시간의 곱으로 계산
- • 커넥션 타임아웃과 누수 탐지 설정 필요
- • 긴 트랜잭션 분리 및 Read Replica 활용
과도한 커넥션 풀 설정으로 인한 컨텍스트 스위칭과 메모리 낭비를 해결하여 안정적인 처리량을 확보하는 상황
커넥션 풀을 서버당 20개로 줄였을 때 오히려 전체 처리량이 증가할 수 있는 이유는 무엇이며, 이를 검증하기 위한 부하 테스트 시나리오를 어떻게 설계하시겠습니까?
Q. 64GB 메모리를 가진 서버에서 Spring Boot 애플리케이션이 실행 중이며, Heap은 32GB로 설정했습니다. 운영 중 Full GC가 30초 이상 소요되어 서비스가 멈추는 현상이 발생하고 있습니다. GC 로그 분석 결과 Old Generation이 90% 이상 차있고, Young GC는 평균 100ms로 정상이지만 Promotion Rate가 높게 나타납니다. G1GC를 사용 중인데, 이 상황을 개선하기 위한 진단 절차와 JVM 파라미터 튜닝 전략, 그리고 애플리케이션 레벨에서의 개선 방안을 제시하세요.
높은 Promotion Rate는 Young Generation에서 살아남은 객체가 너무 많이 Old Generation으로 이동한다는 의미입니다.
먼저 Heap Dump를 떠서 MAT나 VisualVM으로 메모리 누수와 큰 객체를 식별하고, GC 로그에서 Allocation Rate와 Promotion Rate를 분석합니다. Promotion Rate가 높다면 Young Generation 크기를 늘려(-XX:NewRatio 조정 또는 -Xmn 설정) 객체가 Young에서 충분히 정리되도록 합니다. G1GC의 경우 -XX:MaxGCPauseMillis를 200ms 정도로 설정하고, -XX:G1HeapRegionSize를 조정하여 큰 객체 처리를 최적화합니다. Old Generation으로 넘어가는 객체가 많다면 애플리케이션에서 불필요한 객체 생성을 줄이고, 특히 대용량 컬렉션이나 캐시를 Off-Heap 메모리로 이동하거나 외부 캐시(Redis)로 분리합니다. ZGC나 Shenandoah GC로 전환을 고려하여 pause time을 10ms 이하로 줄일 수 있으며, 이 경우 Heap을 더 크게 설정할 수 있습니다. 또한 String.intern() 남용, ThreadLocal 미정리, 정적 컬렉션 무한 증가 등 메모리 누수 패턴을 점검합니다.
- • Heap Dump 분석으로 메모리 누수 및 큰 객체 식별
- • 높은 Promotion Rate 해결을 위한 Young Generation 크기 증가
- • G1GC 파라미터 튜닝 및 ZGC/Shenandoah 전환 고려
- • 애플리케이션 레벨 객체 생성 최소화 및 Off-Heap 활용
대용량 배치 처리나 실시간 스트리밍 애플리케이션에서 긴 GC pause로 인한 서비스 중단을 방지하는 상황
32GB Heap에서 Full GC 30초가 발생하는 상황에서, Heap 크기를 16GB로 줄이고 서버를 2배로 늘리는 스케일아웃 전략과 비교했을 때 각각의 장단점은 무엇입니까?
Q. 일 10억 건의 API 요청을 처리하는 서비스에서 Redis를 캐시로 사용하고 있습니다. 캐시 히트율은 85%이지만, 인기 상품 이벤트 시작 직후 특정 키에 초당 100만 건의 요청이 집중되면서 해당 Redis 노드의 CPU가 100%에 도달하고 다른 키들의 응답도 느려집니다. 또한 캐시 만료 시점에 DB로 트래픽이 몰리는 Cache Stampede 현상도 발생합니다. Hot Key 문제와 Cache Stampede를 동시에 해결할 수 있는 다층 캐시 아키텍처와 캐시 갱신 전략을 설계하고, 각 계층별 TTL 설정 및 모니터링 방안을 제시하세요.
단일 캐시 계층으로는 Hot Key 문제를 해결하기 어려우며, 요청이 DB까지 도달하기 전에 여러 단계에서 차단해야 합니다.
먼저 애플리케이션 로컬 메모리에 Caffeine 같은 L1 캐시를 두고 Hot Key를 1~5초 정도의 짧은 TTL로 캐싱하여 Redis 요청 자체를 줄입니다. Redis는 L2 캐시로 사용하되, Hot Key는 Redis Cluster의 여러 노드에 복제하거나 Client-side sharding으로 읽기를 분산합니다. Cache Stampede 방지를 위해 Probabilistic Early Expiration 기법을 적용하여 TTL 만료 전에 확률적으로 미리 갱신하거나, 분산 락을 활용한 Single Flight 패턴으로 동일 키에 대한 DB 조회를 한 번만 수행하도록 합니다. 캐시 갱신은 Cache-Aside 패턴 대신 Refresh-Ahead 전략을 사용하여 만료 전에 백그라운드로 미리 갱신하고, Write-Through나 Write-Behind 패턴으로 쓰기 일관성을 보장합니다. Redis의 --hotkeys 옵션이나 CloudWatch 메트릭으로 Hot Key를 실시간 탐지하고, 애플리케이션에서 L1 캐시 히트율과 L2 캐시 히트율을 분리하여 모니터링합니다.
- • 로컬 L1 캐시로 Hot Key 요청을 Redis 전에 차단
- • Probabilistic Early Expiration과 Single Flight로 Cache Stampede 방지
- • Refresh-Ahead 전략으로 만료 전 백그라운드 갱신
- • Hot Key 실시간 탐지 및 다층 캐시 히트율 모니터링
대규모 이커머스 플랫폼의 타임딜이나 한정 상품 판매 시 특정 상품 정보 조회가 폭증하는 상황에서 안정적인 서비스 제공
L1 로컬 캐시를 도입했을 때 서버 간 캐시 불일치 문제가 발생할 수 있습니다. 데이터 정합성이 중요한 경우와 eventual consistency를 허용할 수 있는 경우를 어떻게 구분하고, 각각 어떤 무효화 전략을 사용하시겠습니까?
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!