이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- 백엔드 장애에서 socket 상태, file descriptor, ephemeral port, backlog, retransmit, conntrack을 왜 봐야 하는가?
ss,lsof,nstat,sysctl, cloud NAT metric을 어떤 순서로 확인하는가?- 애플리케이션 metric과 OS 네트워크 증거를 어떻게 연결해 인프라 팀과 이야기하는가?
개요
백엔드 애플리케이션은 결국 OS socket 위에서 동작한다. 코드가 정상이어도 file descriptor limit, ephemeral port 고갈, accept queue overflow, retransmit 증가, conntrack/NAT 한계가 오면 연결 실패와 지연으로 보인다.
- 백엔드 장애 중 일부는 코드가 아니라 OS socket, file descriptor, ephemeral port, backlog 한계에서 터진다.
- outbound 호출이 많으면 NAT port나 ephemeral port 고갈이 생길 수 있다.
- OS 관찰은 app metric과 cloud/network metric 사이의 빈 공간을 채워 준다.
왜 백엔드 개발자가 알아야 하는가
운영 장애에서 “CPU는 낮은데 요청이 느리다”는 말을 자주 만난다. 이때 socket 상태와 kernel counter를 보지 않으면 앱 thread, DB pool, 외부 API, NAT, firewall 중 어디가 병목인지 좁히기 어렵다.
- file descriptor가 부족하면 socket accept나 outbound connect가 실패한다.
- ephemeral port가 부족하면 외부 API나 DB로 새 연결을 만들지 못한다.
- NAT gateway port exhaustion은 앱 로그에서는 외부 API timeout처럼 보인다.
- listen overflow는 LB 502/503이나 connect timeout으로 보일 수 있다.
- retransmit 증가는 packet loss나 congestion을 의심하게 한다.
요청 흐름에서의 위치
OS 관찰은 애플리케이션과 네트워크 사이의 커널 경계를 본다.
application thread/pool
→ HTTP client/server library
→ file descriptor
→ socket state
→ kernel TCP/IP stack
→ NIC / virtual network
→ NAT / LB / firewall이 경계는 앱 로그만으로도, 인프라 대시보드만으로도 놓치기 쉽다. 서버 안에서 본 증거가 둘을 이어 준다.
원리
자주 보는 OS 자원과 신호는 다음과 같다.
| 신호 | 의미 | 장애 증상 |
|---|---|---|
| file descriptor | 프로세스가 열 수 있는 파일/socket 수 | Too many open files, accept/connect 실패 |
| ephemeral port | outbound 연결의 local port | 특정 destination 연결 실패 |
| TCP state | 연결 생명주기 | CLOSE_WAIT, TIME_WAIT, ESTABLISHED 폭증 |
| listen overflow/drop | accept queue 한계 | LB connect timeout, 502/503 |
| retransmit | packet loss/congestion | p99 증가, read timeout |
| conntrack/NAT | stateful network 장비의 연결 추적 | 외부 API timeout, 일부 destination 실패 |
Brendan Gregg의 USE method처럼 Utilization, Saturation, Errors를 보면 좋다. 네트워크에서도 사용량, 포화, 에러 카운터를 나눠야 한다.
핵심 판단 기준
- socket 수는 app server connection, HTTP client pool, DB pool, Redis, message broker를 모두 합친 결과다.
- file descriptor limit은 process limit과 system limit을 모두 본다.
- ephemeral port 고갈은 destination IP/port별로 생길 수 있다. 특히 NAT 뒤에서 많은 outbound 호출을 할 때 중요하다.
- retransmit은 앱 코드 최적화로 해결되지 않을 수 있다. network path, instance type, NIC, congestion을 함께 본다.
- cloud 환경에서는 host 내부 지표와 managed NAT/LB 지표를 같이 봐야 한다.
실무에서 자주 만나는 문제
- DB pool만 보고 HTTP client pool과 OS socket을 놓친다.
- file descriptor limit 때문에 accept 실패가 나는 것을 앱 예외로 본다.
- NAT gateway port exhaustion을 외부 API 장애로 오해한다.
- CLOSE_WAIT 누적을 재시작으로만 완화하고 close leak를 고치지 않는다.
ulimit -n만 보고 systemd service limit을 놓친다.- Kubernetes Pod 안의 socket 상태만 보고 Node conntrack/NAT 한계를 놓친다.
- packet loss로 retransmit이 늘었는데 DB query 최적화만 한다.
- 부하 테스트 client의 ephemeral port 고갈을 서버 병목으로 오해한다.
OS 관찰은 장애 순간에 가장 값어치가 크다. 문제가 지나간 뒤에는 counter가 사라지거나 socket 상태가 정상화될 수 있으므로, snapshot 명령과 대시보드를 미리 준비해야 한다.
디버깅 방법
ulimit -n과 process file descriptor를 확인한다.ss -s로 socket summary를 본다.- cloud NAT gateway metric과 app outbound connection 수를 비교한다.
nstat으로 retransmit, listen overflow/drop을 확인한다./proc/sys/net/ipv4/ip_local_port_range로 ephemeral port 범위를 확인한다.- process별 socket 수를 보고 누가 연결을 잡고 있는지 본다.
- managed NAT/LB라면 provider metric에서 port allocation, connection error, reset count를 본다.
ulimit -n
cat /proc/sys/net/ipv4/ip_local_port_range
ss -s
ss -tan | awk 'NR>1 {count[$1]++} END {for (s in count) print s, count[s]}'
lsof -p "$PID" | wc -l
nstat -az | egrep 'TcpRetransSegs|TcpExtListenOverflows|TcpExtListenDrops|TcpOutSegs|TcpInSegs'Linux 서버 기준 명령이다. macOS나 일부 container image에는 명령이 없거나 출력이 다르므로, 운영 환경에서 사용할 명령을 미리 확인해 둔다.
코드로 확인하기
장애 순간에 복사해서 실행할 수 있는 snapshot script를 만들어 두면 좋다.
#!/usr/bin/env bash
set -euo pipefail
PID="${1:?usage: ./net-snapshot.sh <pid>}"
echo "== limits =="
cat "/proc/${PID}/limits" | egrep 'open files|processes' || true
echo "== socket summary =="
ss -s || true
echo "== tcp states =="
ss -tan | awk 'NR>1 {count[$1]++} END {for (s in count) print s, count[s]}' || true
echo "== fd count =="
ls "/proc/${PID}/fd" | wc -l
echo "== tcp counters =="
nstat -az | egrep 'TcpRetransSegs|TcpExtListenOverflows|TcpExtListenDrops' || true이 스크립트 결과와 같은 시각의 app metric, LB metric, NAT metric, dependency latency를 함께 보면 “앱이 느림”을 더 작은 가설로 나눌 수 있다.
주니어가 자주 하는 오해
- CPU와 메모리가 낮으면 서버 자원은 충분하다고 생각한다.
- socket, file descriptor, ephemeral port, backlog도 서버 자원이다.
- DB pool만 보면 outbound 연결 문제를 다 본 것이라고 생각한다.
- HTTP client, Redis, message broker, NAT port도 별도 자원이다.
- NAT gateway는 무한히 연결을 처리한다고 생각한다.
- destination별 port와 state 한계가 있다.
- container 안에서만 보면 Node나 host network 문제도 알 수 있다고 생각한다.
- 일부 지표는 Node, host, cloud managed layer에서만 보인다.
시니어의 설계 판단 기준
- 앱 metric, OS metric, LB/NAT metric을 같은 dashboard나 runbook에서 연결한다.
- dependency별 connection pool size와 OS/NAT 한계를 함께 capacity planning한다.
- FD limit, backlog, ephemeral port range, conntrack limit은 기본값을 그대로 두지 말고 서비스 특성에 맞게 검토한다.
- 장애 시 snapshot을 자동 수집하거나 on-call runbook에 명령을 둔다.
- 수평 확장은 CPU뿐 아니라 outbound port/NAT 분산 효과도 고려한다.
인프라 협업 포인트
- 인프라 팀에 “외부 API timeout”을 전달할 때 destination, connection count, NAT metric, ephemeral port 사용률을 함께 준다.
- listen overflow가 보이면 instance size, OS sysctl, app worker, LB surge를 함께 검토한다.
- packet loss/retransmit 의심이면 source/destination, AZ/zone, instance type, 시간대, tcpdump 가능 여부를 공유한다.
- 인프라 담당자는 managed layer metric과 host 내부 metric이 서로 다른 곳에 있어 상관관계 맞추기가 어렵다는 고충이 있다.
실전 팁
- 부하 테스트는 서버뿐 아니라 client generator의 ephemeral port와 ulimit도 본다.
- “Too many open files”가 한 번이라도 나오면 FD leak와 limit 둘 다 확인한다.
- 외부 API client pool을 늘릴 때 NAT와 ephemeral port 한계를 같이 본다.
- 재시작으로 socket 상태가 사라지기 전에 snapshot을 남기는 절차가 필요하다.
위험 신호!
Too many open files가 로그에 보인다.- CLOSE_WAIT나 ESTABLISHED가 계속 증가하지만 pool metric과 연결되지 않는다.
- NAT gateway error/port allocation metric을 아무도 보지 않는다.
- listen overflow/drop counter가 증가한다.
- 장애 후 재시작만 하고 OS snapshot을 남기지 않는다.
확인 질문
확인 질문
- CPU가 낮은데 요청이 느리면 어떤 OS 네트워크 자원을 봐야 하는가?
- socket state, file descriptor, ephemeral port, listen overflow/drop, retransmit, NAT/conntrack metric을 본다.
- NAT gateway port exhaustion은 앱에서 어떻게 보일 수 있는가?
- 특정 외부 destination에 대한 connect timeout이나 reset, 외부 API timeout처럼 보일 수 있다.
- OS snapshot을 장애 중에 남겨야 하는 이유는 무엇인가?
- socket 상태와 일부 counter는 시간이 지나거나 재시작하면 사라져 원인 분석 증거가 없어지기 때문이다.