pile·
백엔드·쏘카쏘카·

싱글벙글 회원 서비스 성능 튜닝기

쏘카 어카운트팀이 성수기 프로모션 첫날 트래픽 150% 증가에 응답 시간 1000% 이상 급증이라는 위기를 맞아 20줄 남짓한 코드에서 4가지 성능 문제를 찾아 8배 성능을 개선한 과정을 공유한다. @Transactional readOnly, DB 커넥션 범위 축소, Jackson 반사 오버헤드, Tomcat 스레드 최적화, JVM 웜업이 핵심이다.

핵심 포인트
  • 쓰기 트랜잭션 낭비: 조회 전용 메서드에 @Transactional 기본 적용 → 변경 감지·스냅샷 생성 불필요한 오버헤드, @Transactional(readOnly=true)로 해결
  • DB 커넥션 홀딩: 트랜잭션 시작 시 커넥션 획득 후 외부 API 호출·DTO 변환까지 보유 → 커넥션 풀 고갈, 커넥션 범위를 repository로 한정
  • Jackson 반사 오버헤드: ObjectMapper로 Entity→JSON→DTO 변환 시 리플렉션으로 JIT 최적화 불가·빈번한 GC → 직접 필드 매핑으로 교체
  • Tomcat 스레드 과다: 1 core에 스레드 200개 기본값 → 컨텍스트 스위칭 급증, 성능 테스트로 최적값 30개 확인(RPS 1300, 편차 안정)
  • JVM 웜업: ApplicationReadyEvent에서 주요 API 사전 호출 → 레이지 클래스 사전 로딩 + JIT 핫스팟 유도
상세 정리
  • 트래픽 패턴: 이벤트로 트래픽 150% 증가, 응답 시간 1000% 이상 증가 → 개편 대상 외였던 회원 서비스가 병목
  • readOnly 트랜잭션 장점: 변경 감지·세션 플러시·스냅샷 비활성화 + Master/Slave DB 분기 기준으로 활용 가능
  • DB 커넥션 범위 개선: service 메서드 @Transactional 제거, repository에 readOnly=true 적용 → 외부 API 호출 중 커넥션 미점유
  • 커넥션 수 늘리기의 함정: 커넥션 증가 → 스레드 간 경합·컨텍스트 스위칭 증가로 오히려 성능 저하
  • Jackson 오버헤드 원인: 리플렉션은 JIT 메서드 인라이닝 방해 + 힙에 임시 JsonString 생성 → 빈번한 GC 유발
  • 직접 매핑 효과: 정적 구조로 JIT 최적화 활용, 불필요한 힙 할당 제거
  • Tomcat 스레드 성능 테스트: 200/100/50/30/20개 비교, 1 core 환경에서 30개에서 RPS 1300 + 응답 편차 안정
  • 스레드 vs 코어 불균형: 1 core에 200 스레드는 컨텍스트 스위칭 과부하, vmstat으로 측정 확인
  • JVM 웜업 코드: ApplicationListener 구현, 주요 API 사전 호출로 클래스 사전 로딩 + JIT 핫스팟 유도
  • 최종 결과: 개선 전 대비 약 8배 성능 향상, 응답 편차 2~10ms로 안정화
왜 읽나20줄 조회 코드 하나에서 트랜잭션·DB 커넥션·리플렉션·스레드 풀·JVM 워밍업이라는 5가지 성능 함정을 찾아 8배 개선한 과정이 실제 코드 before/after와 함께 담겨 있다.
쏘카
쏘카 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 백엔드·github-engGitHub Engineering·

    조기 종료를 없애야 벡터화된다 — 메모리 속도 소스 코드 케이스 폴딩

    GitHub의 코드 검색 엔진 Blackbird는 480TB 이상의 소스 코드를 인덱싱하기 전 모든 바이트에 case folding을 적용한다. 이 글은 Rust로 구현한 case folding을 메모리 대역폭 한계(45+ GiB/s)까지 끌어올린 두 가지 반직관적 최적화를 상세히 다룬다. 핵심은 루프 조기 종료(break) 제거로 LLVM 벡터화를 유도하고, UTF-8을 디코딩하지 않고 바이트 공간 산술만으로 fold를 수행하는 것이다.

    #rust#unicode#simd+2