장애를 없애는 게 아니라, 장애를 흡수하는 게 설계다
인프라 운영에서 중요한 건 “장애가 절대 나지 않게 만드는 것”이 아니다. 현실적으로 장애는 언제든 발생한다. 중요한 건 장애가 터졌을 때 서비스 영향 범위를 얼마나 작게 만들 수 있는가, 그리고 사용자가 그 장애를 체감하지 않도록 설계했는가다.
3계층 아키텍처의 가운데에 있는 AP(Application) 서버는 매우 중요한 위치에 있다. 앞단의 웹 요청을 받아 비즈니스 로직을 처리하고, 뒤쪽 DB와 통신하는 허리 역할이기 때문이다. 이 구간이 흔들리면 로그인, 장바구니, 결제, 조회 같은 핵심 기능이 함께 흔들린다.
이 글에서 짚는 건 두 가지다. AP 서버 이중화, 그리고 DB 커넥션 풀링. 별개의 기술처럼 보이지만, 실제로는 “서비스 연속성”을 지키는 하나의 설계 축 안에 있다.
AP 서버 이중화 — “서버 한 대 장애 = 서비스 중단”을 끊어내려면
AP 서버는 사용자의 요청을 처리하는 실행 계층이다. 문제는 이 계층이 단순 계산만 하는 게 아니라, 사용자 상태(Session)와 밀접하게 연결된다는 점이다.
로그인 상태, 장바구니, 주문 진행 단계 같은 정보가 AP 계층에 묶여 있다면, 특정 AP 노드 장애는 곧바로 사용자 경험 저하로 이어진다.
그래서 AP 서버 이중화는 단순히 “서버를 두 대 두는 것”이 아니라, 이 질문에 대한 답을 포함해야 한다.
“한 대가 죽어도 요청은 어디로 보낼 것인가?” “사용자 상태는 어떻게 이어갈 것인가?”
LB + 무상태 설계 — 가장 이상적인 형태
AP 서버를 가능한 한 무상태(Stateless)에 가깝게 설계하는 방식이다. 로드밸런서(LB)가 요청을 여러 AP 서버에 분산하고, 세션 상태는 AP 내부가 아니라 외부 저장소(Redis, DB, 별도 세션 서버)에 둔다.
특정 AP가 내려가도 다른 서버가 요청을 이어받기 쉽고, 오토스케일링이나 컨테이너 기반 운영과도 궁합이 좋다.
다만 공짜는 아니다. 세션을 외부 저장소에 두면 네트워크 홉이 하나 늘어나고, 저장소 장애나 지연이 새로운 병목이 된다. 세션을 어디에 둘 것인가는 단순 구현 선택이 아니라 아키텍처 선택이다.
세션 복제 방식 — 상태를 AP 클러스터가 직접 들고 가는 구조
전통적인 WAS/WebLogic/Tomcat 클러스터 환경에서 많이 쓰던 방식이다. AP 서버가 세션을 직접 보유하면서, 기본 노드(Primary)와 보조 노드(Secondary)가 함께 들고 간다.
최초 요청 시 특정 AP가 세션을 생성하고, 이 정보가 클러스터 내 다른 서버로 복제된다. 이후 클라이언트는 쿠키(JSESSIONID 등)를 통해 원래 노드로 우선 라우팅되고, 해당 노드에 장애가 나면 보조 노드가 세션을 이어받는다.
여기서 주의할 점이 있다.
쿠키 기반 라우팅 정보는 제품마다 구현이 다르다. “JSESSIONID에 항상 기본/보조 서버 정보가 들어간다”고 일반화하면 안 된다. 그리고 세션 크기가 크거나 갱신 빈도가 높으면 메모리 사용량과 클러스터 간 네트워크 트래픽이 함께 증가한다. 장바구니, 인증 정보, 사용자별 캐시 데이터가 무거운 시스템에서는 세션 설계를 잘못하면 AP 이중화 자체가 병목이 된다.
세션 복제는 “마법”이 아니라, 리소스를 지불하고 얻는 가용성이다.
반드시 구분해야 할 3가지
운영 현장에서 자주 헷갈리는 개념이다.
- Load Balancing — 요청을 여러 AP 서버로 분산. 이것만으로 세션 연속성이 보장되지는 않는다.
- Sticky Session — 같은 사용자를 가급적 같은 서버로 보내는 방식. 해당 서버가 죽으면 세션 손실 가능성이 있다.
- Session Replication / External Session Store — 세션 자체를 다른 노드나 외부 저장소에서 이어받는 방식. 이때 비로소 장애 시 세션 연속성을 기대할 수 있다.
LB는 분산, Sticky는 고정, Replication은 연속성 보존. 이 셋을 섞으면 장애 대응 설계가 흔들린다.
DB 커넥션 풀링 — 빠른 시스템은 “연결을 잘 만드는” 게 아니라 “연결을 재사용”한다
AP 서버는 결국 DB와 통신하게 된다. 가장 비효율적인 방식은 요청이 들어올 때마다 DB 연결을 새로 만들고, 작업이 끝날 때마다 완전히 끊어버리는 것이다.
DB 연결 하나를 맺는 데는 생각보다 비용이 크다. TCP 연결 수립, 인증, 세션 생성, 자원 할당이 반복된다. 트래픽이 많은 환경에서 이걸 매 요청마다 반복하면, 실제 SQL 처리 이전에 연결 비용으로 성능을 소모한다.
GET과 CLOSE의 진짜 의미
커넥션 풀은 AP 서버가 미리 일정 수의 DB 연결을 확보해 두고, 필요할 때 빌려 쓰고 반납하는 구조다.
애플리케이션은 필요할 때 풀에서 연결을 가져오고(GET), 작업이 끝나면 연결을 닫는다(CLOSE). 하지만 이 CLOSE는 실제 물리 연결 해제가 아니라 풀에 반환하는 동작이다.
애플리케이션 입장에서는 “닫았다”지만, 시스템 입장에서는 “다음 요청이 재사용할 수 있게 돌려놨다”에 가깝다. 이 구조 덕분에 연결 생성/해제 비용을 매번 치르지 않고, 응답 시간과 처리량이 안정적으로 유지된다.
풀이 바닥나면 벌어지는 일
커넥션 풀은 무한하지 않다. 동시에 요청이 몰리면 풀에 있는 모든 연결이 사용 중 상태가 된다.
유휴 연결이 있으면 즉시 할당 → 없으면 최대 풀 크기까지 추가 생성 시도 → 최대치 도달 시 대기 큐에서 반환 대기 → 대기 시간 초과 시 timeout 에러.
여기서 중요한 건, DB 자체는 살아 있어도 “커넥션을 못 빌려서 장애처럼 보이는 상황”이 발생한다는 점이다. 커넥션 풀 장애는 DB 엔진 문제가 아니라, AP 동시 처리량 초과, 커넥션 반환 누락(Connection Leak), 느린 SQL로 인한 연결 점유 시간 증가, DB 세션 한도와 앱 풀 설정 불일치에서 시작되는 경우가 많다.
현장에서 자주 놓치는 설계 포인트
“최초값 = 최댓값”은 만능 정답이 아니다. 예측 가능한 고정 트래픽에서는 초기 풀을 충분히 확보하는 게 유리하다. 하지만 DB 세션 한도가 빡빡한 대규모 환경에서 무조건 initial=max로 두면, 기동 시점에 DB에 과도한 세션을 선점해 전체 안정성을 해칠 수 있다. 풀 설정은 앱 단독 문제가 아니라 DB와의 합의값이다.
방화벽 idle timeout은 반드시 확인해야 한다. 중간에 방화벽이나 NAT 장비가 있으면, 오래 사용되지 않은 세션을 장비가 먼저 정리해 버린다. AP 서버의 커넥션 풀은 그 연결을 아직 살아 있다고 믿는데, 실제 사용 시점에 first packet 실패가 터진다. validation query, keepalive 주기, max lifetime, idle timeout, 방화벽 세션 타임아웃을 함께 봐야 한다. 원칙은 간단하다. “네트워크 장비가 세션을 끊기 전에, 애플리케이션이 먼저 검증하거나 정리해야 한다.”
커넥션 누수(leak) 감지는 필수다. 풀 장애의 상당수는 풀이 작아서가 아니라, 애플리케이션이 연결을 제때 반납하지 않아서 생긴다. 예외 처리 누락, 트랜잭션 종료 누락이 원인인 경우, 겉보기에는 DB가 느린 것처럼 보여도 실제 원인은 애플리케이션이다.
커넥션 풀은 “성능 기능”이자 동시에 “보호 장치”다. 애플리케이션 스레드 수가 많다고 해서 DB 세션을 무한정 열면 DB가 먼저 무너진다. 적절한 풀 크기와 timeout, 큐잉 정책은 “처리 가능한 만큼만 DB에 보내는 장치”다. 커넥션 풀은 단순 최적화가 아니라 백엔드 보호 설계다.
AP 이중화와 커넥션 풀, 결국 하나의 그림
AP 서버 이중화의 목적은 서버 대수를 늘리는 게 아니다. 장애가 발생해도 요청 처리를 이어가고, 필요하면 사용자 상태까지 보존하는 데 있다. 커넥션 풀의 목적도 단순히 DB 접속을 빠르게 하는 게 아니다. 연결 생성 비용을 줄이고, DB가 감당 가능한 범위 안에서 요청을 제어하는 데 있다.
무중단 서비스는 “장애를 막는 기술”이 아니라, 장애가 발생해도 서비스 전체가 무너지지 않게 만드는 설계의 합이다.
그러면 질문 하나. 지금 운영하고 있는 시스템에서 AP 한 대가 갑자기 내려가면, 세션 연속성은 보장되는가? 커넥션 풀 timeout은 방화벽 idle timeout보다 짧게 잡혀 있는가?

