Actor model and akka

다음 프로젝트로 scalable 한 게임 서버프레임워크 구현을 진행 중이다. 아직 구상 중이라 결정된 것은 없지만, scalability와 functional 한 특성을 동시에 살릴 수 있는 scalaakka프레임워크가 후보로 들어왔고 이에 대해 간단하게 정리하여 발표할 기회가 있었다. 일단 발표자료는 간단하게 키워드들만 적었기에 이에 대해 보충 설명을 해보고자 한다.

akka는 무엇인가

akka는 scala로 구현된 concurrency 제어를 위해 actor model을 도입한 프레임워크로 java와 scala API를 제공한다. 우선 akka는 actor model을 기본으로 하고 있기 때문에 akka의 특성을 이해하려면 actor model을 이해해야 한다.

Actor의 특징

actor model은 간단히 설명하면 behavior, state, mailbox로 구성된 actor를 기본 단위로 하는 message processing을 이용하여 behavior를 비동기적으로 실행하는 model이다. 이때 기본단위가 되는 actor는 몇 가지 특징이 있다. 우선 각 actor는 서로 간에 공유하는 자원이 없고 서로간의 state를 건드릴 수 없고, 오로지 message를 이용해서만 간섭할 수 있다.

message는 mailbox에 쌓였다가 들어온 순서대로 처리된다. 실행되는 behavior는 message에 의해 결정되고, 할 수 있는 일은 자신의 state를 바꾸거나, child actor를 만들거나, child actor를 죽이거나 다른 actor에 message를 보낼 수 있다.

actor model의 actor는 사실 OOP에서 말하는 object와 매우 비슷하다. object는 member variable(state)을 가지고 있고, 어떤 방식으로 동작할지 method(behavior)를 가지고 있다. method는 다른 object를 만들거나, 자기가 관리하는 object를 부수거나 다른 object의 method를 호출하는 일을 한다.

현대의 OOP 언어들(Java, C#, c++ 등)만을 사용한 사람들은 message를 이용해 method를 호출한다는 개념이 익숙하지 않을 수 있다. 하지만 과거의 pure한 OOP 언어들(Simula, Smalltalk)에서는 다른 object에 message를 보내면 받은 message에 해당하는 method를 부른다는 개념이 있고, 이것이 간략화되어 object의 method를 호출한다는 개념이 된 것이다. 그렇다면 OOP의 object와 actor model의 actor는 어떤 차이가 있을까?

그 차이는 단 한 가지이다. Object의 method는 message를 보낸 context에서 바로 실행되어 method가 끝날 때까지 기다리지만, actor model의 actor는 message를 보낸 context와 독립적인 context에서 비동기적으로 실행된다는 것이다.

Actor model을 사용하는 이유

사실 Actor model은 그리 새로운 개념이 아니다. 처음 그 개념이 나온 것은 1973년의 일로 프로그래밍 모델중에서는 나름 오래된 편에 속하는 개념이다. 이런 오래된 개념이 요새와서 다시 각광을 받는 이유는 multi processing에 적합한 개념이기 때문이다.

더 이상 moore's law가 적용되지 않기 때문에 CPU vendor들은 하나의 CPU에 여러개의 프로세스를 장착하여 연산속도를 증가시키기 시작했다. multi-core환경을 효율적으로 사용하려면 여러개의 thread를 이용하여 구현하는 것이 중요하다.

하지만 shared resource를 가지는 멀티쓰레드 환경에서는 여러가지 문제들(race condition, deadlock, blocking call 등)이 발생하기 쉽기 때문에 이를 회피하기 위한 패턴 혹은 모델들이 여러가지 나오게 되었고, 그 과정에서 actor model이 다시 각광받기 시작하였다.

Actor model의 장점과 단점

Actor model의 가장 큰 장점은 이해하기 쉽다는 것이다. message를 받으면 그에 맞는 behavior를 실행한다는 매우 간단한 동작원리와 다른 것에 영향을 받지 않는다는 특징 때문에 실행 순서를 이해하고 결과를 예측하기 매우 쉽다. 또한, 모든 간섭을 message를 통해서 한다는 것도 큰 장점이다. 우선 shared resource가 존재하지 않기 때문에 shared resource들로 말미암아서 생기던 문제들(race condition, deadlock 등)이 발생하지 않는다. 그리고 message가 serializable하기만 한다면 같은 서버에서 실행하던 Actor를 다른 서버에서 실행하여 message를 주고받는 것도 가능하므로 손쉽게 서버를 scale-out할 수 있다.

물론 actor model이 장점만 가지는 것은 아니다. shared state를 가지지 않고 모든 통신을 message로 하는 것은 control flow를 제어하고 correctness를 보장하는 것에는 큰 장점이었지만 속도 면에서는 큰 단점이 된다.

Concurrency in akka

그렇다면 scala의 구현체인 akka는 어떻게 actor를 사용할지 다음 코드를 통해서 알아보도록 하자.

Actor 를 상속받아 receive method만 구현하면 Actor로 사용할 수 있다. receive method Any타입을 받을 수 있기 때문에 보통 패턴매칭을 이용하여 구현한다. Actor에 메세지를 보내는 방법은 크게 3가지가 있다.

첫번째 방법은 ActorReftell method를 이용해서 메세지를 보내는 것이다.

하지만 이런 방식은 Actor에서 응답을 보내주는 것을 처리할 방법이 없다. 그래서 사용되는 두번째 방법은 메세지의 응답을 기다리는 방법이다.

Inbox를 이용하여 message를 보내면 recevie method를 통해서 Actor가 message를 처리하고 응답을 보내기를 기다린다. timeout시간을 주기는 하지만 이렇게 하면 결국 서로 다른 2개의 Actor에서 서로를 기다리면서 deadlock이 생길 수 있다. 그렇기 때문에 akka에서는 Actor안에서는 Inbox를 이용하여 synchronous하게 메세지 보내는 것을 추천하지 않는다. 그래서 대부분의 통신은 3번째 방법을 사용한다.

위의 방법은 ask 함수를 이용하여 Future객체를 만들고 callback을 등록하여 asynchronous하게 처리할 수 있게 해준다.

이 외에도 akka의 중요한 특징으로 concurrency외에 scalability와 fault-tolerance를 들 수 있다. akka는 serializable 메시지들만을 이용하였다면, 물리적으로 다른 서버에 있는 actor와 메시지를 주고받을 수 있게 해준다. 이를 이용해서 scale-out을 쉽게 구현할 수 있게 해준다. 또한, child actor를 만들어서 메시지를 주고 모니터링할 수 있는 시스템을 제공해주기 때문에 손쉽게 오류에서 복구할 수 있도록 해준다.

쓰다 보니 의욕이 떨어지고 있는 관계로 이 두 가지에 관해서는 다음 기회에 더 자세히 글을 쓰도록 하겠다.


p.s. 위의 코드는 activator에 들어 있는 hello-akka tutorial을 기반으로 작성되었다.

댓글

  1. 고맙습니다 이해가 잘 되네요 ㅎㅎ

    답글삭제
  2. 고맙습니다 이해가 잘 되네요 ㅎㅎ

    답글삭제
  3. 잘 읽고 갑니다~어려운 개념인데 이걸 보니 조금이나마 이해가 되네요 ㅎㅎ

    답글삭제
  4. 이글을 읽고나니 부족한 머리로 그나마 이해가 되네요 감사합니다

    답글삭제

댓글 쓰기

이 블로그의 인기 게시물

[C++] enum class - 안전하고 쓰기 쉬운 enum

RAII는 무엇인가

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

[Python] cache 데코레이터로 최적화하기

[Web] SpeechSynthesis - TTS API