Chapter 4

용량 안티패턴과 패턴

  • 4.1 Resource Pool Contention
  • 4.2 AJAX Overkill
  • 4.3 Excessive JSP Fragments
  • 4.4 Overstaying Sessions
  • 4.5 HTML Bloat과 Reload Button
  • 4.6 Pool Connections
  • 4.7 Pre-compute Content
  • 4.8 Tune the GC
  • 4.9 용량 패턴의 종합 전략

코드 한 줄 한 줄은 문제없어 보이는데 규모가 커지면 시스템을 질식시키는 안티패턴들이 있어. 이걸 알아야 같은 하드웨어로 더 많은 일을 할 수 있지.

**리소스 풀 경합(Resource Pool Contention)**이 대표적이야. DB 커넥션 풀 크기가 10인데 동시 요청이 50개면 40개가 대기해야 하잖아. 더 심각한 건 커넥션 리크 — 예외 발생 시 커넥션을 반환 안 하면 풀의 유효 크기가 점점 줄어들어. 10개짜리 풀이 3개만 남으면 경합은 극심해지지. 프론트엔드도 문제야. AJAX가 등장하면서 페이지 하나 로드하는데 API 호출이 20~30건씩 나가거든. 사용자 1명이 서버에 30건, 1000명이면 3만 건. 거기에 매 3초마다 찔러보는 폴링까지 더하면 사용자가 아무것도 안 해도 서버에 부하가 계속 가는 거야.

추상화에는 비용이 있다는 것도 잊으면 안 돼. 서버사이드 렌더링에서 컴포넌트를 너무 잘게 쪼개면 렌더링 비용이 폭증하고, 마이크로서비스에서 하나의 페이지를 구성하기 위해 10개 서비스를 호출하는 것도 같은 맥락이야. 코드의 모듈화는 좋지만 런타임에서 각 호출이 리소스를 먹는다는 걸 잊으면 안 돼.

세션 문제도 반복할 가치가 있어. 기본 세션 타임아웃 30분이면, 사용자가 5분 쇼핑하고 떠나도 세션은 30분 동안 메모리에 남아. 실제 활성 시간의 6배나 리소스를 차지하는 거야. 그리고 페이지가 느리면 사용자는 새로고침을 누르는데, 서버 관점에서 이전 요청은 취소되지 않아. 느린 페이지에 10명이 3번씩 새로고침하면 서버엔 30건이 쌓이고 — 악순환이야. 서버가 느리면 새로고침, 부하 증가, 더 느려지고, 더 많은 새로고침...

이 안티패턴들에 대응하는 용량 패턴을 보자. **커넥션 풀링(Pool Connections)**은 이미 널리 쓰이지만, "쓰면 된다"가 아니라 "잘 설정해야 한다"가 핵심이야. min size가 너무 작으면 트래픽 몰릴 때 새 커넥션 만드느라 지연되고, max size가 너무 크면 DB에 부하가 몰려. 대기 타임아웃은 Fail Fast 원칙에 따라 짧게, 유효성 검사(test-on-borrow)로 죽은 커넥션을 안 쓰게 하고, 유휴 타임아웃으로 방화벽이 끊은 커넥션을 정리해야 해. HTTP 커넥션 풀도 마찬가지로 Keep-Alive와 함께 쓰면 외부 호출 지연을 크게 줄일 수 있어.

**미리 계산하라(Pre-compute Content)**는 철학적으로 시간을 공간으로 바꾸는 전략이야. 상품 상세 페이지가 매번 DB 조회해서 렌더링할 필요가 있을까? 상품 정보가 바뀔 때만 HTML을 재생성하고 CDN에서 서빙하면 돼. 서버 재시작 후 캐시가 비면 콜드 스타트 문제가 생기니까 캐시 워밍으로 자주 조회되는 데이터를 미리 올려놓고, 대시보드 통계도 실시간 집계 대신 배치로 사전 계산해둬. 단, 캐시 무효화(invalidation) 전략이 반드시 따라와야 해 — "캐시 무효화는 컴퓨터 과학에서 가장 어려운 두 가지 문제 중 하나"라는 말이 괜히 있는 게 아니거든.

JVM 기반이라면 GC 튜닝도 빠질 수 없어. 8GB 힙에서 Full GC가 10초 걸리면 그 동안 모든 요청이 멈추잖아. Young Gen 크기를 조절하고, G1GC나 ZGC 같은 일시정지 짧은 알고리즘을 쓰고, GC 로그를 분석해서 패턴을 파악해야 해. 근데 핵심은 측정 없이 하지 마라는 거야. 감으로 파라미터 바꾸면 더 나빠질 수 있거든. 리소스 효율 관리, 사전 계산, 런타임 튜닝 — 이 셋을 조합하면 같은 하드웨어로 2~10배 더 많은 트래픽을 처리할 수 있어.


정리

4장 읽고 기억할 거 세 가지:

  1. 리소스 풀 경합은 용량의 사일런트 킬러야 — 커넥션 풀 사용량을 모니터링하고, 리크 방지하고, 대기 타임아웃을 설정해.
  2. 미리 계산하고 캐시해 — 실시간 계산을 줄이는 게 용량 확보의 가장 효과적인 방법이야. 단, 캐시 무효화 전략을 반드시 함께 설계해.
  3. GC 튜닝은 측정 기반으로 — 프로파일링과 GC 로그 분석 없이 파라미터를 바꾸지 마. 감으로 하면 더 나빠져.