새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.

3 분 소요

기획자, 영업팀 부서인원들도 트래픽이 몰리면 회사 서비스가 멈춘다는 것은 알고 있습니다.

개발자라면 트래픽이 몰리는 경우 “왜?” 서비스가 중지되는지 알아볼 필요가 있다고 생각이 들어서 다시 복습해보려고 합니다.

학습 목표

Spring Boot 단일 서버 환경의 DB 과부화와 응답 지연 문제

  1. 복잡한 조인 쿼리와 트래픽 급증 시 DB 과부하 요인

학습 내용

사용자의 요청이 들어와 응답이 나가는 전체 흐름을 먼저 확인해보겠습니다.

기본적인 요청 처리 흐름

  1. 사용자(브라우저 또는 클라이언트)가 HTTP 요청 전송
  2. 요청이 WAS(Tomcat)의 커넥터로 전달됨
  3. Tomcat은 스레드 풀에서 사용 가능한 스레드 하나가 요청 처리 할당
  4. Spring의 DispatcherServlet이 요청 라우팅 → Controller → Service
  5. Service 로직에서 DB 접근 필요 시 HikariCP 커넥션 풀에 커넥션 요청(@Transactional)
  6. 사용 가능한 커넥션을 HikariCP가 반환 (없으면 대기 또는 예외)
  7. JDBC 드라이버를 통해 MariaDB에 SQL 쿼리 전송
  8. MariaDB에서 쿼리 파싱 → 실행 → 결과 생성
  9. JDBC를 통해 애플리케이션에 ResultSet 반환
  10. 비즈니스 로직에서 결과 가공
  11. 사용한 커넥션을 HikariCP에 반납 (매우 중요)
  12. Spring에서 View 또는 JSON 등 응답 객체 생성
  13. Tomcat 워커 스레드가 HTTP 응답을 클라이언트로 전송
  14. 스레드가 Tomcat 풀에 반환되어 다음 요청 대기

트래픽이 급증하는 상황

트래픽은 서버와 클라이언트 간에 오가는 데이터의 양이나 요청 횟수를 말합니다.

이제 기본적인 흐름에서 트래픽이 많아지는 경우우 어떤 일이 생기는지 알아보겠습니다.

커넥션 수 증가 및 대기

  1. 트래픽이 갑자기 증가하면 3번(Tomcat 스레드 할당) 단계가 급증합니다. 동시에 처리해야 할 요청의 수가 많아지기 때문입니다.

  2. 각 요청(스레드)은 대부분 5번 단계에서 DB 커넥션을 요청합니다.
  3. 커넥션풀은 설정된 maximumPoolSize만큼 커넥션만 관리합니다. 트래픽이 몰리면 이 풀의 커넥션들이 빠르게 소진 됩니다.
  4. 풀의 모든 커넥션이 사용중이면, 새로운 스레드의 DB커넥션 요청은 다른 스레드가 커넥션을 반납하기 전까지 대기합니다.
    대기시간은 application.properties / connectionTimeout으로 하게 됩니다.
  5. DB 도 활성화된 커넥션의 수가 급증하며 실행되는 쿼리의 양이 급증해야합니다.

쿼리 실행 시간 증가

  1. 캐시가 부재인 상황에서 모든 요청이 DB로 직접 쿼리를 날리게 됩니다
  2. 동시에 실행되는 쿼리 수가 폭발적으로 증가합니다.
  3. DB는 사용자의 요청을 처리하기 위해 각 세션 요청마다 별도의 스레드가 담당하게 됩니다.
  4. 운영체제는 데이터베이스에서 생성된 다수의 스레드에 CPU자원을 할당하기 위해 스케줄링을 수행합니다.
    각 스레드는 쿼리를 처리하면서 디스크 I/O를 요청하게 되고, 이때 디스크 스케줄러 역시 요청 순서를 결정합니다.
    이처럼 CPU와 디스크 등 제한된 자원을 여러 스레드가 동시에 사용하려고 하면서, 스케줄링과 대기가 반복되고 시스템 오버헤드가 증가되는 상황이 발생됩니다.

락 결합 심화

  1. 여러 스레드가 동시에 동일한 테이블이나 레코드에 접근하여 데이터를 읽거나 쓸 때 락(Lock)이 발생합니다.
    쓰기 작업은 다른 작업을 블록하는 경우가 많습니다.
  2. 트래픽이 늘어나면 동일한 데이터에 접근하는 쿼리 수가 많아지고, 특히 데이터 변경 작업이 많을 경우 락 경합이 더 심해집니다.
  3. 락에 걸린 쿼리는 앞선 쿼리가 락을 해제하기 까지 무한정 대기하게 되며 여기서 데드락도 발생될 수 있습니다.

트래픽 급증으로 데이터베이스에 대한 활성화된 커넥션 증가와 동시 실행 쿼리 증가, 자원 경합으로 인한 쿼리 실행 시간 지연이 발생하고, 그 과정으로 데이터 락 경합을 야기하여 데이터베이스에 부하가 발생됩니다.

급증한 쿼리 요청은 자원 경합을 발생시키고 동시 요청 수에 비해 응답의 수는 적기에 병목이 됩니다. 즉 활성화된 커넥션 수보다 응답의 수가 낮은 상황입니다.

고갈과 대기

데이터베이스는 이제 요청을 보내도 응답이 늦게오는 상황이 되었습니다.

커넥션 풀 고갈 및 대기

  1. 데이터베이스의 응답이 늦어지면서 Hikari CP의 커넥션 수는 빠르게 0이 됩니다.
  2. 이후에 들어오는 커넥션 요청은 다른 요청이 커넥션을 반납하기 까지 대기하게 됩니다.
  3. 대기 기본 설정이 30초로 가정할 때 대기 시간이 길어지면서 예외가 발생하게 되며 요청 처리가 실패하게 됩니다.

Tomcat 쓰레드풀 대기

  1. 커넥션 풀 고갈에서 1~3번의 과정동안 웹 서버 쓰레드는 계속 점유하게 됩니다.
  2. 데이터베이스 커넥션을 획득한 쓰레드도 데이터베이스 응답을 기다리는 상황입니다.
  3. 데이터베이스 커넥션을 획득하지 못한 쓰레드는 커넥션 획득을 기다리는 상황입니다.
  4. 웹 서버 스레드는 최대 쓰레드 갯수(maxThreads)의 스레드는 모두 대기 상태로 갑니다.
  5. 이제 더 이상 발급하지 못하는 상황이라면 새로운 사용자의 요청은 내부 요청 큐에 쌓이게 됩니다.
  6. 요청 큐 역시 크기 제한이 있으므로 큐가 가득 차면 더이상 요청은 서버에 진입조차 못하고 바로 거부됩니다.

요청 처리 시간 증가 및 타임아웃 발생

  • 데이터베이스 응답 대기
  • 데이터베이스 커넥션 요구 대기
  • 웹 서버 쓰레드 할당 대기

여러 단계에서 병목이 발생하면서 사용자의 개별 요청이 서버에 들어온 이후 전체 시간이 길어집니다.

그러면서 페이지 로딩 지연이 됩니다.

순서도

단일 서버 구조의 한계

단일 서버(모놀리식) 구조는 모든 기능과 데이터 접근이 하나의 애플리케이션 인스턴스 또는 소수의 공유자원(특히 DB)에 집중되는 경향이 있습니다.

댓글남기기