클러스터 시스템에서 중요한 특징 중 하나는 **위치 투명성(location transparency)**으로, 클러스터 내의 노드나 액터가 어디에 위치해 있는지 상관없이 동일한 방식으로 접근하고 사용할 수 있다는 점입니다.
이를 통해 클러스터 시스템의 확장성과 유연성을 크게 높일 수 있습니다.
Pub/Sub 시스템에서 상태없는 서비스와 상태있는 액터 모델을 사용했을 때의 메시지 전송 빈도 효율을 비교해 보겠습니다. 특히, 이벤트 생성 시마다 브로드캐스트가 필요하지 않도록 효율적으로 설계하는 방법도 다뤄보겠습니다.
1. 상태없는 서비스 기반 Pub/Sub
- 특징: 상태없는 서비스는 일반적으로 이벤트를 수신하여 필요한 작업을 처리하고 결과를 즉시 반환하는 방식으로 동작합니다. 요청 간의 상태를 유지하지 않기 때문에 외부 데이터 소스나 캐시를 통해 필요할 때마다 데이터를 가져와야 합니다.
- 메시지 전송 빈도: 상태없는 서비스는 이벤트를 수신할 때마다 해당 이벤트에 맞는 모든 구독자에게 메시지를 브로드캐스트해야 합니다. 각 이벤트는 새로운 작업으로 처리되므로, 이벤트 생성 빈도와 메시지 전송 빈도가 거의 일치하게 됩니다.
- 비효율성:
- 매 이벤트마다 메시지를 모든 구독자에게 보내면, 구독자가 많을수록 브로드캐스트가 과도하게 발생해 네트워크와 CPU 리소스를 많이 소비하게 됩니다.
- 이벤트 빈도가 높은 시스템에서는 상태없는 서비스의 브로드캐스트 방식이 성능에 큰 영향을 줄 수 있습니다.
2. 상태있는 액터 모델 기반 Pub/Sub
- 특징: 상태있는 액터 모델은 개별 액터가 상태를 유지하며 이벤트를 처리할 수 있습니다. 액터 모델에서는 각 구독자나 구독 그룹을 개별 액터로 구현하고, 상태를 관리하면서 메시지를 전달할 수 있습니다.
- 메시지 전송 빈도: 상태있는 액터 모델은 필요할 때만 구독자에게 메시지를 전송하도록 설정할 수 있습니다.
- 예를 들어, 액터는 이벤트를 수신할 때 상태를 갱신하고, 상태 변화가 중요한 임계치나 특정 조건을 만족할 때만 구독자에게 브로드캐스트를 보낼 수 있습니다.
- 이를 통해 모든 이벤트에 대해 메시지를 전송하는 대신, 필요한 시점에만 효율적으로 메시지를 전달할 수 있습니다.
- 효율성 개선:
- 상태있는 액터 모델에서는 구독자의 상태와 조건을 관리하면서 필터링된 이벤트 전송이 가능합니다. 이는 이벤트 발생 빈도가 높더라도 불필요한 브로드캐스트를 줄이고, 네트워크 자원을 절약할 수 있습니다.
- 상태있는 액터는 이벤트의 중요도에 따라 구독자에게 전송할지 여부를 결정할 수 있기 때문에, 리소스 최적화에 유리합니다.
브로드캐스트 지양을 위한 설계 방안
이벤트를 생성할 때마다 브로드캐스트하지 않도록 하는 몇 가지 방법을 상태있는 액터 모델과 함께 적용할 수 있습니다.
필터링과 조건 기반 전송:
- 액터가 상태를 기반으로 중요한 이벤트만 구독자에게 전송하도록 설정할 수 있습니다. 예를 들어, 구독자가 설정한 특정 조건을 만족할 때만 메시지를 보냄으로써 메시지 빈도를 줄입니다.
이벤트 집계 (Aggregation):
- 여러 이벤트를 하나로 집계한 후 일정 간격으로 구독자에게 전송하는 방식입니다. 이를 통해 이벤트가 자주 발생해도 전송 빈도를 줄일 수 있습니다.
- 예를 들어, 일정 시간 내 발생한 이벤트들을 한 번에 묶어 전송하거나, 이벤트 빈도가 일정 수준에 도달할 때만 브로드캐스트를 실행하는 방식입니다.
스냅샷 기반 전송:
- 특정 상태 변화가 발생했을 때만 구독자에게 상태 스냅샷을 전송하는 방법입니다. 예를 들어, 데이터가 임계치를 넘었을 때만 전체 상태를 보내는 방식으로 메시지 전송 빈도를 줄일 수 있습니다.
결론
- 상태없는 서비스는 이벤트 발생마다 브로드캐스트를 수행하기 때문에 메시지 전송 빈도가 높아지고, 리소스를 많이 소비할 수 있습니다. 빈번한 이벤트가 발생하는 Pub/Sub 시스템에서는 비효율적입니다.
- 상태있는 액터 모델은 상태를 관리하여 필터링과 조건에 따른 전송이 가능하므로, 이벤트 발생 빈도와 관계없이 메시지 전송 빈도를 줄일 수 있습니다. 이벤트 브로드캐스트를 지양하는 시스템에서는 상태있는 액터 모델을 활용한 조건부 전송, 집계, 스냅샷 방식을 통해 효율적인 메시지 처리를 구현할 수 있습니다.
따라서 이벤트 발생 빈도가 높거나 구독자가 많은 시스템에서는 상태있는 액터 모델을 사용하는 것이 효율적이며, 브로드캐스트 빈도를 줄여 네트워크와 시스템 리소스를 절약할 수 있습니다.
상태없는 서비스의 PUBSUB
최종 사용자가 무엇을 구독하였는지 서버에서 상태관리가 되지 않기때문에 PUB/SUB을 이용하더라도 결국
모든 이벤트를 분산하는것이 아닌 모든 이벤트를 분산된 노드에게 전체 전송해야하기때문에 올바른 의미에서 분산처리 시스템으로 볼수 없습니다.
클러스터 내에서 Point To Point 전송
클러스터 내에서는 PubSub을 브로드캐스트 방식을 사용하지 않고도 , 정확하게 구독한 객체에게 전송이 가능하며
이것이 가능한 이유중에 하나는 클러스터내에서 P2P 전송을 지원하며 물리적 주소를 알 필요없이 Key와 같은
논리적으로 생성한 주소만으로 원격지의 객체에게 이벤트 전송을 할수있습니다.
클러스터 테스트 코드
// A와 B는 다른 포트로 구성된 Node입니다. testKitA = ActorTestKit.create("ClusterSystem",cluster1) testKitB = ActorTestKit.create("ClusterSystem",cluster2) @Test fun testClusterRouter(){ //테스트를 위한 준비코드 val probeA = testKitB.createTestProbe<Receptionist.Listing>() val probeB = testKitB.createTestProbe<HelloActorAResponse>() var list = testKitB.system().receptionist() list.tell(Receptionist.find(ClusterHelloActorA.ClusterHelloActorAKey, probeA.ref)) val listing = probeA.receiveMessage() //실제 활용되는 코드 val router = listing.getServiceInstances(ClusterHelloActorA.ClusterHelloActorAKey) router.forEach { it.tell(HelloA("Hello", probeB.ref)) probeB.expectMessage(HelloAResponse("Kotlin")) } }
- val router : 클러스터 내에서 해당 키를 가진 액터를 모두 탐색해냅니다.
- ClusterHelloActorA.ClusterHelloActorAKey 라는 논리적 주소만으로, B 시스템에서 A 시스템에 구성된 액터에게 이벤트를 보내고 응답을 받는 샘플입니다.