1:49 · boss fight · receive queue · zero window · send queue

클라가 느리면 서버도 느려진다

보스전처럼 받을 데이터가 몰리는 순간 클라이언트가 이를 제때 처리하지 못하면, 밀린 데이터가 TCP 흐름 제어를 타고 서버의 송신 버퍼와 앱 송신 큐까지 쌓이는 과정을 보여주는 영상이다. 느린 클라이언트 몇 명은 티가 나지 않지만, 보스전처럼 한꺼번에 몰리면 서버 메모리와 처리량에 영향을 주고, 구조에 따라서는 한 명이 존 전체를 멈추게 할 수도 있다. 대응은 양쪽 모두에서 한다. 클라이언트는 소켓을 빨리 비우고, 서버는 클라이언트당 보내는 양을 묶어 송신 큐 상한에 닿지 않게 한다.

01보스전에 생기는 일

받아야 할 양은 늘고, 클라가 처리하는 속도는 떨어진다 · ▶ 이 장면 보기

그래프는 클라이언트 한 명 기준의 초당 패킷 수다. 메인 스레드가 한 프레임에 처리할 수 있는 패킷을 200개로 잡았고, 화면에 예시라고 표시되어 있다.

보스가 등장하면 받아야 할 패킷이 초당 1,500개에서 8,000개로 늘어난다. 같은 시점에 화면에 그릴 것이 많아져 FPS가 60에서 15로 떨어진다. 처리할 수 있는 패킷은 FPS × 200이므로 초당 12,000개에서 3,000개로 줄어든다.

받을 양이 가장 많은 순간에 처리 속도는 가장 느려진다. 두 선 사이의 빨간 영역이 처리하지 못하고 밀린 패킷이고, 오른쪽 위 숫자로 계속 늘어난다. 이 차이는 어딘가에 쌓이며, 클라이언트 안에서 시작해 서버까지 올라올 수 있다.

02클라 안의 수신 큐

수신 스레드를 따로 둔 구조라면: 수신 스레드가 받고, 메인 스레드가 꺼낸다 · ▶ 이 장면 보기

클라이언트 내부 구조다. 수신 스레드를 따로 둔 구조라면, 커널 수신 버퍼에 들어온 데이터를 수신 스레드가 바로 꺼내 앱 메모리의 수신 큐에 넣는다. 메인 스레드는 프레임마다 이 큐에서 정해진 한도, 여기서는 200개만큼 꺼내 게임 로직과 렌더에 반영한다.

평소에는 초당 1,500개가 들어오고 FPS 60에서 초당 12,000개를 꺼낼 수 있으니 큐가 바로바로 비워진다. 보스전에는 초당 8,000개가 들어오는데 FPS 15에서 초당 3,000개밖에 꺼내지 못해, 꺼내는 속도보다 쌓이는 속도가 빠르다.

한 프레임에 큐를 다 비우면 그 프레임이 길어지므로 처리량에 한도를 두고, 그만큼 큐에 남는다. 이 단계까지는 클라이언트 안의 문제다. 오래된 이동을 늦게 반영해 렉과 러버밴딩이 생기고 클라이언트 메모리가 늘어난다. 수신 스레드가 커널 버퍼를 계속 비우고 있으므로 아직 서버에는 영향이 없다. 메인 스레드가 프레임마다 소켓을 직접 읽는 구조라면, FPS가 떨어지는 것만으로 커널 수신 버퍼가 차서 바로 서버까지 압력이 간다.

03소켓이 안 비워지는 순간

수신 스레드가 멈추거나, 회선이 느리거나 · ▶ 이 장면 보기

수신 스레드가 멈추면 상황이 달라진다. 커널 수신 버퍼를 비우는 쪽이 없어지니 소켓이 비워지지 않는다. 영상은 수신 스레드가 멈추는 경우 네 가지와 느린 회선을 든다.

수신 스레드가 멈추면 이때부터 클라이언트 커널 수신 버퍼가 차기 시작하고, 압력이 서버 방향으로 향한다.

04압력이 서버로 거꾸로 온다

수신 버퍼 → Zero Window → 송신 버퍼 → 앱 송신 큐 · ▶ 이 장면 보기

네 개의 탱크가 서버에서 클라이언트 방향으로 놓여 있다. 왼쪽부터 서버 앱 송신 큐(세션별, 메모리), 서버 커널 송신 버퍼(SO_SNDBUF), 클라이언트 커널 수신 버퍼(SO_RCVBUF), 클라이언트 수신 큐다. 클라이언트 수신 큐는 이미 가득 차서 수신 스레드가 멈춘 상태다. 서버에서는 보스전 브로드캐스트가 계속 만들어진다.

TCP는 받는 쪽이 못 받으면 보내는 쪽을 멈추게 되어 있다. 그래서 클라이언트가 뽑아내지 못한 양이 결국 서버 앱 메모리에 남는다.

05상한 전에도 서버는 느려진다

메모리 · 버퍼 · 재시도 — 몇 명이면 작고, 몰리면 커진다 · ▶ 이 장면 보기

송신 큐가 상한에 닿기 전에도 서버는 비용을 치른다. 네 장의 카드가 이를 보여준다.

①~③은 느린 클라이언트가 몇 명일 때는 티가 나지 않는다. 보스전처럼 느린 클라이언트가 몰리고 트래픽도 최고일 때 겹쳐서 커진다.

06송신 큐 상한은 안전장치

닿으면 끊긴다 — 닿지 않게 운영한다 · ▶ 이 장면 보기

서버는 보통 세션 송신 큐에 상한을 두고, 닿으면 그 연결을 끊는다. 왼쪽은 상한에 닿는 경우다. 보스 패턴 동안 큐가 쌓여 상한에 닿고, 접속이 끊기고, 재접속한 뒤 다음 보스전에서 같은 일이 반복된다. 그 유저는 보스전마다 계속 튕긴다.

오른쪽은 상한에 닿기 전에 보내는 양을 묶은 경우다. 이동은 최신값으로 덮어쓰고, 먼 대상은 갱신 빈도를 낮추고, 시야 인원에 상한을 둔다. 큐가 상한 아래에서 유지된다.

장비 임계치나 인원 제한은 서버 전체 기준이라서 한 세션의 큐를 막지 못한다. 한 세션의 큐 크기는 그 클라이언트가 받아가는 속도에 달려 있다. 송신 큐 상한은 넉넉하게 두는 마지막 안전장치이고, 실제 운영에서는 클라이언트당 보내는 양을 줄여 상한에 닿지 않게 한다.

07해결 방법

클라는 빨리 뽑아내고, 서버는 클라당 보내는 양을 묶는다 · ▶ 이 장면 보기

클라이언트 쪽은 소켓을 빨리 비우는 데 집중한다.

서버 쪽은 클라이언트당 보내는 양을 묶는다.

느린 회선은 클라이언트가 고칠 수 없고, 클라이언트 처리 속도는 서버가 고칠 수 없다. 그래서 양쪽 모두 손봐야 한다.

08인프라에서 확인하는 법

Send-Q · Zero Window · 세션 큐 · 끊김 수 · 클라 지표 · ▶ 이 장면 보기

왼쪽 터미널은 확인에 쓰는 명령과 지표다.

오른쪽은 판단 기준이다.

영상은 Send-Q를 커널 송신 버퍼에 남은 바이트(아직 못 보냈거나 ACK를 기다리는 것)로 설명하고, 느린 클라이언트를 찾는 가장 직접적인 신호로 꼽는다.

이 영상에 나온 용어는 용어집에 정리했다.