본문 바로가기

컴퓨터 과학

c10k problem: 동시 사용자 1만명 채팅서버는 어떻게 구현되는가?

오늘날과 같이 컴퓨터의 성능이 업그레이드 된 시대와 다르게, 인터넷 초창기 때만 해도 동접자(cocurrent connections) 1만명 서버를 만드는 일은 쉬운 일이 아니었다. 이를 영어로 c10k problem이라고 칭하는데, 1999년 Dan Kegel이라는 분이 제기한 문제이다.

 

그래서, 동시사용자(cocurrent connections)이라는 게 뭐야?

c10k problem의 핵심은, 제한된 자원으로 동시에 일어나는 연결을 처리해야 한다는 것이다. 아니, 클릭하거나 텍스트 입력 후 enter를 누르면 그게 끝 아닌가? 전송하고 받고, 어차피 통신은 비슷한게 아니야? 라고 생각할 수 있다. 하지만, 우리가 사용하는 일반적인 웹 통신인 HTTP 요청-응답 방식과 실시간으로 채팅이 이뤄지는 웹 소캣 방식은 서로 다르다. 왜 다르고, 달라야 하는 지 이해해 보자.

혹시 페이지 새로고침이라는 것을 사용해 본 적이 있는가? 새로 고침 시, 새롭게 페이지가 로드되면서 구성이 바뀌는 경우가 많다. 이러한 통신을 HTTP 통신이라 한다. 클라이언트, 즉 당신이 '새로고침'과 같은 요청을 해야 서버 측에서 응답을 하여, 바뀐 페이지를 그제서야 보내주는 것이다. 하지만 채팅방은 어떠한가? 본인이 텍스트를 입력 후 enter(요청)을 하지 않아도, 상대가 전송하였을 경우 새롭게 변한 대화 창을 보내줘야 한다. 이러한 통신은 웹 소캣을 통해 실시간으로 이뤄진다. 표로 요약하자면 다음과 같다:

HTTP 요청-응답 웹 소캣
요청-응답 (클라이언트에서 서버로 요청 -> 서버에서 응답) 양방향 통신 (클라이언트와 서버가 양방향으로 메시지 송수신)
비상시 연결이 끊어짐 (요청 후 응답을 받으면 연결 종료) 지속적 연결 유지 (클라이언트와 서버 간 연결 지속)
웹 페이지 요청, API 요청, 클라이언트-서버 간 데이터 전송 실시간 통신 (채팅, 실시간 알림, 게임 등)

 

여기서 핵심은 지속적으로 연결이 유지되어야 한다는 점이다. 연결 상태를 유지하기 위해 서버에서 자원을 계속 소비해야 한다는 것. 그리고 그 자원을 바로 스레드라는 것. 스레드에 대한 설명은 바로 아래 링크를 참고하길 부탁드린다:

https://subplus.tistory.com/19

 

프로세스와 스레드(thread)의 차이, 그리고 멀티 스레드란?

기술면접, 그리고 업무상 필요로 CS지식을 공부하는 경우가 꽤 있을 것이다.그 중 아마 자주 들었던 단어는 프로세스,그리고 좀 더 깊게 파고들면 스레드,특히 스레드는 이것은 싱글 스레드이고,

subplus.tistory.com

지금은 다르지만, 과거 c10k problem이 제기되었던 시기에는 CPU에 있는 코어 하나는 오직 하나의 스레드만 실행이 가능했다. 따라서, 1만명이 동시 통신을 하려면 1만 개의 스레드를 만들고, 이걸 한정된 갯수의 코어에 빠르게 바꿔치기 하면서 병렬 수행을 하였는데, 이를 스레드 풀이라 불린다. 하지만 스레드 풀 방식은 너무나 많은 자원을 소비했고, 이를 지금대비 상당히 저 사양인 컴퓨터로 처리하기엔 부담이 컸었고, 과거에 비해 많이 사용되지 않는다고 한다.

 

 

문제가 있다면 해결책이 있는 법!

하지만 유능하고 뛰어난 개발자들이 이를 해결하는 방법을 찾아냈다. 먼저 Event-Driven Architecture(이벤트 기반 아키텍처)를 구성하는 것이다. 자세한 부분은 역시 공부를 해야겠지만, 대략 요약을 하자면, 이벤트.. 예를 들어 '결제'라는 이벤트, '가입'과 같은 하나의 이벤트가 발생할 때, 이에 초점을 맞춰서 서버 아키텍처를 구성하는 것이다. 이렇게 할 경우, 상시 서버를 유지하기 위해 소모되는 스레드 낭비를 줄일 수 있다.

두 번째는 핵심, 비동기 논블로킹 모델 (Non-Blocking I/O) 모델이다. 단어만 들어도 무슨 뜻인가 싶을 것인 만큼, 하나하나 해부해서 살펴보자.

출처: https://velog.io/@octo__/posts

동기(synchronous)와 비동기(asynchronous)는 일이 순차적으로 일어나고 처리하도록 하는 방식과 그렇지 않고 한꺼번에 일어나도록 하는 방식의 차이이다. 다음 아래의 그림이 이를 설명하는 예시가 되겠다.

블록킹과 논 블록킹의 경우 blocking이 '막는다'라는 의미가 있다는 사실에 주시할 필요가 있다. 예를 들어 A라는 작업이 이루어 지고 있을 때, 설정해놓은 B라는 작업이 이뤄지지 않도록 하는 것이다. 만약 A 작업이 끝났다면, B가 이루어진다. 한 작업이 끝나면 다음 작업이 수행되는 동기(synchronous)와 비슷하지 않냐고 반문할 수 있다. 하지만 동기의 경우, 앞에 호출한 함수가 수행하여 나온 결과를 받아서 다음 함수가 수행되는 순차적인 방식인 반면에, 블로킹은 말 그대로 A라는 작업이 시행 중일 때는 B를 막는다. 이 정도이다.

 

 

그래서, 비동기 논블로킹 I/O 모델이 뭐야?

출처: https://www.geeksforgeeks.org/non-blocking-server-in-java-nio/

위의 이미지가 조금 난해할 수 있다. 하지만, 각 소캣(socket)이 하나의 스레드(thread)를 공유하는 모습을 볼 수 있다. 그렇다. 여러 개의 소캣이 하나의 스레드를 서로 순서 없이 요청이 오면 그때그때 번갈아 가면서 사용한다. 즉, 하나의 스레드로 여러 개의 소캣을 관리가 가능하며, 소캣의 연결 상태 유지가 가능하다는 것이다!

 

하지만, 이것 만으로는 더 높은 사양의 채팅서버를 만드는 것이 힘들다. 이외에 해야 추가 보완해야 할 점이 있다면:

1. 하드웨어 근본을 업그레이드: CPU의 코어 수를 늘려서 실제 물리적으로 가능한 스레드 수를 늘리거나, RAM을 늘려 더 많은 스레드를 위한 공간을 확보한다.

2. 로드 벨런싱: 서버 자체를 여러 개를 둔다. 한마디로 분산을 하는 것. 그리고 각 서버에 일을 균형(밸런싱)에 맞춰 할당한다.

3. 캐싱: 유저의 요청의 80%는 이미 이전에 했던 요청, 즉 반복요청이라고 한다. 이를 대비해서 미리미리 저장해 놓은 다음, 굳이 서버가 일을 하지 않고, 보여준다면? 수고가 훨 덜 들 것이다.

 

이렇게 이번 글도 마친다. 설명력이 부족한 제 글을 읽어 주신 것에 감사의 말씀을 전하며~

끝!

 

 

 

 

 

글을 작성하는데 도움이 된 출처들:

https://webhostinggeeks.com/blog/c10k-problem-understanding-and-overcoming-the-10000-concurrent-connections-challenge/

 

C10K Problem: Understanding and Overcoming the 10,000 Concurrent Connections Challenge | Web Hosting Geeks' Blog

Explore the world of server performance as we delve into the C10K problem – the challenge of handling 10,000 concurrent connections. Learn its origins, impacts, and the evolution of solutions from thread-based models to event-driven architectures.

webhostinggeeks.com

https://oliveyoung.tech/2023-10-02/c10-problem/

 

고전 돌아보기, C10K 문제 (C10K Problem) | 올리브영 테크블로그

오래된 과거에서 시작하는 Node.js의 비동기 처리로의 여정

oliveyoung.tech

그리고 chatGPT