1.1 아카란 무엇인가?
아카는 액터 프로그래밍 모델, 실행 환경 그리고 확장 가능한 애플리케이션을 구축할 때 필요한 여러 지원 도구를 제공하는 툴킷.
1.2 액터: 간략한 정리
- 액터는 설정과 메시지 브로커(message vroker) 설치라는 부가 비용이 들지 않는 메시지(message queue)와 비슷하다.
- 액터는 프로그래밍 가능한 메시지 큐를 아주 작은 크기로 줄인 것이다.
- 각 액터는 메시지를 받기 전까지는 아무런 일도 수행하지 않는다.
- 액터가 받는 메시지는 생성된 다음에는 변경될 수 없다(불변 immutable)
- 액터는 한 번에 하나의 메시지를 받을 수 있으며, 메시지를 받을 때마다 어떤 동작을 수행한다.
- 액터가 수행하는 모든 것은 비동기적으로 실행된다.
1.3 확장성에 대한 두 가지 접근 방법
아래의 두 가지 방식으로 비교해보자
- 전통적인 접근 방법
- 액터 프로그래밍 모델
1.4 전통적인 규모 확장
시작은 서버 한 대로 시작. 모든 애플리케이션 데이터는 메모리에 로드한다.
전통적인 규모 확장과 내구성 : 모든 것을 데이터베이스에 저장
가용성을 위해 LB와 프론트 서버를 두고 인-메모리 객체를 다루지 않도록 데이터 베이스를 추가한다. Dao를 통해 데이터베이스 명령을 실행한다.
따라오는 부가 작업은 아래와 같다.
- 인-메모리 방식에서 발생하지 않는 DB 오류에 대한 고려가 필요하다.
- 인-메모리 버전에 동시성을 위한 락이 존재한다. DB 라이브러리를 사용해 이를 대체할 방법을 찾아야 한다
- 인-메모리 버전에 비해 단위 테스트를 실행하는 데 걸리는 시간이 길어진다. 테스트를 위해 로컬 서버에서 데이터베이스를 추가해야 하며, 테스트 격리에 필요한 데이터베이스 테스트 유틸리티를 추가해야 한다.
인-메모리 코드를 DB 호출로 변환하게 되면 네트워크로 인한 부가 비용으로 인해 성능 문제가 발생할 수 있다. 따라서 적합한 DB 구조를 설계해야 하며, 기존에 이용하던 코드를 거의 재활용할 수 없다.
전통적인 규모 확장과 상호 작용 : 폴링
사용자와 상호 작용하는 기능을 추가한다고 하자. 만약 DB의 데이터를 분석하여 사용자에게 알림을 주어야 한다면 주기적으로 DB를 풀링 해야 한다. DB는 이전보다 훨씬 많은 부하를 받게 되며, 이는 DB성능에 영향을 줄 수 있다. 하지만 DB가 웹 서버로 데이터를 '푸시'하기란 불가능하므로 별다른 대안이 없다. 또한 빠르게 사용자에게 알림을 보내기 위해 메시지 큐를 추가하게 된다. 이로써 애플리케이션의 복잡도가 증가하며 코드 배포 전 메시지 큐를 설치해야만 하는 번거로움이 있다.
전통적인 규모 확장과 상호 작용 : 웹 서비스
서버가 제공하는 서비스 중 하나가 자주 실패한다고 하자. 가장 나쁜 방식(시간이 많이 지난 뒤 타임아웃이 등)으로 문제가 발생할 때, 이 실패로 인해 문제없던 다른 서비스도 실패하는 등 영향을 끼치게 된다.
1.5 아카를 사용한 규모 확장
목표는 앞서 살펴본 전통적인 규모 확장의 단점들을 보완하는 것이다. 또한 애플리케이션 코드를 단 한 번 작성하고 그것이 우리가 원하는 대로 규모 확장을 처리해준다면 더할 나위 없이 좋다.
아카를 사용한 규모 확장과 내구성 : 메시지 송수신
메모리에 추가되는 모든 메시지에 대해 액터가 로그에 저장한다. 코드를 DB연산으로 작성하지 않아도 액터는 메시지를 로그에 보내고, 애플리케이션이 시작할 때 로그에서 메시지를 다시 받는다.
모든 변경 사항은 이벤트 시퀀스로 유지한다. 이런 종류의 데이터베이스를 저널(journal)이라 부르며 이런 기법은 이벤트 소싱(event sourcing)이라 부른다. 저널은 모든 이벤트를 순서대로 저장하며, 저장했던 순서 그대로 이벤트를 다시 불러온다.
이런 방식의 문제는 한 서버에 모든 데이터를 담고 있다는 것이다. 이는 샤딩(sharding) 또는 파티셔닝(partitioning)을 통해 해결할 수 있다. 데이터를 여러 서버로 분산시키는 것이다.
아카를 사용한 규모 확장과 상호 작용 : 푸시 메시지
웹 애플리케이션의 사용자마다 DB를 폴링 하는 대신, 해당 사용자의 웹 브라우저로 직접 메시지를 보내 이벤트를 통지할 방법을 찾을 수 있다.
아카를 사용한 규모 확장과 실패 : 비동기 디커플링
컴포넌트의 작동 여부와 관계없이 다른 컴포넌트의 동작은 유지되어야 한다. 이벤트 기반 접근 방법을 사용할 시 다음과 같은 장점이 있다.
- 컴포넌트 사이의 의존 관계를 최소화한다. 이벤트를 수신하는 객체를 알 필요가 없으며, 어떤 일이 벌어질지 신경 쓰지 않아도 된다.
- 애플리케이션 컴포넌트들이 시간상으로 더 느슨하게 연결된다.
- 컴포넌트들의 위치가 디커플링 되어있다. 이때의 위치는 물리적 위치를 말한다.
아카의 접근 방법 : 메시지 송신과 수신
기존엔 한 객체가 다른 객체의 메서드를 직접 호출했다면, 아카는 이벤트를 사용해 통신하는 방식을 취한다. 이때의 핵심 요구 사항은 어떤 이벤트와 그다음 이벤트가 의존적인 경우, 모든 액터가 한 번에 하나씩 메시지를 순서대로 보내고 받을 수 있어야 한다는 점이다.
아카엔 슈퍼바이저(supervisor) 객체가 존재한다. 슈퍼바이저는 다른 컴포넌트를 감시하며 문제가 생긴 컴포넌트를 재시작한다. 재시작 이외 문제가 발생할 시 동작할 적절한 처리를 슈퍼바이저가 수행하는 것이다.
액터: 한 가지 프로그래밍 모델로 수직/수평 확장을 동시에 할 수 있음
비동기 모델
애플리케이션의 규모를 여러 서버에 걸쳐 키우고 싶을 때 가장 중요한 요구사항은 비동기적이어야 한다는 것이다. 어떤 컴포넌트가 다른 컴포넌트의 응답을 아직 받지 못했을 때도 계속해서 필요한 일을 수행할 수 있어야 한다.
액터 연산
액터는 액터 모델을 이루는 기본 단위다. 모든 컴포넌트는 액터이다. 액터는 생성(create), 송신(send), 상태 변화(become), 감독(supervise)이라는 네 가지 핵심 연산만을 제공하는 경량 프로세스(lightweight process)다. 이런 모든 연산은 비동기적이다.
송신
액터가 다른 액터와 의사소통하는 방법은 오직 메시지를 보내는 방법밖에 없다. 액터는 내부 상태에 대한 외부 접근을 허용하지 않는다. 메시지 송신은 항상 비동기적이며, 발사 후 망각(fire and forget) 스타일로 이뤄진다. 액터는 메시지를 한 번에 하나씩만 받기에, 송신 액터와 수신 액터 사이에서는 전송된 메시지의 순서가 유지된다.
생성
액터는 다른 액터를 생성할 수 있으며, 이는 액터의 계층 구조를 만들어낸다.
상태 변화
상태 기계(state machine)는 어떤 시스템이 특정 상태에 있을 때만 특정 동작을 수행하도록 보장하고 싶을 때 사용된다. 액터는 한 번에 메시지를 하나씩만 받으며, 이는 상태 기계를 구현하는데 아주 편리한 특성이다. 액터가 메시지를 처리하는 방법을 바꾸고 싶을 경우는 액터의 행동 양식(behavior)을 변경하면 된다.
감독
액터는 자신이 만든 다른 액터를 감독해야 한다. 그렇기에 어떤 액터든 슈퍼바이저가 될 수 있다(단, 자신이 만든 액터에 대해서만 가능하다).
1.7 아카 액터
아카가 액터 모델을 어떻게 구현하는지 살펴보자.
액터 시스템
아카는 ActorSystem을 제공하여 최상위 (top-level) 액터를 만들 수 있도록 한다. 애플리케이션의 모든 액터 위에 최상위 액터를 오직 하나만 만드는 것이 일반적인 패턴이다. ActorSystem은 만들어진 액터 자체를 반환하지 않고 액터에 대한 주소를 반환한다. 이 주소를 ActorRef라고 부르며 액터에게 메시지를 보내려면 ActorRef를 사용해야 한다.
때로 액터 시스템에서 액터를 찾아야 할 필요가 있는데 그럴 때 ActorPath를 사용한다. 액터의 계층 구조는 URL의 경로 구조와 비슷하며, 모든 액터에는 이름이 있는데, 이름은 같은 계층 내에서 유일해야 한다.
ActorRef, 우편함, 액터
메시지는 액터의 ActorRef로 보내진다. 모든 액터에는 우편함(mailbox)이 있다. 우편함은 큐와 비슷하며, ActorRef에 보내진 메시지는 우편함에 저장되어 처리를 기다린다. 액터는 한 번에 하나씩 우편함에 도착한 순서대로 메시지를 처리한다.
디스패처
액터는 디스패처(dispatcher0에 의해 어느 순간 깨어난다. 디스패처가 우편함에 있는 메시지를 액터에게 푸시한다.
디스패처의 유형은 메시지를 전달할 때 사용할 스레딩 모델을 결정한다. 대부분의 액터는 여러 스레드에 의해 메시지를 푸시받는다.
디스패처 위에서 실행되므로 액터는 경량이다. 액터의 수가 스레드의 수와 정비례할 필요는 없다. 아카 액터는 스레드보다 공간을 훨씬 덜 차지하며, 1GB 메모리에 약 2,700,000개의 액터를 넣을 수 있다(스레드는 1GB에 4096개 밖에 들어가지 못한다).
애플리케이션에서 사용할 디스패처와 우편함을 설정하고 튜닝할 수 있으므로 성능을 아주 유연하게 튜닝할 수 있다.
액터와 네트워크
아카 액터가 네트워크를 통해 통신하는 방법을 알아보자. ActorRef는 근본적으로 액터의 주소다. 네트워크를 도입할 때 그 주소를 실제 액터와 연관시키는 방법만 바꿔 문제를 해결한다. 아카에서 주소가 local인지 remote인지 알아서 주소 해석 부분의 설정만 변경하면 전체 애플리케이션의 규모를 확장할 수 있다.0