3줄 요약
- 『주니어 백엔드 개발자가 반드시 알아야 할 실무지식』 2장을 읽고 성능 진단의 기준 지표를 정리했다.
- 인사이트: 병목을 모른 채 서버를 늘리면 돈만 쓴다. 커넥션 풀도 DB 상태를 보고 늘린다.
- 다음 주는 이월된 jemalloc 프로파일링을 배포한다.
지난주 Try 점검
- 테스트 환경에 jemalloc 프로파일링 배포하기 → 못 했다. 이번 주는 책 정리에 시간을 썼다. 다음 주로 이월.
이번 주의 공부: 『주니어 백엔드 개발자가 반드시 알아야 할 실무지식』 2장
2장 “느려진 서비스, 어디부터 봐야 할까”를 읽었다. 사용자가 실행하고 동작을 볼 때까지 걸리는 시간에는 네트워크 속도, 디스크 속도, 메모리 크기, 클라이언트 CPU까지 여러 지표가 얽혀 있다. 그중 서버 성능과 직결되는 두 가지가 응답 시간과 처리량이다.
응답 시간과 처리량
응답 시간은 API 호출을 처리하는 데 걸리는 시간이다. 전송량에 따라 두 지표로 나눠 측정한다.
| 지표 | 의미 |
|---|---|
| TTFB | 응답 데이터의 첫 바이트가 도착할 때까지 걸린 시간 |
| TTLB | 응답 데이터의 마지막 바이트가 도착할 때까지 걸린 시간 |
데이터 특성과 네트워크 환경에 따라 둘 중 적절한 쪽을 고른다. Speed Matters for Google Web Search에 따르면 100ms 지연에 검색 횟수가 0.2%, 400ms 지연에 0.6% 줄었다. 아마존은 100ms 지연마다 매출이 1% 감소한다고 한다. 응답 시간이 길어지면 트래픽과 매출이 줄어든다.
응답 시간의 대부분은 로직 수행, DB 연동, 외부 API 연동에서 나온다. 비중이 큰 곳부터 개선해야 효과가 크다.
처리량은 단위 시간당 시스템이 처리하는 작업량이다. 동시 5개를 처리할 수 있고 요청당 1초가 걸리면 TPS는 5다. 최대 TPS를 넘는 요청은 대기하고, 사용자에게는 응답 시간 증가로 보인다. 응답 시간 증가는 이탈로 이어지므로 동시 처리량을 늘리거나 처리 시간을 줄이거나, 둘 중 하나를 골라야 한다.
병목과 확장
- 수용 가능한 TPS를 초과하면 성능이 악화된다. 모니터링 도구로 오래 걸리는 작업부터 추적한다.
- 수직 확장은 급한 불을 빠르게 끈다. 수평 확장은 장비 용량 한계를 넘게 해주며, 로드밸런서와 함께 쓴다.
- 무턱대고 서버를 추가하지 않는다. DB가 병목인데 서버를 늘리면 아무것도 나아지지 않는다. 병목 파악이 먼저다.
DB 커넥션 풀
DB 연결 과정이 오래 걸려서 커넥션을 미리 만들어 재활용하는 게 커넥션 풀이다.
- QPS 산정: 1초에 처리할 수 있는 요청 수 = (1s ÷ 쿼리 실행 시간) × 풀 크기. 풀 크기 5, 쿼리 0.1s면 QPS 50.
- 풀 크기: 초당 처리해야 할 요청 수 기준으로 산정하되, DB 상태를 보고 늘린다. DB CPU가 80%에 육박하는 상황에서 풀을 늘리면 부하가 더 커져 쿼리 실행 시간이 급격히 늘어난다. 서버 수평 확장도 결국 커넥션 풀을 늘리는 행위라는 점을 주의한다.
- 커넥션 대기 시간: 커넥션을 얻으려 대기하는 만큼 응답 시간도 늦어진다. 응답 시간이 중요하면 짧게 잡는다. 책은 0.5s~3s를 권한다.
캐시
DB를 확장하면 처리량은 늘지만 응답 시간을 획기적으로 줄이긴 어렵다. DB 확장 없이 처리량과 응답 시간을 함께 개선하려면 캐시를 고려한다.
- 캐시 메모리는 한정적이라 적중률(조회 시도 대비 캐시에 있던 건수)과 삭제 규칙(LRU, LFU, FIFO — 최신 데이터를 자주 조회한다는 가정이 맞을 때)을 정해야 한다.
- 사전 적재: 트래픽 급증이 예상되면 미리 캐싱한다. 적중률이 낮으면 응답 시간이 길어지고 DB 부하도 늘어난다.
- 무효화: 유효하지 않은 데이터를 지우는 게 중요하다. 신선도에 민감하면 리모트 캐시에 보관하고, 민감하지 않으면 유효 시간으로 주기 갱신한다.
| 구분 | 장점 | 단점 |
|---|---|---|
| 로컬 캐시 | 접근이 빠르다 | 용량이 작다 |
| 리모트 캐시 | 용량이 크다 | 접근이 상대적으로 느리다 |
정적 자원도 캐시의 영역이다. 이미지, JS, CSS 같은 정적 자원이 전체 전송 데이터의 80%를 차지한다. 재활용 가능한 데이터는 Cache-Control, Expires로 브라우저 캐시에 맡기고, 요청이 몰려 대역폭이 부족해지면 CDN을 쓴다. CDN은 전송량 과금이라 큰 파일을 올릴 때 주의한다.
그 외
- GC와 메모리: GC는 메모리 관리 부담을 줄여주지만 실행 중 전체가 멈출 수 있다. 메모리 사용량이 높을수록 GC가 활발해진다. 버퍼 대신 스트림을 쓰면 메모리 사용량을 줄일 수 있다 — 지난주
MultipartFile#getBytes()문제와 정확히 겹치는 대목이다. - 응답 압축: 전송 시간은 네트워크 속도와 데이터 크기로 결정된다.
Accept-Encoding,Content-Encoding으로 압축 알고리즘을 정해 데이터 크기를 줄인다. - 대기 처리: 트래픽 폭증 시 리소스를 증설하거나, 수용 가능한 트래픽만 받도록 사용자 대기 시스템을 만든다.
실무에 적용할 점
- DB 커넥션 풀 설정 기준이 늘 고민이었는데 산정 기준(요청 수 기반 + DB 상태 확인)을 얻었다. 다만 아직 DB CPU 폭증이나 커넥션 대기 지연을 실제로 겪어본 적은 없다.
- 브라우저 캐시를 제어하는 방법(
Cache-Control,Expires)을 이해했다.
더 알아볼 것
Speed Matters 논문에서 얻은 인사이트
웹서비스 관점에서 둘.
- 지연 노출이 길어질수록 악화된다.
- 지연을 제거해도 바로 회복되지 않는다.
백엔드 관점에서 셋.
- 스트리밍 응답은 사용성을 실제로 개선한다. 첫 데이터가 빠르냐 느리냐에 따라 이탈률 차이가 크다.
- 그래도 “일단 뭔가 보여줬으니 뒤는 느려도 된다”는 생각은 버리자. 논문은 렌더링 시점마다 지연을 넣었고, 첫 데이터가 빨라도 렌더링 완료가 늦으면 이탈로 이어졌다.
- 지연 회복은 더디다. 문제가 터진 뒤 대응하는 것보다 CI에서 성능 회귀를 잡는 게 훨씬 싸다.
『마이크로서비스 아키텍처 구축』 13.4장 캐시 내용 복기
책의 캐시 내용을 계기로 예전에 읽은 내용을 다시 꺼내 정리했다.
캐시를 쓰는 세 가지 목적
- 성능: 네트워크 호출 비용(홉 + 요청 생성)을 줄인다.
- 확장성: 원본이 경합 지점일 때 읽기 경합을 피한다. DB 읽기 복제본이 이 경우다.
- 견고성: 원본이 죽어도 동작한다. 대신 원본과의 동기화 메커니즘이 잘 갖춰져야 한다.
캐싱 위치
| 위치 | 목적 | 장점 | 단점 |
|---|---|---|---|
| 클라이언트 측 | 지연 시간 개선 + 견고성 | 원본까지 안 간다 | 무효화 수단이 제한적이고, 클라이언트 간 불일치가 생긴다 |
| 서버 측 | 쿼리 비용 감소 | 정교한 무효화 구현이 쉽다 | RTT가 남아 지연 시간 개선은 어렵다 |
| 요청 캐싱 | 응답 시간 개선 | 속도 최적화에 가장 효과적 | 캐싱 대상이 한정적이라 효과 범위가 좁다 |
클라이언트 간 불일치는 notification 기반 무효화나 캐싱 외부화로 완화할 수 있지만, 전자는 완전한 해소가 안 되고 후자는 소유가 불분명해진다.
캐시 무효화 방식
| 방식 | 장점 | 단점 |
|---|---|---|
| TTL | 구현이 간단하다 | 만료 전까지 오래된 데이터를 서빙한다 |
조건부 GET (ETag, Cache-Control) | 조건 충족 시에만 서빙, 재생성 비용 절감 | RTT 횟수는 줄지 않는다 |
| 알림 기반 | 오래된 데이터 제공 가능성을 줄인다 | 구현·검증이 어렵고 민감 데이터가 노출될 수 있다 |
| write-through | 캐시가 항상 최신이다 | 연속 쓰기에 비효율적일 수 있다 (아래 질문) |
| write-behind | 캐시를 버퍼로 써 읽기가 빠르다 | 캐시 내구성이 떨어지면 기록이 손실된다 |
캐싱의 황금 법칙
신선도와 지연 시간의 균형이 어렵다. 캐시가 많을수록 데이터는 오래되고, 클라이언트가 보는 게 최신인지 판단하기 어려워진다. 그래서 캐싱은 주로 성능 최적화 용도로 한정하고, 신선도를 추론하기 쉽도록 가능한 한 적은 위치에 캐시를 둔다. 캐싱 효과는 강력하지만 원본에서 목적지까지의 전체 캐싱 경로를 이해해야 복잡성과 문제를 인식할 수 있다 — 이해 없이 쓰면 캐시 중독이다.
남은 질문
- write-through는 연속 쓰기가 발생하면 정말 비효율적인가? 서버 측 캐시에서 덜 유용하다는 서술의 근거는 뭘까. 책만으로는 확신이 안 서서 더 찾아봐야 한다.
다음 주 Try
- (이월) 테스트 환경에 jemalloc 프로파일링(
LD_PRELOAD)을 배포해 살아있는 1GB의 할당 스택 확인하기 - 『주니어 백엔드 개발자가 반드시 알아야 할 실무지식』 3장 읽고 정리하기