Skip to content
isdnetworks
Go back

게임 서버에 루프가 둘이었다

게임 서버 라이브러리의 구조를 봤다. 네트워크 처리와 작업 처리가 따로 있었다.

네트워크 이벤트 처리
태스크 처리

IOCP 로 통신을 받는 부분과 실제 작업을 처리하는 부분이 각각 별도의 루프로 돌고 있었다.

Table of contents

Open Table of contents

두 루프로 나뉘어 있었다

한 루프에서 받고 처리까지 하면 코드가 단순해진다. 그런데 이 라이브러리는 굳이 둘로 나눠 두었다.

두 루프 사이에는 큐가 있었다.

받는 쪽 → 큐 → 처리 쪽

GetQueuedCompletionStatus 로 완료를 꺼낸 쪽이 큐에 넣고 로직 쪽이 꺼내 간다.

나눈 이유는 처리에 걸리는 시간 때문으로 보였다. 한 루프에서 다 하면 어떤 요청의 처리가 길어지는 동안 다음 WSARecv 완료를 못 꺼낸다.

받는 것이 막히면 접속 자체가 느려지고 사용자는 서버가 죽은 것으로 느낀다. 나눠 두면 처리가 아무리 길어도 받는 쪽은 계속 받는다. 대신 처리가 밀리면 큐가 쌓인다.

큐가 쌓이면 정책이 필요하다

받는 속도가 처리 속도보다 빠르면 큐가 쌓이고 무한정 쌓이면 결국 메모리가 바닥난다. 그러니 한계를 정하고 넘었을 때 무엇을 할지도 정해야 한다.

선택지가 셋이었다.

버림   요청이 사라짐 — 사용자가 모름
거부   지금 안 된다고 응답 — 재시도 가능
대기   받는 쪽을 멈춤 — 나눈 이유가 없어짐

조용히 버리면 사용자는 요청이 사라진 것을 모르고 기다린다. 받는 쪽을 멈추면 루프를 나눈 이유 자체가 없어진다. 거부하면 적어도 실패했다는 것은 알게 되므로 그쪽이 나아 보였다.

제약 — 처리기를 여럿 둘 때의 순서

처리하는 쪽을 여럿 두면 더 빨리 처리된다. CreateIoCompletionPortNumberOfConcurrentThreads 를 0 으로 두면 OS 가 프로세서 수만큼 동시에 돌게 한다. 그런데 그러면 요청의 순서가 보장되지 않는다.

같은 사용자의 요청 A, B
    ↓ 각각 다른 처리기로
B 가 먼저 끝날 수 있음

그래서 세션 번호를 처리기 수로 나눈 나머지로 배정해 같은 사용자는 항상 같은 처리기로 가게 했다. 그 사용자 안에서는 순서가 맞는다.

다만 그렇게 하면 특정 처리기에만 몰릴 수 있다는 것도 함께 봐야 했다. 나누는 방식에 달렸다.

연결 풀과 잠금 없는 메모리 풀

라이브러리에는 DB 연결 관리도 따로 있었다. 매번 연결하면 느리니 미리 여러 개 만들어 두고 빌려 쓰는 방식이다.

몇 개를 만들지가 문제인데 처리기 수와 관련이 있었다. 처리기가 넷이면 동시에 최대 넷이 DB 를 쓰므로 처리기 수만큼이면 된다. 그것보다 적으면 처리기들이 풀에서 연결을 기다리게 되고 처리기를 늘린 이득이 준다.

여러 서버가 같은 DB 에 붙는 경우에는 계산이 달라진다.

서버 3대 × 처리기 4 = 12

DB 쪽에서 12개를 받아야 하므로 MySQL 의 max_connections 까지 함께 봐야 한다. 서버 수에 처리기 수를 곱한 값이 그 한도를 넘으면 접속 자체가 거절된다.

메모리도 미리 잡아 두고 나눠 쓰는 풀 구조로 되어 있었다. 게임 서버는 패킷마다 객체를 만들고 없애는데 초당 수천 개라 newdelete 자체가 비용이고 조각도 난다.

풀에는 잠금 없는이라는 말이 붙어 있었다. 여러 스레드가 동시에 써도 서로 안 기다린다는 뜻으로 보였다. 처리기가 여럿이면 동시에 메모리를 요청하는데 잠금이 있으면 한 번에 하나씩 기다리므로 처리기를 늘린 이득이 여기서도 깎인다.

하나를 나누니 나머지가 따라왔다

여기까지 보니 전부 이어져 있었다.

루프를 나눔  →  처리기 여럿
처리기 여럿  →  연결도 여럿 · 메모리 경합

연결 풀 · 잠금 없는 메모리 풀

루프를 나눈다는 결정 하나가 큐와 순서와 연결 수와 메모리까지 줄줄이 끌고 오는 구조였다.

읽어도 모르는 것이 남았다.

큐가 넘칠 때 실제로 어떻게 하나
처리기 수를 무엇으로 정하나
잠금 없는 방식이 어떻게 동작하나

코드를 봐야 알 것 같다.

정리


Share this post on:

Previous Post
200을 받고 원본을 지웠다
Next Post
만드는 쪽과 처리하는 쪽이 떨어져 있었다