Multi-core의 Concurrency를 위해선 멀티스레딩 뿐인가?

답은 아니다라고 생각한다 — 실제 현실도 그런 것 같고. 명확히 정리된 생각은 아니지만, 어제~오늘 생각했던 얘기를 풀어보겠다. 응용에 따라선 다른 방식으로 하드웨어(특히 CPU)의 concurrency를 끌어다 쓸 수 있을 것 같으니.[1]

멀티코어 / 멀티 CPU 머신이 시대의 흐름인 것은 거의 확실하다. 적어도 앞으로 수 년 정도 범위에서 CPU 기술의 방향은,

  • 메모리와의 통신 효율성 재고 — 현재 최대의 병목은 CPU – 메모리 통신이다
  • 캐쉬 용량 증대 — 앞에서 말한 효율성의 개선
  • CPU 코어 수 증가 — CPU 클럭을 올리는게 힘들어지지만 당분간은 트랜지스터 수는 늘릴 수 있으니 이 쪽일 것이다

일 거라고 생각한다. 이 중에 마지막 예측 때문에 허브 서터 같은 “대가” 들이 concurrency를 활용하기 위한 방안들을 설명하거나, 강조하고 있다. 그러나 한 프로세스 ((컴퓨터 공학의 OS 개론에서 말하는 process를 의미한다. OS에서 구분하여 자원을 관리해주고, 독립적인 메모리 영역을 갖는 단위를 의미한다.)) 의 성능 향상 범위에서 보면 이건 정확하다. 적어도 CPU 발전 방향이 코어 수 증가로 쏠려있는 상황에서는[2] 단일 프로그램의 1개 실행 속도가 올라가는데는 concurrency를 이용하지 않고는 한계에 직면한다. 뭐 “단일 프로그램”의 얘기다.

범위를 좀 더 확대해서 생각해보자.

1명의 사용자가 아니라 여러 명에게 서비스를 제공하는 “사업자” 측면을 생각해보자. 성능을 끌어올리기 위해 — 혹은 많은 사용자에게 서비스를 제공하기 위해 — 선택할 프로그래밍 모델은 세 가지 정도라고 생각된다.

  1. 멀티스레딩 or concurrent 응용을 만들어낸다. 게임 쪽에서 MMORPG 서버를 만들 때 이런 방식을 취한다.
  2. 같은 서비스를 제공하는 프로세스를 여럿 사용한다. 필요에 따라선 여러 개의 서버에 나눠 띄운다.
  3. 같은 서비스를 제공하는 여러 개의 프로세스[3] 를 만들고, 이들간의 통신 시스템을 만든다. 역시 필요에 따라선 여러 개의 서버에 나눠 띄운다.

아마 많은 경우에 프로그래밍 복잡도는 1 > 3 > 2가 아닐까. 반대로 scalability는 3 > 2 > 1 순서.

물론, 하나의 프로세스 실행을 가속할 방법은 1 밖에 없지만, 사업자 측면에서 보면 1~3 모두가 scalable한(확대 제공 가능한) 서비스를 만들어낼 수 있는 방법이다.

게임 서버 처럼 프로세스보다 작은 단위 — 예를 들어 게임 내의 NPC와 게이머의 상호작용, 게이머간의 상호작용, … — 에서 데이터 공유가 매우 많다면, 프로세스를 1개로 제한하는 수 밖에 없지만[4] , 그렇지 않은 상황에선 2/3 중에 적당한 것을 골라야 하지 않을까.

현존하는 초대형 웹 서비스들 — 웹 포탈이나 WordPress.com 같은 대형 호스팅 서비스들 — 은 3과 비슷한 구조를 가지고 있다. Load–balancer, 응용 서버 그리고 분산 DB로 이어지는 잘 정의된 분할을 가지고 있다. 물론 이런 대형 서비스들은 그것만으로 부족해서 구글의 분산 파일 시스템[5] 같은 도메인에 정말 잘 어울리는 응용을 개발해버리기도 하지만 :)

게임 서버의 경우에도 2, 3 과 같은 구현체들이 적지 않다. Sun의 Dark Star도 3과 유사한 구조를 가지고 있다 — 알려진 성능 평가는 “안습” 한 마디 빼고는 해줄 말이 없지만 -_-a.[6] 카트라이더나 많은 수의 MMOG — 사용자 간의 상호작용이 일정 수(30인 이하 수준)으로 유지되는 게임들 — 의 경우에는 3과 같은 구조를 만들어내기 쉽다. Front-end의 load-balancer와 뒷 단의 DB, 그리고 중간이 분산된 게임 서버들 구성을 하는 경우들이 있다.

이런 분산된 개별 게임 서버는 간단하게(…) 싱글 스레드/프로세스를 채용할 수 있게 된다. N모사 (내가 다니는 곳이 아닌 :p )에서 퇴사한 모군의 경우엔 싱글 스레드로 서버를 바꿨더니,

누구나 수정할 수 있게 되었어요!

라고 좋아하더라. 비슷하게 또 다른 개발자 한 분도, “굳이 멀티스레딩 쓸 이유있나요” 라는 뉘앙스의 얘기를.

사실 맞는 얘기다. 이런 구조에서 얻을 수 있는 병렬성이 있다 = 코어 수가 늘어갈 때도 분명히 이익이 있다. CPU 코어 수 만큼 게임 프로세스를 띄워버리면 되니까. 그럼 CPU를 낭비할 일도 없이(혹은 적게) 서버를 운영할 수 있게 된다.

그리고 위에서 후배 모군이 얘기한 것처럼 단일 스레드 프로그래밍은 쉽다. 굉장히 예측가능하고 인간의 뇌가 이해하기 쉽다. 다만 프로세스 간 통신이 힘들다는 근본적인 문제 때문에 만들어야하는 응용에 쓸 수 있냐없냐하는 문제가 있다. 오류가 발생해도 단순히 일부 게임들만 죽고(?) 끝나고, 개별 메시지를 처리할 때마다 오류처리를 아주 힘들게 할 이유가 없다 — 정 안되면 일부 게임을 포기하고 죽여버리면 되니까. 심지어 서버를 죽인다는 메모리 릭[7] 이 좀 있어도 일부 게임만 죽이면서(…) 프로세스를 다시 띄우는 방식으로 해결할 수 있다…[8]

그래서 MMORPG 서버처럼 무시무시하게 coupling된 시스템이 아닌 이상 잘 쪼갤 궁리부터 열심히 해야하지 않을까(…). 잘 쪼개고, 쉽게 (단일 스레드로) 가는게 개발자 건강에 유익하지 않을까 하고 잡 생각하는 밤이다.

ps. 완전히 여담이지만. 1의 구조로 가기엔 지금 우리가 가진게 너무 없다. STM이나 묵시적인 locking algorithm, 메시지 전송 같은 좀 더 편안하고, 안전하게 쓸 수 있는 primitive 혹은 개념들이 제공되기 시작했지만 멀티스레딩-concurrent programming이란 것은 저수준 시스템 프로그래밍에 가깝다;

OS에서 제공하지 않는 기능만으로 하기엔 한계가 있고, OS에서 제공하는걸 쓰자니 굉장히 저수준이 되어가고.[9]

  1. 엄밀한 의미로 말하면 concurrent-programming이 아닐 수도 있다. 모든 구성요소가 동시에 돌지 않아도 되는 내용위주로 글을 썼다. []
  2. 2003년에 2.4 Ghz CPU를 보편적으로 쓸 수 있었는데, 지금 사용하고 있는 intel Core2Duo CPU의 클럭이 겨우2.66Ghz다. 물론 캐쉬메모리의 증가, 명령어 파이프라인의 개선, 명령 실행 방식 변경(메모리 접근 재정렬), 명령의 병렬 수행 등등으로 “의미면에서의 실행속도”는 확실히 빨라지긴 했다. []
  3. 여러 프로세스가 한 묶음이 되게 []
  4. 혹은 가능하다면 통신량이 적을 곳 끼리 잘라내는 정도의 분할. 그러면 3에 조금 가까워진다. 상호작용이 많다는 문제 때문에 아무리 MMORPG 서버를 잘 만들어도, 한 서버(논리적인 의미에서)에서 처리하는 인원 수에는 한계가 있다. 가장 많은 인원을 처리한다는 Lineage II 서버도 이상적인 네트웍 상황에서 1개 서버에서 8천명 정도가 한계일 것이다. 물론 단일 서버 처리량으론 절대 작지 않다… []
  5. 검색 자체가 결과만 재빨리 나오면 되고, 굉장한 저장용량을 필요로 하지만 저장 공간 자체는 append-only 에 가깝게 쓴다는 특징들을 잘 이용해서 단순한 RAID로라면 하루 종일 디스크 교체만 해도 (전체 서버군이) 유지되기 힘든 문제를 해결했다. 논문이 꽤나 재밌지만 집에선 IEEE/ACM을 볼 수가 없군 -_- a []
  6. 서버간 병렬성으로 성능을 올리는 것은 둘째치고, 단일 서버 안에서는 당장 상용에서 쓰기 힘든 수준이라니 []
  7. 대부분의 경우 서버는 오래 떠 있을걸 가정하기 때문에, 아주 조금씩 새도 결국엔 물리 메모리를 다 채우고, 스왑핑을 하고, thrashing으로 들어가면서 CPU를 못 쓰게되고, 요청 속도를 처리 속도가 못 따라가게 되면서 서버는 사망. []
  8. 여담이지만 현재 회사 들어오기전에 저런 기능을 하는 코드를 진짜로 본 적이 있다… []
  9. 물론 boost::ASIO 팩키지나 ACE 프레임웍 처럼 각 OS의 저수준 기능을 잘 wrapping해서 제공하기도 하지만 그렇게 잘 되는 경우도 별로 흔하진 않은 것 같다. 당장 떠오르는게 저거랑 boost::thread 정도 뿐이니… Java도 epoll system call 같이 각 OS의 최적화된 I/O multiplexing을 쓰게된게 1.6 부터였던 것 같으니. []

Author: rein

나는 ...

4 thoughts on “Multi-core의 Concurrency를 위해선 멀티스레딩 뿐인가?”

  1. N모사에서 퇴사한 모군 – 싱글스레드의 강점을 널리 전파하시는 분이시죠[…] 저도 이 분의 의견에 많이 동감해요

  2. 강점이라기보단 비교 우위라고 해야하나.

    특정 응용에서는 싱글 스레드가 월등히 나은 (성능 얘기가 아니라 편의성, 프로그래밍 시간, 복잡도, 관리 편의성 등 자원 총합의 얘기) 경우가 생기지.

    뭐 잘 골라내고, 잘 만들어내는게 엔지니어의 일이겠지

  3. 전반적인 글의 기조는 동감합니다만..

    어차피 태스크 사이에 공유할 것이 거의 없다면 싱글스레드-멀티프로세스나 멀티스레드-싱글/멀티프로세스나 별로 차이가 없겠죠. 아시다시피 웹서버 팜과 같은 경우에는 stateless하게 만드는 프랙티스가 오래전부터 정착되어왔기 때문에 어느 쪽도 널리 쓰이고 있습니다. (Apache MPMs, Tomcat, …) 더군다나 싱글스레드가 가장 단순하리라는 사실을 부인할 사람은 아마..아무도 없겠죠.
    글에서도 논하셨지만, 문제는 이러한 태스크 사이에 공유할 것 = 통신할 것 생기면서 생기기 시작합니다. 공유할 것의 정도나 통신의 복잡도 (e.g. 웹서버의 세션 스토리지 vs. IRC network)는 도메인/응용에 따라 차이가 많이 나겠죠. 이런 응용 의존적인 거시적인 아키텍쳐 요소를 단순히 3가지 모델로 설명하는 부분에서 좀 무리가 있지 않나 싶습니다.

    분산시스템의 통신 구조는 physical boundary를 넘어설 때 어떻게 해야하나라고 할 때 (점점 싸지고 있지만) 비용을 감수하고 당연히 취할 수 밖에 없는 선택지인 것이고, 만약 싱글스레드와 멀티스레드의 진정한 flame war :)를 하나의 글 내에서 구현하시려면 하나의 physical entity 내 (이를테면 GFS node 내의 구현, Mysql/MongoDB/Redis Shard 내의 구현)에서의 선택 사항들을 논해야 정당하지 않을까요?

    1. 1. 응용마다 다른 부분이 있…는 정도가 아니라 많은데 단순화 했다는 점은 말씀하신대로입니다(…)

      2. 한 물리 노드 내의 구현 문제…를 논의하는게 더 정당하지 않냐고 하시면 제가 할 말이 좀 없긴한데(이글 논지에선 특히),
      이건 약간 다른 주제라고 생각합니다. 방법론적인 측면이 아니라 개별 도메인/응용에 따라 OS나 프로그래밍 언어 수준에서 제공해줄 수 있는 IPC 방법 혹은 동기화, 분산 트랜잭션 등등에 대한 요구사하잉 다를테니까요; 일단은 아주 간략하게 봤을 때의 얘기로 생각해주시면 좋겠습니다. (플레임 워를 원하는게 아니라 “이런 길도 있으니 한 방향만 보지 맙시다”니까요 ;) )

      그리고 이게 서비스하는 입장이 되면 좀 더 괴랄한 구조도 가능하기 때문에 논하기엔 덧글은 좀 짧은 거 같긴 합니다만(…). 제가 있는 곳 얘기는 아니지만 라이브 서비스 입장에선 더 재밌는 구조도 나옵니다.서버군 N개를 물리서버 M대 위에 올리는 구조에서,

      * 서버를 논리적인 단위 M(m_0, m_1, …, m_{M-1})개로 쪼개고,
      * 각각의 논리 단위를 하나의 프로세스로 표현하고,
      * 이걸 M개 머신 위에 띄우고,
      * 이걸 N개 서버 군이 머신 M개를 공유해서 동시에 진행 (다만 같은 인덱스의 m_이 뜨지 않도록…)

      같은 방식도 있습니다. 즉, 거대한 단일 서비스가 아니라 똑같은 서비스 인스턴스가 복수로 있는 거면 다른 구현이 나오고 할테니까요(…).

      여하튼(…), 멀티스레딩 한 방식 말고 좀 쉽게쉽게도 (적어도 잘 알려진 몇 가지 도메인에선) 별 무리없이 다중프로세스로 잘 가볼 수 있다라는 얘기죠(…).

Leave a Reply