한 줄 정의
핵심 메시지
자바 성능은 마법이 아니라 실증 과학(empirical science) 입니다. 측정 가능한 지표를 정의하고, 통계적으로 다루며, 시스템 전반의 트레이드오프를 인식하는 것이 출발점입니다.
쉽게 말하면
성능 최적화는 영화에 나오는 “고독한 해커가 마법 같은 한 줄을 고쳐서 시스템을 살리는” 그림과 거리가 멉니다.
실제로는 의사가 환자를 진단하는 과정에 가깝습니다. 어디가 어떻게 아픈지(지표) 측정하고, 다른 환자(시스템) 데이터와 비교하고, 가설을 세워 처방한 뒤 다시 검사하는 반복 작업입니다.
게다가 자바는 살아있는 환자입니다. JVM이 런타임에 동적으로 최적화하기 때문에, 같은 코드가 어제와 오늘 다르게 동작할 수 있습니다.
JVM의 동적 실행 특성
자바 코드의 실행 속도는 매우 동적이며, 기본적으로 자바 가상 머신에 따라 달라집니다. 오래된 자바 코드라도 소스 코드를 다시 컴파일하지 않아도 최신 자바 가상 머신에서 더 빠르게 실행될 수 있습니다.
왜 중요한가?
성능 분석이 후순위 문제로 취급되다가 사고가 터지면 “영웅이 등장하는 시나리오”가 펼쳐집니다. 하지만 그때는 이미 늦습니다.
자바 애플리케이션은 클라우드와 분산 시스템으로 옮겨가면서 관심 영역이 계속 넓어지고 있습니다. 단일 JVM의 GC 튜닝만 알아서는 부족하고, 컨테이너·오케스트레이션·네트워크·관측 가능성까지 모두 성능에 영향을 미칩니다.
또 하나 결정적인 차이가 있습니다. 클라우드에서는 비효율이 곧 청구서로 돌아옵니다. 자체 데이터센터 시절에는 자원 낭비가 자본 지출(CapEx)로 묻혔지만, 클라우드는 운영 비용(OpEx)이라 재무 부서의 검토 대상이 됩니다. 효율성과 활용도가 더 이상 엔지니어만의 관심사가 아닌 이유입니다.
이 책이 다루지 않는 것
이 책은 코드에 적용할 성능 팁을 모아 놓은 요리책이 아닙니다. 대신 좋은 성능 엔지니어링을 이루기 위한 핵심 측면 에 집중합니다.
- 소프트웨어 수명 주기 내에서의 성능 방법론
- 성능에 적용되는 테스트 이론
- 측정·통계·도구 사용법
- 분석 기술(시스템과 데이터)
- 기반 기술과 메커니즘
핵심 내용
잘못된 자바 성능 최적화 방법
자바 성능 분야에는 유물 같은 잘못된 조언이 인터넷에 그대로 남아있습니다. 1997~1998년에 작성된 글이 검색 상위에 떠서 수많은 개발자를 잘못된 방향으로 이끈 대표적 사례가 있습니다.
당시에는 메서드 디스패치 성능이 좋지 않아 “작은 메서드를 피하고 단일 거대 메서드를 작성하라”는 조언이 통했습니다. 그런데 현대 JVM은 자동 관리 인라이닝(automatic managed inlining)을 통해 대부분의 호출 지점에서 가상 디스패치를 제거합니다.
문제는 이 조언을 따른 코드가 이제는 JIT 컴파일러와 부조화를 일으켜 오히려 성능 저하를 부른다는 점입니다. 인라이닝 임계값을 넘는 거대 메서드는 JIT가 컴파일을 포기하거나 비효율적으로 처리합니다.
이 사례가 주는 진짜 교훈은 두 가지입니다.
- 정량적·검증 가능한 접근 방식 없이 코드를 만지면 위험합니다.
- 인터넷에서 본 모든 조언을 그대로 믿으면 안 됩니다.
존재하지 않는 것 세 가지
- 자바 가상 머신 성능을 획기적으로 높여주는 ‘마법 같은’ 스위치
- 자바를 더 빠르게 만드는 ‘팁과 트릭’
- 숨겨진 비밀 알고리즘
책 후반부 휴리스틱과 코드 기법에 대한 경고
해당 절로 바로 건너뛰어 기술을 적용하기 전에, 제공된 맥락을 충분히 이해해야 합니다. 이러한 기법들은 올바르게 이해하지 않고 적용하면 득보다 해를 끼칠 수 있습니다.
자바 성능 개요
제임스 고슬링
자바는 블루칼라 언어입니다. 박사 논문용이 아닌 실용적인 일을 위한 언어입니다.
자바의 초기 성능 관점은 “환경이 충분히 빠르다면 원시 성능을 어느 정도 희생하더라도 개발자의 생산성을 높이는 것이 더 중요하다”였습니다. 2005년경 핫스팟이 성숙해지면서 이 절충안이 마침내 깨지고 자바도 고성능 컴퓨팅 환경으로 진입했습니다.
자바의 실용성을 가장 잘 보여주는 특징은 관리되는 서브시스템(managed subsystem) 입니다. 개발자가 저수준 제어를 포기하는 대신 JVM이 알아서 처리합니다. 가장 대표적인 예가 가비지 컬렉션 을 통한 메모리 관리이고, JIT 컴파일·클래스 로딩·스레드 스케줄링도 모두 관리되는 서브시스템에 속합니다.
관리되는 서브시스템의 비용
관리되는 서브시스템은 자바 가상 머신 전반에 걸쳐 존재하며, 이러한 자바 가상 머신 애플리케이션의 런타임 동작에 추가적인 복잡성을 초래합니다.
이 추상화가 생산성을 높여주는 대신, 런타임 동작에 추가적인 복잡성을 가져옵니다. 그래서 JVM 애플리케이션의 동작은 실험 대상으로 다뤄야 합니다. “코드를 읽으면 성능을 알 수 있다”가 자바에서는 통하지 않는 이유입니다.
실증 과학으로서의 성능
자바 가상 머신은 매우 최적화되고 적응력이 뛰어난 플랫폼입니다. JVM이 무어의 법칙과 하드웨어의 비약적 발전을 효과적으로 활용한 덕분에, 위에서 동작하는 프로덕션 시스템은 미묘하고 복잡한 성능 동작을 보입니다.
헨리 페트로스키
컴퓨터 소프트웨어 산업의 가장 놀라운 성과는 컴퓨터 하드웨어 산업이 얻은 꾸준하고 엄청난 성과를 지속적으로 상쇄하고 있다는 것입니다.
엘리 골드랫
명확하게 정의되지 않은 측정은 활용할 수 없습니다.
따라서 성능 튜닝은 다음 단계를 거치는 목표 지향적 반복 프로세스 가 됩니다.
- 원하는 결과를 정의한다 (정량적 목표).
- 기존 시스템을 측정한다.
- 요구사항을 충족하기 위해 필요한 작업을 결정한다.
- 개선 작업을 수행한다.
- 다시 테스트한다.
- 목표가 달성되었는지 확인한다.
여기서 핵심은 정량적 목표 설정 입니다. “빠르게 만들자”는 목표가 아닙니다. “p99 지연 시간을 100ms 이하로 유지한다”가 목표입니다.
자바 성능 측정의 함정
정규 분포 전제가 깨진다
JVM 애플리케이션의 측정값은 종종 정규 분포를 따르지 않습니다. 표준편차나 분산 같은 기본 통계는 결과 분포가 정상적이라는 전제 위에 서 있는데, JVM에서는 그 전제가 깨집니다. 단순한 기법을 적용하면 잘못된 결론을 자주 산출합니다.
이상치(outlier)가 매우 중요한 역할을 합니다. 저지연 거래 시스템이나 티켓 예약 시스템에서 평균 응답 시간이 좋아도, p99나 p99.9가 망가지면 사용자 경험은 무너집니다. 단순히 평균만 보면 중요한 이벤트를 놓치게 됩니다.
측정에는 오버헤드가 있습니다. 빈번한 샘플링은 측정값 자체에 영향을 줍니다(관찰자 효과). 정교한 통계적 접근이 필요하고, 단순한 기법을 적용하면 잘못된 결론을 자주 산출합니다.
성능을 위한 분류 체계
성능을 논의할 때 사용하는 7가지 관측 가능한 지표 입니다. 모든 지표를 동시에 최적화하는 것은 일반적으로 불가능하며, 한 지표를 최적화하면 다른 지표에 불리하게 작용할 가능성이 충분히 존재합니다.
| 지표 | 의미 | 비유(파이프) |
|---|---|---|
| 처리량 (throughput) | 단위 시간당 처리한 작업 수 | 1초 동안 생성된 물의 양 |
| 지연 시간 (latency) | 한 거래의 시작~끝 시간 | 물 한 리터가 파이프를 통과하는 시간 |
| 수용량 (capacity) | 동시에 진행할 수 있는 작업 단위 수 | 파이프 내 저수조 크기 |
| 활용도 (utilization) | 자원이 실제 작업에 쓰이는 비율 | 파이프가 얼마나 채워져 있는가 |
| 효율성 (efficiency) | 처리량 / 사용된 자원 | 같은 물을 보내는 데 든 자원 |
| 확장성 (scalability) | 자원 추가 시 처리량이 어떻게 변하는가 | 파이프를 늘렸을 때 물의 양 변화 |
| 성능 저하 (degradation) | 부하 증가 시 지표 악화 정도 | 입구가 좁아 물이 튀는 정도 |
처리량과 지연 시간의 분리
파이프 비유
파이프 비유의 핵심 통찰은 처리량과 지연 시간이 서로 다른 물리량 이라는 것입니다.
- 지연 시간: 파이프의 길이와 물의 이동 속도의 함수. 직경과는 무관.
- 처리량: 물의 속도와 파이프 단면적에 의해 결정.
굵은 파이프를 깐다고(서버 확장) 한 요청의 지연 시간이 줄어들지는 않습니다. 둘은 독립 차원이고 우선순위부터 정해야 합니다.
처리량 값이 의미 있으려면 참조 플랫폼 설명이 반드시 포함되어야 합니다. “초당 1만 건”은 어떤 하드웨어·OS·소프트웨어 스택에서, 단일 서버인지 클러스터인지, 어떤 거래 단위인지가 함께 명시돼야 비교 가능한 수치가 됩니다.
수용량과 활용도
수용량은 시스템이 병렬로 동시에 진행할 수 있는 작업 단위의 수입니다. 동시 부하가 증가하면 처리량이나 지연 시간에 영향을 미칩니다.
활용도는 자원이 실제 작업에 사용되는 비율인데, 작업 부하 특성에 따라 자원별로 크게 다릅니다.
- 그래픽 처리·암호화 같은 계산 집약 워크로드는 CPU를 거의 100% 쓰지만 메모리는 일부만 씁니다.
- 마이크로서비스 환경에서는 CPU보다 메모리가 더 낭비되고, 네트워크 트래픽이 진짜 병목 인 경우가 많습니다.
- 입구가 좁은 파이프는 본체가 굵어도 전체 활용도가 낮을 수 있습니다(입구 제한).
이게 왜 중요한가? 클라우드 네이티브 환경에서 자원 비용을 결정하는 것은 활용도와 효율성입니다. CPU가 5%만 사용되는 메모리 32GB 컨테이너에 돈을 내는 것은 명백한 낭비입니다.
확장성과 성능 저하
확장성은 자원을 추가할 때 처리량이 어떻게 변하는지로 정의하는 것이 유용합니다. 이상적인 시스템 확장성은 자원 증가에 처리량이 정확히 비례 하는 것입니다.
서버 클러스터를 두 배로 확장했을 때 거래량이 두 배가 되면 “완벽한 선형 확장성”입니다. 하지만 실제로 이 목표는 다양한 부하 상황에서 달성하기 매우 어렵습니다. 보통 자원 범위 일부에서는 선형에 가깝다가, 부하가 높아지면 한계를 만납니다.
성능 저하는 시스템 아키텍처의 견고함에 영향을 받습니다. 어린이 풍선처럼 약한 시스템은 부하가 가해지면 치명적으로 무너지고, 처리량이 0으로 떨어질 수 있습니다. 견고한 시스템은 새는 부분이 생기거나 요청을 거부하는 등 현실적인 저하 시나리오 를 보입니다.
지표들 간의 상관관계
7개 지표는 독립적이지 않습니다. 부하 증가의 영향은 시스템이 최대 성능에 얼마나 가까운지 에 따라 다르게 나타납니다.
- 낮은 활용 상태: 부하를 늘려도 지표가 잘 안 변합니다.
- 과부하 상태: 다른 관측 지표에서 영향이 즉시 드러납니다.
확장성과 성능 저하는 모두 부하 증가에 따른 시스템 동작 변화를 표현하지만, 결정적 차이가 있습니다.
- 확장성: 부하가 증가할 때 자원도 함께 추가 됩니다.
- 성능 저하: 부하는 증가하나 추가 자원이 제공되지 않습니다. 결과적으로 지연 시간 등이 악화됩니다.
직관에 반하는 결과
드물게는 추가 부하가 직관에 반하는 결과 를 초래할 수 있습니다. 예를 들어 메서드가 인터프리터 모드에 머물다가 호출 빈도가 높아져 JIT 컴파일 대상이 되면, 부하가 더 늘었는데 지연 시간이 감소 하는 현상이 나타날 수 있습니다. JIT의 자기 워밍업 효과입니다.
성능 그래프 읽기
성능 엘보 (performance elbow)
증가하는 부하 아래에서 지연 시간이 갑작스럽고 예기치 않게 저하되는 현상입니다.
지연 시간
| .
| .
| .
| . . .
+-----------------> 부하
엘보 직전까지는 안정적이다가 임계점을 넘으면 급격히 무너집니다. 자원 한계에 도달했거나 잠금 경합·큐잉이 폭발하는 지점일 가능성이 높습니다.
거의 선형적인 확장
이상적인 동작에 가까운 패턴입니다. 클러스터에 머신을 추가할 때 처리량이 거의 선형으로 늘어납니다. 세션 친화성이 필요 없는 무상태 프로토콜 확장이 대표 사례입니다.
암달의 법칙(Amdahl’s law)
작업의 일부만 병렬화 가능할 때 도달할 수 있는 최대 속도 향상의 상한 을 정의합니다.
x축이 로그 스케일임에 주의해야 합니다.
| 병렬 처리 비율 | 최대 속도 향상 |
|---|---|
| 75% | 4배 |
| 90% | 10배 |
| 95% | 20배 |
95% 병렬 알고리즘이라도 12배 속도 향상을 얻으려면 32개 프로세서가 필요하고, 코어 수와 무관하게 최대 향상은 20배에 묶입니다. 실무 알고리즘은 직렬 비율이 5%보다 훨씬 높으므로 실제 상한은 더 낮습니다.
실무 함의
“코어를 더 박으면 N배 빨라진다”는 가정은 깨집니다. 병렬화 가능한 비율을 측정하고, 직렬 구간(공유 상태, 동기화, I/O 의존성)을 줄이는 작업이 본질 입니다.
메모리 사용의 톱니형(sawtooth) 패턴
JVM의 GC 하위시스템 때문에 정상적인 애플리케이션은 메모리 사용량이 톱니파 모양 으로 나타납니다. 객체 할당으로 사용량이 올라가다가, GC가 발생하면 사용량이 뚝 떨어지고, 다시 올라가기를 반복합니다.
진단 포인트
- 톱니파가 보이면 건강한 신호 입니다.
- 톱니가 사라지고 메모리가 단조 증가하면 메모리 누수 를 의심해야 합니다.
- 톱니의 진폭이 점점 커지고 GC가 잦아지면 할당 속도가 한계에 가까워졌다는 뜻입니다.
할당 속도(allocation rate)
새로운 객체를 초당 몇 바이트 생성하는지를 나타냅니다. JVM의 주요 성능 지표 중 하나입니다.
JDK 미션 컨트롤(JMC)로 측정하면 시스템의 최대 할당 속도가 보입니다. 책의 벤치마크는 8 GiB/s를 시도했지만 하드웨어 한계 때문에 실제로는 4~5 GiB/s에서 막혔습니다. 할당 속도가 너무 높으면 young GC가 빈번해지면서 처리량이 떨어지는 신호 입니다.
높은 부하에서의 지연 시간 저하
자원 누수와는 다른 종류의 문제입니다. 자원 누수는 부하 증가에 따라 서서히 악화 되는 형태로 나타나지만, 할당 한계에 도달한 시스템은 변곡점을 만나기 전까지 안정 하다가 어느 순간 급격히 악화됩니다.
두 패턴을 구분하는 것이 진단의 시작입니다.
클라우드 시스템의 성능
현대 클라우드 시스템은 거의 항상 분산 시스템입니다. 단일 JVM의 복잡성에 더해 또 다른 수준의 복잡성을 다뤄야 합니다.
분산 시스템 운영자는 다음 같은 사항을 고려해야 합니다.
- 클러스터 내 작업 분배 방식
- 소프트웨어 새 버전·구성의 배포 방식
- 노드 이탈 시 동작
- 신규 노드 추가 시 동작
- 신규 노드가 잘못 구성된 경우의 동작
- 신규 노드가 다른 동작을 보일 때의 영향
- 클러스터 제어 코드 자체의 장애
- 인프라 치명적 장애의 파급
- 의존 인프라 자원 제한 시 확장성 병목
클라우드의 결정적 차이 두 가지
첫 번째, 배포 단위가 JVM 프로세스가 아니라 컨테이너 입니다. 대형 물리 서버 시대와 정반대 모델이고, 컨테이너 내부에서 일어나는 많은 일이 엔지니어에게 보이지 않을 수 있습니다.
두 번째, 클라우드 제공업체 사용 방식이 운영 비용에 직접 반영됩니다. 비효율이나 잘못된 설정이 곧 요금으로 드러납니다. 이는 클라우드의 부상을 이해하는 핵심 관점이기도 합니다.
| 구분 | 자체 데이터센터 | 클라우드 |
|---|---|---|
| 비용 성격 | 자본 지출(CapEx) | 운영 비용(OpEx) |
| 회계 처리 | 자산 | 서비스 비용 |
| 변경 가시성 | 낮음 (이미 산 자산) | 높음 (매월 청구서) |
| 비효율 영향 | 묻힘 | 즉시 드러남 |
| 검토 주체 | IT 부서 | 재무 부서까지 |
클러스터의 동적 특성
가정의 전환
클라우드 클러스터는 시간이 지남에 따라 동적으로 변하는 프로세스 집합 으로 인식해야 합니다. 규모가 커지거나 작아지고, 참여하는 프로세스가 변경됩니다.
전통적인 호스트 기반 시스템: 오래 유지되는 안정적 호스트 그룹. 클라우드 네이티브: 노드는 언제든 사라지고 새로 들어옵니다.
비교 / 트레이드오프
기본 통계 vs JVM 데이터에 맞는 통계
| 항목 | 기본 통계 (평균/표준편차) | JVM에 적합한 접근 |
|---|---|---|
| 전제 | 정규 분포 | 왜곡된 분포, 긴 꼬리 |
| 중요 지표 | 평균, 분산 | 백분위수(p50, p99, p99.9) |
| 이상치 처리 | 노이즈로 간주 | 신호로 간주 |
| 적합한 도메인 | 안정적 시스템 | 저지연 거래, 실시간 시스템 |
확장성 vs 성능 저하
| 차이점 | 확장성 | 성능 저하 |
|---|---|---|
| 부하 변화 | 증가 | 증가 |
| 자원 변화 | 함께 추가 | 추가 없음 |
| 기대 동작 | 처리량 유지/증가 | 지연 시간 악화 |
| 분석 관점 | 자원을 어떻게 활용하는가 | 한계에서 어떻게 무너지는가 |
내 생각
-
“정량적 목표 정의”가 형식이 아니라 본질입니다. “API가 빨라야 한다”가 아니라 “p99 200ms 이하, 동시 5000 요청, 에러율 0.1% 이하”라야 측정·검증·종료 조건이 분명해집니다. SLO·SLI 문화가 이 자리에서 자라납니다.
-
자바 성능에서 가장 자주 잊는 사실은 이상치가 신호 라는 것입니다. 평균 응답시간이 좋아 보이는데 사용자 컴플레인이 들어오면, p99·p99.9를 봐야 합니다. APM 도구가 percentile 기반인 이유가 여기에 있습니다.
-
톱니파 패턴을 “건강의 증거”로 받아들이는 시각 전환이 중요합니다. 메모리가 평탄하다고 안심하면 누수를 놓칩니다. 반대로 톱니가 자주 튀고 가팔라지면 할당 속도가 한계에 가까워졌다는 뜻이라 GC 로그·할당 프로파일을 봐야 합니다.
-
암달의 법칙이 클라우드 시대에 더 가혹하게 작동합니다. 마이크로서비스로 쪼개도 결국 직렬 의존 구간(인증·세션·동기 호출 체인)이 남고, 거기서 전체 처리량이 묶입니다. 분산 추적으로 직렬 구간을 식별하는 것이 실무의 성능 작업입니다.
-
클라우드 비용이 재무 부서 검토 대상이 됐다는 말은, 성능이 곧 비용 관리 라는 의미입니다. FinOps 흐름이 이 변화의 산물입니다. 백엔드 엔지니어가 이제는 GC 옵션만이 아니라 CPU/메모리 request·limit, autoscaling 임계값, p99 기반 캐파 산정까지 책임지게 됩니다.
더 알아볼 것
- 백분위수 기반 SLO 정의와 alerting 전략
- JMC, async-profiler로 할당 속도와 GC 패턴 측정 실습
- 분산 추적으로 직렬 구간(critical path) 식별
- FinOps 관점에서의 자원 활용도 최적화
관련 개념
출처
- 자바 최적화 2판, Ch01 최적화와 성능 정의