스프링 개발자/Spring & Backend

DB 부하 vs App 부하를 나누는 기준

르무엘 2025. 12. 15. 19:35

DB 부하 vs App 부하를 나누는 기준은 “병목이 데이터 접근 계층(I/O, 락, 플랜) 에 있냐, 아니면 애플리케이션 계층(CPU, GC, 스레드, 외부호출) 에 있냐”로 쪼개면 됩니다. 실무에서는 아래 체크리스트로 거의 판별돼요.

1) 지표로 1차 분류 (가장 확실)

DB 부하 쪽 신호

DB CPU가 높고, slow query / p95 쿼리시간이 튐

connections 증가 + lock wait, deadlock, buffer/cache miss, IO wait 상승

특정 테이블/인덱스가 핫스팟(인덱스 스캔 폭증, Full Scan, Sort/Hash Spill)

App은 “DB 응답 기다리느라” 스레드가 묶임 (thread dump에 JDBC wait 다수)


App 부하 쪽 신호

App CPU 높음, DB 쿼리는 빠른데 전체 API 응답이 느림

GC 시간/빈도 증가, heap pressure, allocation rate 폭증

스레드 풀 고갈(서버 worker/thread pool, async queue, connection pool은 여유인데 app 스레드가 바쁨)

직렬화/역직렬화(JSON), 템플릿 렌더링, 암호화/압축, 대용량 객체 생성이 원인

외부 API 호출(latency) 때문에 app에서 대기(HTTP client wait)가 많음


2) “시간 분해”로 나누기 (APM/로그로 쪼개면 끝)

요청 1건을 아래로 쪼개서 비율을 보면 됩니다.

T_total = T_app + T_db + T_external + T_queue

여기서 T_db(쿼리 수행 + 결과 전송)가 p95의 대부분이면 DB 부하

T_app(비즈니스 로직/변환/검증/렌더링/암호화)가 대부분이면 App 부하


실무 팁: SQL 실행시간(ms) + rows + 호출횟수 를 요청 단위로 남기면 “DB가 문제인지, 호출을 너무 많이 하는지(N+1)”가 바로 보여요.

3) 병목 형태로 구분 (패턴)

DB가 병목인 대표 패턴

트래픽 증가에 따라 쿼리 시간도 같이 증가(락/IO/플랜 문제)

“동일 데이터”를 많이 조회/갱신(핫로우) → 락 대기

인덱스 부재/잘못된 조건 → Full Scan

트랜잭션 길게 잡음 → 락 장기화


App이 병목인 대표 패턴

DB는 여유인데 WAS CPU/GC가 먼저 터짐

객체 매핑/JSON 변환/대용량 리스트 처리로 CPU 소모

동기 외부호출이 많아 스레드 대기 증가(타임아웃/재시도 폭증)

로깅 과다(특히 debug, 큰 payload 출력)


4) “풀 고갈”로 더 명확히 보기

DB Connection Pool 고갈(active=max, wait time 증가) + DB 쿼리 느림 ⇒ DB 병목 또는 쿼리/트랜잭션 설계 문제

서버 스레드 풀 고갈(Tomcat threads busy) + DB는 빠름 ⇒ App/외부호출/락(앱단 synchronized 등) 병목


5) 결론적으로 쓰는 실무 기준 한 줄

DB 부하: “DB에서 시간이 소비된다(쿼리/락/IO/플랜).”

App 부하: “DB 밖에서 시간이 소비된다(CPU/GC/스레드/외부호출/변환).”


LIST