본문 바로가기
서버연구 블로그 서버연구 블로그

TCP Backlog와 서버 Connection 처리 구조 이해하기

읽는 시간 약 18분

서버에서 “동시접속자가 많아졌다”는 말은 단순히 ESTABLISHED Connection 수가 늘었다는 뜻만은 아니다.

실제 TCP Connection은 클라이언트의 SYN 패킷이 도착한 순간부터 바로 애플리케이션으로 전달되지 않는다. Linux Kernel 내부의 여러 단계를 거쳐 3-Way Handshake가 완료되고, 이후 accept() 호출을 통해 애플리케이션으로 넘어간다.

이 과정에서 핵심이 바로 TCP Backlog다.

Linux TCP 서버에서는 Handshake가 끝나기 전의 요청과 Handshake가 끝났지만 아직 애플리케이션이 가져가지 않은 Connection이 서로 다른 Queue에서 관리된다.

이번 글에서는 SYN 도착부터 애플리케이션 전달까지의 흐름과 Backlog 장애 분석 방법을 정리한다.

1. TCP 서버의 Connection 처리 흐름

TCP 서버 프로그램은 일반적으로 다음 순서로 소켓을 준비한다.

socket()
   ↓
bind()
   ↓
listen()
   ↓
accept()

socket()으로 소켓을 만들고 bind()를 통해 IP와 Port를 연결한다.

이후 listen()을 호출하면 해당 소켓은 클라이언트의 Connection 요청을 받을 수 있는 Listening Socket이 된다.

여기서 중요한 부분이 있다.

TCP 3-Way Handshake 전체를 Nginx나 애플리케이션 프로세스가 직접 처리하는 것이 아니다.

Handshake는 Linux Kernel의 TCP Stack이 처리한다.

전체 흐름을 단순화하면 다음과 같다.

Client
   ↓
SYN
   ↓
SYN Queue
   ↓
SYN/ACK
   ↓
ACK
   ↓
Accept Queue
   ↓
accept()
   ↓
Application

이 흐름을 이해해야

“포트는 LISTEN 상태인데 왜 신규 접속이 느리지?”

“CPU는 남아 있는데 왜 Connection이 실패하지?”

같은 상황을 제대로 분석할 수 있다.


2. 첫 번째 대기 구간, SYN Queue

클라이언트가 서버에 TCP Connection을 만들기 위해 가장 먼저 보내는 것은 SYN 패킷이다.

Client → Server : SYN

서버 Kernel은 SYN을 받은 뒤 해당 Port에 LISTEN 중인 Socket이 있는지 확인한다.

정상적인 Listening Socket이 존재한다면 서버는 SYN/ACK를 반환한다.

Server → Client : SYN/ACK

하지만 아직 Connection이 완성된 것은 아니다.

클라이언트가 마지막 ACK를 보내야 한다.

이 시점의 연결은 일반적으로 다음 TCP 상태로 확인된다.

SYN_RECV

즉,

SYN 수신
↓
SYN/ACK 전송
↓
Client ACK 대기

상태다.

이처럼 3-Way Handshake가 아직 완료되지 않은 Connection 요청을 관리하는 영역을 흔히 SYN Queue 또는 SYN Backlog라고 부른다.

Linux에서 이 영역과 관련된 대표적인 Kernel Parameter가 있다.

sysctl net.ipv4.tcp_max_syn_backlog

현재 서버에서 SYN_RECV 상태를 확인하려면 다음 명령을 사용할 수 있다.

ss -ant state syn-recv

개수만 빠르게 확인하려면 다음과 같이 볼 수도 있다.

ss -ant state syn-recv | wc -l

다만 SYN_RECV가 많다고 해서 바로 SYN Flood 공격이라고 판단해서는 안 된다.

정상적인 대규모 접속 증가나 네트워크 지연, 패킷 손실 등에 의해서도 SYN_RECV가 일시적으로 증가할 수 있다.

따라서 실제 장애에서는 Source IP 분포와 SYN 유입량, SYN/ACK 이후 ACK가 정상적으로 돌아오는지까지 함께 확인해야 한다.


3. 두 번째 대기 구간, Accept Queue

클라이언트가 서버의 SYN/ACK를 받고 마지막 ACK를 보낸다고 해보자.

Client → Server : ACK

이제 3-Way Handshake가 완료된다.

TCP 상태도 일반적으로 ESTABLISHED로 전환된다.

그런데 Handshake가 끝났다고 해서 해당 Connection이 즉시 Nginx나 애플리케이션으로 전달되는 것은 아니다.

Handshake가 완료된 Connection은 애플리케이션이 accept()를 호출해 가져갈 때까지 Kernel 내부에서 기다린다.

이 대기열이 Accept Queue다.

두 Queue의 차이를 명확하게 구분하면 다음과 같다.

SYN Queue
= Handshake가 아직 끝나지 않은 Connection

Accept Queue
= Handshake는 끝났지만
  애플리케이션이 아직 accept()하지 않은 Connection

현대 Linux에서 listen(fd, backlog)의 backlog는 완전히 연결된 Socket이 accept()를 기다리는 Queue 길이와 관련된다.

그리고 애플리케이션이 지정한 backlog가 net.core.somaxconn보다 크다면 실제 적용 가능한 한도는 Kernel 설정의 영향을 받는다.

현재 값은 다음과 같이 확인할 수 있다.

sysctl net.core.somaxconn

하지만 여기서 매우 중요한 점이 있다.

somaxconn 값을 크게 만든다고 서버의 Connection 처리 성능이 자동으로 증가하는 것은 아니다.

애플리케이션이 accept()를 충분히 빠르게 수행하지 못한다면 Accept Queue는 결국 다시 쌓이게 된다.


4. Backlog는 왜 필요한가

실제 서버로 들어오는 Connection은 항상 일정한 속도를 유지하지 않는다.

평소에는 비교적 안정적으로 들어오던 Connection이 이벤트, 서비스 재시작, 장애 복구 후 재접속, 특정 시간대 트래픽 집중 등으로 아주 짧은 순간 급증할 수 있다.

이것을 Connection Burst라고 생각하면 이해하기 쉽다.

예를 들어 짧은 시간 동안 신규 Connection 1,000개가 들어왔는데 애플리케이션이 같은 시간 동안 700개만 accept()할 수 있다고 해보자.

남은 Connection이 즉시 실패할 필요는 없다.

일정 수의 Connection은 Queue에서 기다릴 수 있다.

Backlog는 이런 순간적인 차이를 흡수하는 Buffer 역할을 한다.

따라서 Backlog는 흔히 생각하는 서버의

최대 동시접속자 수

와 같은 의미가 아니다.

예를 들어 이미 ESTABLISHED 상태인 Connection이 수만 개 존재한다고 해도 신규 Connection을 빠르게 accept하고 있다면 Accept Queue는 거의 비어 있을 수 있다.

반대로 전체 Connection 수가 많지 않더라도 짧은 순간 신규 접속이 폭증하면 Accept Queue가 가득 찰 수 있다.

따라서 Backlog 문제에서 중요한 것은 전체 Connection 숫자가 아니다.

핵심은

신규 Connection 유입 속도

vs

애플리케이션의 Connection 소비 속도

다.


5. Accept Queue가 가득 차면 어떤 일이 발생할까

애플리케이션이 Connection을 가져가는 속도보다 새로운 Connection이 완성되는 속도가 계속 빠르면 다음과 같은 흐름이 만들어진다.

신규 Connection 증가
        ↓
3-Way Handshake 완료
        ↓
Accept Queue 진입
        ↓
Application accept() 지연
        ↓
Queue 누적
        ↓
Backlog 한계 도달

이 상황이 위험한 이유는 서버 프로세스가 죽지 않았을 수도 있기 때문이다.

Nginx도 실행 중이고 Port 역시 LISTEN 상태일 수 있다.

그런데 외부에서는 신규 Connection이 느려지거나 일부 접속이 실패한다.

운영 환경에서는 최초 연결 시간이 길어지거나 간헐적인 접속 실패가 발생하고, 재시도하면 정상적으로 연결되는 현상이 나타날 수 있다.

Kernel의 Listen Queue overflow나 drop 관련 통계가 증가할 수도 있다.

그런데 이때 CPU 사용률이 반드시 100%가 되는 것은 아니다.

그래서 Connection 장애를 top의 CPU 사용률만 보고 판단하면 원인을 놓칠 수 있다.


6. 실제 서버에서 Backlog 상태 확인하기

먼저 LISTEN 상태의 Socket을 확인한다.

ss -lnt

특정 Port만 확인하려면 다음처럼 사용할 수 있다.

ss -lnt 'sport = :80'

ss를 이용하면 Listening Socket의 Queue 상태를 확인할 수 있다.

다만 Kernel과 iproute2 버전 등에 따라 출력 및 해석에 차이가 있을 수 있기 때문에 운영 환경의 man ss도 함께 확인하는 것이 안전하다.

다음으로 Kernel의 Listen Queue 관련 통계를 확인한다.

netstat -s | grep -i listen

또는 Linux Kernel의 TCP 확장 통계도 볼 수 있다.

cat /proc/net/netstat

여기서 관심 있게 볼 항목 중 하나가

ListenOverflows
ListenDrops

다.

중요한 것은 숫자가 존재하느냐가 아니라 증가하고 있느냐다.

예를 들어 장애 발생 전까지 ListenOverflows가 거의 증가하지 않았는데 트래픽 증가 시점부터 계속 증가한다면 Listen Queue가 포화되는 상황을 의심할 수 있다.


7. SYN Queue 문제와 Accept Queue 문제를 구분하자

Backlog 장애 분석에서 가장 중요한 부분 중 하나다.

SYN_RECV가 비정상적으로 증가하고 있다면 먼저 Handshake 단계의 문제를 확인해야 한다.

ss -ant state syn-recv

이 경우에는 다음과 같은 가능성을 살펴본다.

대량의 신규 SYN 유입
SYN Flood
네트워크 지연
패킷 손실
SYN/ACK 이후 ACK 응답 지연

반대로 Handshake는 정상적으로 완료되고 있는데 LISTEN Socket의 Queue가 계속 증가하고 ListenOverflows가 발생한다면 관심 지점이 달라진다.

애플리케이션이 Connection을 충분히 빠르게 가져가지 못하고 있을 가능성을 확인해야 한다.

예를 들어 다음과 같은 원인이 있을 수 있다.

Worker 부족
CPU Scheduling 지연
Event Loop 정체
File Descriptor 부족
Application 내부 처리 병목
과도한 순간 Connection 유입

따라서

Connection 문제 발생
→ somaxconn 증가

처럼 바로 Kernel Parameter를 변경하는 접근은 좋지 않다.

먼저 어느 Queue에서 Connection이 막히고 있는지를 확인해야 한다.


8. somaxconn과 tcp_max_syn_backlog는 역할이 다르다

서버 튜닝 과정에서 자주 혼동되는 두 값이 있다.

net.ipv4.tcp_max_syn_backlog

과

net.core.somaxconn

이다.

tcp_max_syn_backlog는 아직 최종 ACK를 받지 못한 SYN_RECV 단계의 Connection 요청과 관련된다.

반면 somaxconn은 애플리케이션이 listen()을 통해 요청하는 Listen Backlog의 상한과 관련된다.

Linux Kernel 문서에서도 tcp_max_syn_backlog는 SYN_RECV 상태로 기억되는 연결 요청 수와 관련된 값으로 설명하고 있으며, somaxconn은 Socket의 listen() backlog 한도와 관련된 값으로 설명한다.

현재 서버 값은 다음처럼 확인한다.

sysctl net.ipv4.tcp_max_syn_backlog

sysctl net.core.somaxconn

그렇다고 특정 숫자를 인터넷에서 찾아 그대로 적용하면 안 된다.

적절한 값은 Kernel 버전, 서버 메모리, 트래픽 패턴, Connection 생성 속도, 애플리케이션 처리 구조에 따라 달라진다.


9. Backlog 장애가 발생했을 때 분석 순서

실제 운영 서버에서 Connection 문제가 발생했다면 Parameter부터 변경하지 말고 흐름을 따라가며 확인하는 것이 좋다.

먼저 전체 TCP 상태와 신규 Connection 증가 여부를 본다.

ss -s

필요하면 다음 도구도 활용할 수 있다.

sar -n TCP,ETCP 1
tcpdump

다음으로 SYN_RECV가 쌓이는지 확인한다.

ss -ant state syn-recv

SYN_RECV가 비정상적으로 많다면 SYN Queue와 Handshake 단계부터 확인한다.

그다음 Listening Socket 상태를 본다.

ss -lnt

그리고 Listen Queue 관련 Kernel 통계를 확인한다.

netstat -s
cat /proc/net/netstat

ListenOverflows나 ListenDrops가 계속 증가한다면 Accept Queue 포화 가능성을 확인한다.

그다음 중요한 작업은

Kernel Queue 한계 때문인가?

아니면

Application accept() 지연 때문인가?

를 구분하는 것이다.

이때 Worker 수, CPU Scheduling, File Descriptor, Event Loop, Application 처리 속도까지 함께 확인해야 한다.

원인이 확인된 뒤에야 backlog, somaxconn, tcp_max_syn_backlog 같은 값을 조정할 필요가 있는지 판단하는 것이 맞다.

특히 SYN Cookie 역시 정상적인 고부하 서버의 Connection 처리 용량을 늘리기 위한 일반적인 튜닝 수단으로 접근해서는 안 된다. Linux Kernel 문서에서도 SYN Cookie를 SYN Queue overflow에 대한 fallback 수단으로 설명하며, 정상 트래픽 때문에 Queue가 넘친다면 다른 병목 원인을 먼저 확인하도록 안내한다.


10. Backlog를 크게 하면 문제가 해결될까

결론부터 말하면 그렇지 않다.

Backlog를 늘리는 것은 순간적으로 몰려드는 Connection을 더 오래 Queue에서 기다릴 수 있게 해주는 방법이다.

예를 들어 애플리케이션이 초당 1,000개의 신규 Connection을 소비할 수 있는데 초당 2,000개의 Connection이 지속적으로 들어온다고 해보자.

Queue 크기를 두 배로 늘리면 장애 발생 시점을 늦출 수는 있다.

하지만 계속해서

유입 속도 > 처리 속도

상태가 유지된다면 결국 Queue는 다시 가득 찬다.

따라서 안정적인 서버에서는 장기적으로 다음 관계가 유지되어야 한다.

Connection 유입 속도
        <
서버의 Connection 소비 속도

Backlog는 지속적인 성능 부족을 해결하는 장치가 아니라 순간적인 불균형을 흡수하는 완충 장치에 가깝다.


마무리

TCP Backlog를 이해하려면 하나의 Kernel Parameter만 보는 것이 아니라 TCP Connection이 서버 내부에서 이동하는 전체 흐름을 이해해야 한다.

Client
   ↓
SYN
   ↓
SYN Queue
   ↓
SYN/ACK
   ↓
ACK
   ↓
Accept Queue
   ↓
accept()
   ↓
Application

Handshake가 끝나기 전에는 SYN Queue가 관여한다.

Handshake가 완료된 이후 애플리케이션이 Connection을 가져가기 전에는 Accept Queue가 관여한다.

따라서 신규 Connection 장애가 발생했을 때 가장 먼저 할 일은 somaxconn 값을 키우는 것이 아니다.

먼저 SYN_RECV가 쌓이는지 확인해야 한다.

그다음 Accept Queue가 쌓이는지, ListenOverflows와 ListenDrops가 증가하는지 확인한다.

마지막으로 애플리케이션이 accept()를 충분히 빠르게 수행하고 있는지를 확인해야 한다.

이렇게 Connection이 이동하는 경로를 따라가면 병목이 Handshake 단계인지, Kernel Queue인지, 애플리케이션 처리 단계인지 구분할 수 있다.

결국 Backlog는 서버의 최대 동시접속자를 결정하는 하나의 숫자가 아니다.

Kernel의 TCP Connection 생성 속도와 애플리케이션의 Connection 소비 속도 사이에서 순간적인 차이를 흡수하는 구조다.

이 개념을 이해하고 나면

“서버는 살아 있는데 왜 신규 Connection만 실패하는가?”

라는 문제를 단순한 추측이 아니라 실제 TCP 처리 구조를 기준으로 분석할 수 있다.

instone
함께 보면 좋은 글

댓글 0

첫 댓글을 남겨보세요.

error: Content is protected !!

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.