왜 2번 WAS에서만 Broken Pipe가 발생했을까?

· August 04, 2026 · 15 min read

얼마 전 옆자리 팀원으로부터 다급한 도움 요청을 받았습니다. 최근 다른 시스템을 인수인계받아 운영 중인데, APM(모니터링 툴)에서 이중화된 WAS 중 유독 2번 서버에서만 지속적으로 Broken Pipe (ClientAbortException)가 발생한다는 것이었습니다.

팀원은 인프라 설정이나 웹 서버 구성을 깊게 다뤄보지 않아 원인 파악에 어려움을 겪고 있었고, 함께 원인을 추적하며 해결했던 과정을 정리해 보았습니다.

1. 현상 파악 및 문제 정의

가장 먼저 APM 도구인 InterMax(인터맥스)를 확인했습니다.

  • 1번 WAS: 600초가 넘어가는 장시간 배치 API 호출도 에러 없이 정상 처리 중
  • 2번 WAS: 306초, 440초, 442초, 502초 등 약 300초(5분)를 초과하는 시점에 예외 발생
org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe

Broken Pipe란?

클라이언트가 서버로 요청을 보낸 뒤, 서버가 응답을 완료하기 전에 클라이언트 측(혹은 중간 프록시/LB)에서 타임아웃이나 네트워크 이슈로 먼저 커넥션을 닫아버려 발생합니다. 서버가 이미 끊어진 소켓에 응답 데이터를 쓰려고(write) 할 때 커널 레벨에서 파이프가 끊겼음을 알리는 에러입니다.

초기 가설

  1. 타임아웃 불일치 가설: 1번 WAS 앞단의 타임아웃은 600초 이상으로 잡혀 있고, 2번 WAS 앞단의 타임아웃만 300초로 설정되어 있을 것이다.
  2. 라우팅 불일치 가설: L4/LB나 Web Server 레벨에서 1번과 2번 서버로 들어가는 트래픽 경로의 타임아웃 설정이 다를 것이다.

2. 인프라 아키텍처 분석

정확한 트래픽 흐름을 파악하기 위해 AWS EC2 인스턴스와 엔터프라이즈 WAS 미들웨어인 RENA(레나) 관리 콘솔을 확인했습니다.

인프라 구조도

                    [ 외부 클라이언트 (유저 / 타 시스템) ]
                                │
                                ▼
┌─────────────────────────────────────────────────────────────┐
│    1차 로드밸런서 (AWS ALB / NLB / L4 스위치)                    │
│    • EC2 #1과 EC2 #2로 트래픽을 분산 (Random / Round-Robin)     │
└─────────────────────────────────────────────────────────────┘
                                │
        ┌───────────────────────┴───────────────────────┐
        ▼                                               ▼
┌───────────────────────────────┐ ┌───────────────────────────────┐
│          EC2 #1 노드          │ │          EC2 #2 노드            │
│                               │ │                               │
│      ┌─────────────────┐      │ │      ┌─────────────────┐      │
│      │   WEB 1 서버     │      │ │      │   WEB 2 서버     │      │
│      └─┬─────────────┬─┘      │ │      └─┬─────────────┬─┘      │
│        │             │        │ │        │             │        │
│AJP (1) │     AJP (2) │        │ │        │ AJP (3)     │ AJP (4)│
│(Local) │     (Cross) │        │ │        │ (Cross)     │ (Local)│
│        │             │        │ │        │             │        │
│        ▼             └────────┼─┼────────┼──────┐      ▼        │
│   ┌─────────┐                 │ │        │    ┌─┴─────────┐     │
│   │  WAS 1  │ ◄───────────────┼─┼────────┘    │  WAS 2    │     │
│   │ (Tomcat)│                 │ │             │ (Tomcat)  │     │
│   └─────────┘                 │ │             └───────────┘     │
│                               │ │                               │
│      * RENA Manager/Agent     │ │      * RENA Manager/Agent     │
└───────────────────────────────┘ └───────────────────────────────┘

구조를 확인해 보니 다음과 같은 사실을 알 수 있었습니다.

  1. WEB 서버(Apache 기반)와 WAS(Tomcat 기반) 간에는 AJP(Apache JServ Protocol) 통신을 사용하고 있다.
  2. WEB 1과 WEB 2는 각각 WAS 1, WAS 2를 모두 인지하고 로드밸런싱(mod_jk worker)을 수행하고 있다.

즉, 트래픽이 특정 WAS로 1:1 고정 매핑되는 구조가 아니라 완전한 교차 분산 구조였습니다.

3. . 설정 검증: 미궁으로 빠진 타임아웃

처음 웹 서버 설정 파일을 열었을 때, 2번 WEB 서버의 HTTP 타임아웃이 300초로 설정되어 있는 것을 확인했습니다.

"옳다구나! 2번 웹 서버 타임아웃이 300초라 Broken Pipe가 터진 거였네. 그럼 1번 웹 서버는 타임아웃이 훨씬 길게(600초 이상) 잡혀 있겠구나!"

드디어 원인을 찾았다고 확신하며 곧바로 1번 WEB 서버의 설정 파일을 열었습니다.

1차 반전: 1번도, 2번도 똑같이 300초?

하지만 결과는 허탈했습니다. 1번 WEB 서버의 HTTP 타임아웃도 똑같이 300초였습니다. 게다가 앞서 확인했듯 WEB 1, 2 서버는 각각 WAS 1, 2 서버로 트래픽을 랜덤하게 교차 분산하고 있었습니다.

웹 서버의 단순 타임아웃 설정만으로는 1번 WAS와 2번 WAS의 동작 차이를 설명할 수 없는 구조였습니다.

2차 가설: mod_jk Worker별 타임아웃이 다른 걸까?

'웹 서버에서 두 WAS로 랜덤 분산하면서도, 특정 WAS에 대해서만 타임아웃을 다르게 줄 수 있는 방법이 있나?'

머리가 복잡해져 아파치 미들웨어 구조를 다시 파고들었습니다. RENA 웹 서버는 Apache 기반이고, Tomcat과의 AJP 연동에 mod_jk 모듈을 사용합니다. 찾아보니 workers.properties 파일에서 로드밸런서 worker를 정의할 때 WAS 인스턴스별로 타임아웃을 다르게 부여할 수 있는 reply_timeout 옵션이 존재한다는 것을 알게 되었습니다.

worker별로 개별 타임아웃을 다르게 설정할 수 있는 예시

# worker별로 개별 타임아웃을 다르게 설정할 수 있는 예시
worker.lb_was.type=lb
worker.lb_was.balance_workers=was1, was2

# WAS 1번 설정 (타임아웃 60초)
worker.was1.type=ajp13
worker.was1.host=127.0.0.1
worker.was1.port=8009
worker.was1.reply_timeout=60000

# WAS 2번 설정 (타임아웃 600초)
worker.was2.type=ajp13
worker.was2.host=10.0.2.20
worker.was2.port=8009
worker.was2.reply_timeout=600000

"아, worker 설정에서 1번 WAS와 2번 WAS의 AJP 응답 대기 시간을 다르게 묶어뒀겠구나!"

희망을 품고 두 서버의 workers.properties 파일을 열어보았습니다.

하지만… 해당 파일에는 타임아웃 관련 커스텀 설정이 아예 없었습니다. 기본값 그대로 동작하고 있었던 것이죠.

멘붕의 순간: 모든 설정이 똑같은데 왜?

이 시점부터 머리가 뜨거워지기 시작했습니다. 현재 아키텍처에서 타임아웃에 관여할 수 있는 레이어는 총 4가지였습니다.

  1. L4 / ALB 타임아웃: 공통 인프라 영역이라 특정 WAS만 골라서 다르게 동작할 리 없음.
  2. WEB 서버 HTTP 타임아웃: 1번, 2번 노드 모두 300초로 동일
  3. WEB mod_jk Worker AJP 타임아웃: 1번, 2번 노드 모두 설정값 없음 (기본값 동일)
  4. WAS Tomcat 커넥터 타임아웃: 1번, 2번 노드 모두 300초로 동일

"설정 파일만 수정해두고 실제 프로세스 재시작(Reload)을 안 해서 반영이 안 된 건가?"

혹시나 싶어 확인해봤지만, 최근 정기 PM 작업으로 두 서버 모두 깔끔하게 재기동된 상태였습니다.

인프라 경로, 웹 서버 설정, AJP 설정, WAS 커넥터 설정까지 모든 조건이 1번과 2번 완전히 동일한데, 대체 왜 2번 WAS에서만 300초에 에러가 터지고 1번 WAS는 600초 넘게 멀쩡히 돌고 있는 것인가?

혼자만의 생각에 갇혀 도무지 답이 나오지 않아, 결국 시스템 전임자와 주변 개발자분들께 SOS를 치며 자문을 구해보기로 했습니다.

나 : EC2 2대에서 각 EC2가 웹서버와 와스를 갖고있고, 각 웹서버가 와스 두 개를 다 알고 있는 상태에서 랜덤으로 트래픽을 보내는데, 이 상황에서 2번 와스만 브로큰 파이프가 터지는 상황인데 어떤걸 의심할 수 있을까요?

전임자 : 그거 젠킨스 스케줄러인데... 이유는 모르겠어요

4. 시점의 전환: 호출 주체(Jenkins) 확인

시스템 내부에서 자체적으로 도는 스케줄러 배치인 줄 알았으나, 외부 젠킨스(Jenkins)에서 시스템의 배치 트리거 API를 HTTP 호출하고, 해당 API가 타 ERP 시스템과 통신하는 구조라는 것을 알게 되었습니다.

바로 젠킨스 빌드 콘솔 로그를 확인했습니다.

젠킨스 로그

HTTP/1.1 502 Bad Gateway
Connection: close
Server: ...

놀랍게도 젠킨스 입장에서는 1번 WAS, 2번 WAS 가릴 것 없이 300초가 넘어가는 호출에 대해 모두 502 Bad Gateway를 반환받고 있었습니다.

실제 동작 흐름

Jenkins (배치 트리거 호출)
   │
   │ [HTTP Request]
   ▼
L4 / ALB (타임아웃 300초)
   ▼
WEB (Apache mod_jk, 타임아웃 300초)
   │
   │ [AJP]
   ▼
WAS (Spring Boot / Tomcat)
   │ 
   │ ──> 타 ERP 시스템 연동 및 데이터 처리 진행 (약 400~600초 소요)
   │     (WAS 내부 비즈니스 로직은 백그라운드에서 정상 완료됨)
   │
   ▼
[300초 도달 시점]
L4 / WEB 계층에서 타임아웃으로 Jenkins와의 커넥션 종료 -> Jenkins는 502 수신
   │
[처리 완료 후 (400초~)]
WAS가 응답(Response)을 반환하려 하지만, 이미 AJP/HTTP 소켓이 끊어져 있음
   │
   └──> WAS 레벨에서 'Broken Pipe (ClientAbortException)' 발생!

결국 1번 WAS와 2번 WAS 모두 동일하게 Broken Pipe가 발생하고 있었던 것입니다.

5. 근본 원인(Root Cause): APM 설정 차이

그렇다면 가장 큰 의문점이었던 "왜 InterMax 모니터링 화면에는 2번 WAS만 에러가 잡혔는가?"에 대한 답을 찾아야 했습니다.

1번 인스턴스와 2번 인스턴스의 InterMax Agent 설정 파일(intermax.conf 등)을 내려받아 diff로 비교해 보았습니다.

# [1번 WAS InterMax 설정]
EXCLUDED_EXCEPTION = org.apache.catalina.connector.ClientAbortException, ...

# [2번 WAS InterMax 설정]
EXCLUDED_EXCEPTION = X

원인은 허탈할 정도로 단순했습니다. 과거 어떤 이유에선지 1번 WAS의 모니터링 에이전트에만 ClientAbortException이 예외 무시(Exclude) 처리되어 있었고, 2번 WAS는 기본 상태로 남아 있어 에러가 그대로 수집되었던 것입니다.

6. 조치 및 개선 사항

단기 조치 (인프라 및 모니터링 정합성 동기화)

  • 모니터링 설정 동기화: 1번, 2번 WAS의 InterMax 에이전트 설정을 동일하게 맞춰 관측 정합성을 확보했습니다.
  • 타임아웃 임계치 상향: 300초를 초과하는 대용량 인터페이스 처리를 위해 L4, Apache HTTP, mod_jk worker, WAS 커넥터 타임아웃을 안전한 범위(예: 900초 이상)로 상향 조정했습니다.

7. 마치며

이번 트러블슈팅을 통해 얻은 교훈은 세 가지입니다.

  1. 모니터링 도구의 데이터를 맹신하지 말 것: 모니터링 툴은 강력하지만, 에이전트 설정 하나로 수집 결과가 왜곡될 수 있습니다. 의심스러운 현상이 있다면 메트릭 도구뿐 아니라 실제 클라이언트/서버의 원본 액세스 로그 및 에러 로그를 교차 검증해야 합니다.
  2. 트래픽의 시작부터 끝까지 End-to-End로 파악할 것: 중간 계층(WAS/WEB)부터 들여다보기보다, 요청을 시작하는 클라이언트(Jenkins)부터 최종 응답까지의 흐름을 먼저 정의하고 접근하면 문제 범위를 훨씬 빠르게 좁힐 수 있습니다.
  3. 이중화 환경의 Configuration Drift 경계: 동일한 역할을 수행하는 인스턴스 간 설정 불일치(Drift)는 언제든 미스터리한 장애 원인이 될 수 있습니다. IaC(코드형 인프라)나 형상 관리 자동화의 중요성을 다시 한번 체감했습니다.
@Pang
안녕하세요! 왜 이렇게 까지 하지를 담당하는 5년차 프론트엔드 개발자입니다!