이벤트기반과 메세지기반 아키텍처의 차이를 간단하게 컴퓨터의 역사를 통해서 철학적으로 생각해보자.





과거 초기의 컴퓨터에서는 하나의 프로그램을 실행하면 그 프로그램의 수행이 끝날때까지 해당 처리만 할 수 있었다.


데이터 입출력을 생각해보자.


파일을 수정하기 위해 텍스트 파일을 열었다. 이때 하드디스크로부터 메모리로 파일의 내용을 load할 것이다.

지금에야 파일의 용량이 상당히 크더라도 금방 로딩이 되지만, 과거 성능이 좋지 않았을 때에는 해당 내용을 읽어오는 작업 만으로도 상당한 시간이 걸렸을 것이다.

처리를 하나씩만 할 수 있다면 데이터를 읽어오는 시간동안은 데이터를 읽는 작업만 수행할 수 있고 해당 작업이 끝날때까지 기다려야 한다는 것이다. 

게다가 이런 I/O작업은 I/O 디바이스에서 수행되므로 CPU는 가만히 놀고있게 된다. 굉장한 자원의 낭비이다. 

이는 지금에선 상상도 할 수 없는 끔직한 일이다. 


데이터를 읽어오는 동안 다른일을 할 수 있다면 굉장히 효율적일 것이다. 이를 실현하기 위해 프로세스, 스레드라는 개념이 만들어지며 병행처리가 가능하게 되었다.

프로세스, 스레드를 이용해 자원을 나누어 여러개의 일을 동시에 처리하는 것처럼 할 수 있다. 어떤 방법으로 자원을 공유할 것인지는 어떤 스케줄링 방식을 사용할 것인지에 따라 달라진다. 만약 이런 스케줄이 없다면 자원에 대해서 접근하는 여러 작업들간에 충돌이 일어날 것이다. 이를 막기위해 스케줄을 구현하는 스케줄러는 Lock이나 fiber등의 개념을 사용해서 충돌을 방지할 수 있다.


기본적으로 병행처리를 하는 방법은 두가지로 나눈다.

preemptive multi-task(선점형 멀티태스크)방식이과 non-preemptive multi-task(비선점형 멀티태스크)방식이다.


non-preemptive multi-task는 한 처리가 수행될 때 CPU를 독점하고 있므면서 CPU가 프로세스들의 수행제어에 관여하지 않는 방식이다. 즉 수행중인 프로세스가 자신의 처리가 다 끝나거나 혹은 자발적으로 수행을 그만할 때 비로소 제어가 다른 쪽으로 넘어갈 수 있다. 한번 프로세스가 실행되면 다른 처리는 계속 기다려야 하므로 최적의 어떠한 간격을 두고 교대를 해야한다 라는 전제조건을 릴요로 한다. Windows 3.1이나 NT와 Max OS 9가 이 방식을 사용했다. 문제가 발생하면 다른 프로세스에게 제어가 자연스럽게 넘어가지 못하므로 통째로 백업해야하는 단점도 있다. 


preemptive multi-task 방식은 CPU가 프로세스들의 수행제어에 선점적으로 관여한다. 일정 시간이 지나면 현재 진행중이던 처리를 강제로 중단한 후 다음의 프로그램이 수행되도록 한다. 이는 UNIX나  LINUX가 선택한 방식이기도 하다.


preemptive 방식에서 프로세스들은 자신의 수행이 언제 중단될지 알 수 없기 때문에 이들을 제어하는 스케줄러의 역할은 매우 중요하다. 만약 잘못한다면 데이터나 자원의 상태가 꼬이거나 충돌이 발생할 수 있기 때문이다. 이런 상태를 race condition 혹은 non thread safe라고 한다.

그리고 처리 도중 끼어들 수 있는 개념이 interrupt이며 이를 이벤트기반의 아키텍처라고 볼 수 있겠다. 즉 이벤트 기반이라는 것은 어떤 일이 발생하였을 때 해당 이벤트를 알려주고 해당 이벤트에 맞는 처리를 수행한다는 것이다. 소프트웨어 개발의 관점에서 봤을때에도 마찬가지로 이벤트를 발생시키면 이벤트를 받는 쪽에서는 해당 이벤트를 수행할 수 있다. 보통 실시간 작업처리를 위해 사용된다.


race condition을 발생하는 조건은 다음과 같다.

1. 2가지 처리가 변수를 공유하고 있음.

2. 적어도 하나의 처리가 그 변수를 변경할 수 있음.

3. 한쪽 처리가 한 단락 마무리 되기 전에, 다른 쪽의 처리가 끼어들 가능성이 있음.


race condition을 방지하거나 해결하려면 위의 사항 중 하나만이라도 처리하면 된다. 


첫번째 항목을 살펴보자. 처음부터 공유하는 것이 없도록 만들면 된다.

처음의 시스템은 메모리를 공유하는 방식이었지만 1969년 발표된 Multics라는  OS에는 다양한 기능들이 많이 들어가면서 시스템이 복잡해 졌으며 이를 해결할 방법이 필요했다. 이를 해결하기 위해 1970년에 UNICS라는 OS가 만들어졌고 이것이 현재의 UNIX이다. UNIX는 프로세스별로 메모리영역을 따로 지정하여 사용하는 구조를 가진다.


하지만 이런 방식은 성공하지 못했다. 프로세스끼리 정보를 공유해야 하는 경우도 있었기 때문에 메모리공유가 필요했던 것이다. 스레드는 여기서 나온 개념이다.



병행처리의 다른 모델로 actor 모델이라는 것이 있다.

위에서 설명한 병행처리에서는 프로세스들이 정보를 교환하는 방법으로 메모리를 공유하였다. 이를 위해서는 lock이라는 것을 이용하여 다른 프로세스의 접근을 막아야 했고 locking이 수행되다면 어떤 처리가 끝날때까지 기다려야하는 프로세스가 생기게 된다. 즉 자원이 비효율적으로 사용 될 수 있다는 것이다. 

자신(A)의 처리에 필요한 다른 처리의 수행을 위해 프로세스(B)가 필요하다고 가정해보자. 만약 A가 B에게 바로 알리게 된다면  B는 현재 수행중이던 작업에 방해를 받을 것이다. 그렇다고해서 B의 현재 작업이 끝날때까지 기다린다면 A는 그만큼 시간을 버리게 된다. 그래서  A는 해당 처리를 B에게 메세지로 남긴 후 돌아간다. 그러면 B는 자신이 수행하던 처리를 마친 후 메세지를 확인하여 처리한 후 A쪽에 메세지로써 결과를 남긴다. A는 이 메세지를 확인하고 비로소 자신이 맡긴 처리가 잘 수행되었다고 판단한다.


이것이 Actor 모델이며 이러한 처리는 대용량의 데이터를 처리할 때 적합하다. 실시간으로 처리되는 작업은 아니지만, 경우에 따라서는 자원의 효율을 극대화 하여 사용할 수 있다. Facebook이나 Twitter에서 대량의 사용자 메시지를 처리할때 사용한다. Erlang이나 Scala에서 해당 모델을 채용하고 있으며 이런 방식을 메시지 기반의 방식이라고 한다.


2번째 항목은 어떨까? 메모리를 공유하더라도 변경하지 않으면 된다. 

const는 상수화하여 값을 변경할 수 없도록 하고, Scala에서는 변수선언 키워드가 var, val 2가지로 구현되여 val은 const로 선언된다.

또한 Java에서는 class에 private한 영역을 만들어서 getter 메소드는 생성하여 읽기는 가능하지만 setter 메소드는 생성하지 않아 값을 수정할 수 없도록 하는 Immutable 패턴이 자주 사용된다.


3번째 항목을 위해서 어느 한 처리 중에는 끼어들지 않도록 하는 기법들이 생겨났다. 

그 중 하나가 서로 협력하는 스레드가 생겨났다는 것이다. 스레드는 기본적으로 preemptive하므로 non-preemptive한 스레드를 만들자라는 개념이다. fiber, coroutine, Green thread와 같은 기법을 사용하는 Ruby의 Fiber Class나 Python이나 JavaScript의 Generator가 그 예이다.

다른 하나는 다른 처리에게 해당 메모리로 접근하지 말라고 표시하는 Lock, Mutex, Semaphore를 이용하는 것이다. 이 방식에서는 deadlock의 문제를 잘 인식하고 사용해야 할 필요가 있다. 이는 굉장히 까다로운 일이다.


이를 약간 편하게 사용하기 위해 DataBase의 transation 기법을 적용한 Transactional memory 기법이 탄생했다. 해보고 실패하면 다시 처음으로 돌아가고 끝까지 성공하면 변경을 공유하는 것이다. 이는 락을 걸지 않아도 문제가 없이 병행처리가 가능하다. 하지만 다시 고쳐쓰는 작업이란 비용이 많이 드는 작업이다.





이벤트기반과 메세지기반 아키텍처는 어느 한 것만 고집할게 아니라, 시스템의 환경에 맞게 적절히 사용하는게 가장 좋겠다.






참고. 코딩을 지탱하는 기술

'Development > Etc.' 카테고리의 다른 글

type(형)  (0) 2016.02.06
언어별로 문자열 처리  (0) 2016.02.06
전역스코프 동적스코프 정적스코프  (0) 2016.02.06
상속이 is-a 관계여야 하는이유와 delegation  (0) 2016.02.06
언어별 클래스의 의미  (0) 2016.02.06

+ Recent posts