티스토리 뷰

요즘 너무 바빠서 포스팅을 미루고 미루다 드디어 쓰게 되었네요.


1편의 내용을 이어서 쓰자면, 당연히 쓰레드풀을 그렇게 어마무시하게 늘렸으니 문제가 생기는건 당연했습니다. 하지만, 예측하기 힘들게 서서히 찾아오더군요.


갑자기 서버들간의 Connect가 끊긴다던지, 특정 게임의 접속'만' 갑자기 안된다던지... 대비되지 않은 상황에 대한 예외 케이스들이 많이 발생했습니다. 처음엔 서버 머신 축소로 인한 장애로 생각했으나, 사실 이 모든 원흉엔 토너먼트가 있었던거죠.


그 후 저는 토너먼트 서버의 어떤 부분에서 부하가 걸리는지 찾아내는데 많은 삽질을 했습니다... 아직까지 생각하면 눈물이 앞을 가리네요.


물론 그러면서 깨닫고, 배운점이 많이 있습니다. 그래서 저 스스로도 이것들을 잊지 않고 상기하기 위해, 그리고 다른 분들께 공유하기 위해 저의 본격적인 삽질 일기를 써내려가보도록 하겠습니다. 역시 개발자로서 최고의 성장 발판은 삽질인 것 같습니다. 정신승리중


1. 문제의 발견

불굴의 디버깅 + 로그에 눈 박고 있기 신공으로 병목이 일어나는 부분은 찾았습니다. 유저의 토너먼트 점수 정보를 갱신시켜 주는 부분이 문제더군요. (이때까지만 해도 하루면 고칠 수 있을줄 알았습니다)

이 함수에서 클라이언트에게 패킷을 전송하는 부분이 있는데, 여기서 Send가 제대로 되지 않고 버퍼에 쌓이고 있었습니다.


그 후 관련된 코드를 싹 뒤져봤습니다. 그런데, 별로 문제 있는 부분을 찾을 수 없습니다! 이때부터 멘붕의 시작이었습니다.


2. Work Time 측정

뭔가 이상하다는것을 깨닫고 바로 문제가 있는 함수의 Work Time을 측정해 보았습니다. 그런데 웬걸요, 정말 정상적인 수치가 나왔습니다. 전혀 병목을 일으키지 않을만한 수치로요. (10ms 내외) 이 때부터 '이건 다 꿈일거야'라고 되새겼습니다...


3. 프레임워크 분석

대강 구조만 알고 있던 프레임워크도 분석해 보았습니다. 어쨌든 그렇게 많은량의 패킷을 쏘는것도 아닌데 Send Buffer가 가득 차는건 문제가 있으니까요. 그런데, 프레임워크에도 문제는 없었습니다. 오히려 상용 프레임워크도 아닌데 이 정도라니... 대단하다. 라고 감탄만 했네요. 캬하하핰!

중간에 이 프레임워크를 개발하신 이사님을 만나 여쭤보기도 하였는데, 여태까지 하셨던 모든 라이브 서비스들에서 Send Buffer는 아무리 많아야 10개 내외로 유지됐다고 하시더군요. 이 때 2차 멘붕! 대체 어떻게 Send Buffer가 200개나 차는거야!?


4. Send Queue

기존의 인프라나 프레임워크에서 문제를 찾는것은 체념하고, 새로운 솔루션을 내놓기로 했습니다. 실 그렇게 많지도 않은 패킷 때문에 서버에 부하가 가서 전송 속도를 조절해야 한다는 것 자체가 굉장히 아이러니컬한 상황이긴 한데 일단 장애는 고쳐야 하니까요...

클라이언트의 점수 갱신 요청을 바로 처리하지 않고, Queue에 담아 적절한 갯수만큼 처리하는 방식으로 구현했습니다. 결과는 성공이었습니다만, 찝찝함이 남을 수 밖에 없었습니다.


5. 패킷 사이즈 축소

한 패킷이 여러 역할(입장, 공지, 종료, 갱신 등)을 하다보니 패킷 사이즈가 불필요하게 커지는 문제가 보여 이를 축소해서 테스트 했더니, 정상적으로 되긴 하였습니다만, 클라이언트 수정이 들어가야 하는 문제이기에 이것도 패스했습니다. 그리고 사실 500바이트 짜리 패킷이 그렇게 큰 패킷도 아니고, 클라이언트가 패킷 크기를 줄인만큼 많아지면 이 문제는 또 발생할 거라는 얘기니까요.


6. Serverless 아키텍쳐 사용

AWS Lambda + AWS API Gateway를 사용해 토너먼트 서버를 재구축해보기도 했습니다.

하지만 비용 문제와, 보안상 문제(클라에서 호출하는것이기 때문에 API Gateway 주소가 노출된다면 위험할 수 있음)로 이 방법도 패스했습니다.

이 삽질로 인해서 Lambda가 간단하지만 정말 강력하다는 것도 깨달을 수 있었습니다.

관리가 거의 필요없으며, 파이썬과 자바, Node JS 등 최신 트렌드에 따른 언어 지원도 좋았구요.

머지않아 저희 회사의 프로젝트에도 본격적으로 사용하게 되는 날이 오지 않을까 싶었습니다.

7. Scale-Out

마지막으로 택한건 게임 프론트엔드 서버의 수평 확장이었습니다.

게임 프론트엔드와 백엔드는 거의 아무런 인수인계도 받지 못한 상태였기에 굉장히 조심스러울 수 밖에 없었는데,

다행히 현재는 회사에 계시지 않는 이사님현재 회사에서 사용하는 서버 프레임워크를 개발하신 분! 께서 게임 프론트엔드 서버는 수평 확장을 위해 있는 서버니까 괜찮다! 라는 조언을 해주셨습니다.

물론 제가 국면한 이슈에서 수평 확장이 정확한 답은 아니었지만요.



자... 그래서 이런 삽질 끝에 해결은 했냐구요? 네 드디어 했습니다 (유유... 눈물이 앞을 가린다)


저희 프레임워크단에서 Send Buffer가 가득 찼다는 에러가 발생한 이유는 간단했습니다.


'Send 요청을 받아주는 쪽에서 Recv를 못 하고 있다!'


하지만 문제를 정확하게 찾은 후에도, 이 이슈를 해결하는건 결코 쉽지 않았습니다. 우선 고쳐도 고쳐도 어디선가 병목이 생기는 부분이 있었구요... A의 병목을 고치면 B의 병목이 발견되고, B의 병목을 고치면 C의 병목이 발견되고...


또, 저희 게임의 백엔드, 프론트엔드 서버에서 몰랐던 특징 중 하나는... 서버간 통신에서는 동기 방식을 사용한다는 것이었습니다! 이러니까 쓰레드 풀만 죽어라 늘렸는데 해결이 됐던거죠 유유...


아마 구현의 복잡도 때문에 이런 방식을 채택하신게 아닌가 싶은데... 게임서버는 비동기로 잘 돼있는데 ㅠㅠ 뭐 어쨌든 긴 삽질 끝의 발견으로, 여태껏 발생했던 모든 현상에 대한 설명을 할 수 있게 되었습니다.


1. 토너먼트 서버에서 유저의 점수를 갱신시킨 뒤, 갱신된 정보를 프론트엔드 서버로 보낸다.

2. 프론트엔드 서버에서 갱신된 정보를 받으면, 클라이언트가 요구하는 몇몇 요구사항을 채워 클라이언트에게 보낸다.

3. 이때 2번의 클라이언트의 요구사항을 채우는 과정에서, 다른 서버와의 통신이 필요하다.

4. 서버간 모든 통신은 동기 방식이기 때문에, 3번의 과정에서 blocking이 발생한다.

5. 따라서 점수를 갱신할 때 마다, 많은 쓰레드가 블로킹 상태에 빠질 수 밖에 없게 되어 있었다.

6. 그러다보니 당연히 들어오는 Send 요청을 받을 수 있는 쓰레드가 없었던 것이었다.

7. 이러니까 무작정 쓰레드 풀만 늘리면 해결이 됐던 것이였고... 평화가 무너지고...


결국 이 작은 문제 하나는, 저희 게임의 프론트엔드 서버 + 백엔드 서버 + 프레임워크 까지 모두 파악하고 있어야 보이는 문제였습니다. 정말 길고 긴 삽질을 끝내게 되어 한편으로는 기쁘기도 하지만, 이런 상황에 국면한 제 상황에 마냥 웃을수만은 없는 현실이네요.

'개발 > Server' 카테고리의 다른 글

서버 개발자의 라이브 서비스 장애 해결기 - 1  (0) 2018.05.17
댓글