클러스터 시스템에서 중요한 특징 중 하나는 **위치 투명성(location transparency)**으로, 클러스터 내의 노드나 액터가 어디에 위치해 있는지 상관없이 동일한 방식으로 접근하고 사용할 수 있다는 점입니다.

이를 통해 클러스터 시스템의 확장성과 유연성을 크게 높일 수 있습니다.

클러스터 내에서 Point To Point 전송

클러스터 내에서는 가장 작은단위의 메시지 전송방식은 보낼대상 지점을 통해 P2P 전송이 가능합니다.

실제 중간지점의 전송계층을 경유하지 않고 클러스터내 Node내 액터간 1:1 통신을 할수 있으며 물리적인 위치를 알필요없이 위치투명성을 이용합니다.


클러스터 테스트 코드

// 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 : 위치 투명성이 있는 객체로 시스템내에 생성이 가능하며 논리적인 키를 보유한 원격지 액터에게 전송이 가능합니다.
    • ClusterHelloActorAKey 라는 논리적  키로, B 시스템에서 A 시스템에 구성된 액터에게 이벤트를 보내고 응답을 받는 샘플입니다.


클러스터 액터정의

/** HelloActor 클래스 */
class ClusterHelloActorA private constructor(
    private val context: ActorContext<HelloActorACommand>,
) : AbstractBehavior<HelloActorACommand>(context) {

    companion object {

        var ClusterHelloActorAKey:ServiceKey<HelloActorACommand> = ServiceKey.create(HelloActorACommand::class.java, "ClusterHelloActorA")

        fun create(): Behavior<HelloActorACommand> {
            return Behaviors.setup { context -> ClusterHelloActorA(context) }
        }
    }

    init {
        //Router
        context.system.receptionist().tell(Receptionist.register(ClusterHelloActorAKey, context.self))

    }
  • 로컬액터에서 클러스터 액터로 배치되는 액터로 변식하기 위해서는 단지 자신의 액터가 생성될시 Key를 등록하면 됩니다.


특정 Role이 부여된 액터배치

val clusterA = Cluster.get(testKitA.system())
// Role에 따라 작동하는 Actor 구분생성
if (clusterA.selfMember().hasRole("helloA")) {
	actorA = testKitA.spawn(ClusterHelloActorA.create(), "actorA")
}
  • 클러스터는 설정을 통해 Role이 부여되며 , 부여된 Role에서만 우리가 설계한 액터를 배치할수 있습니다.
  • 기능단위 마이크로 서비스가 꼭 저장소로 분리되고 CI가 별도 구성되어 관리포인트가 증가하는것을 방지할수 있으며 응집력있는 코드를 유지할수 있는것이 Akka Cluster의 특징입니다. 
    • 상담히 진보적인 아이디어로 MSA로 인해 코드가 분산되고 관리되어야하는 포인트를 증가하는것을 방지할수 있습니다. ( MSA의 대표적인 단점을 해결 )


가장 작은단위의 전송인 PointToPoint 전송을 기본으로 지원하게되면  Topic단위 또는 이벤트 버스를 구현할때도 최소 전송단위가 존재하기때문에  브로드캐스트를 효율적인 전송을 위해 활용해 수신처에서 필터기법을 사용할수도 있지만

필수적으로 필요로 하지는 않게됩니다.



Pub/Sub 시스템에서 상태없는 서비스와 상태있는 액터 모델을 사용했을 때의 메시지 전송 빈도 효율을 비교해 보겠습니다. 특히, 이벤트 생성 시마다 브로드캐스트가 필요하지 않도록 효율적 설계하는 방법도 다뤄보겠습니다.

1. 상태없는 서비스 기반 Pub/Sub

  • 특징: 상태없는 서비스는 일반적으로 이벤트를 수신하여 필요한 작업을 처리하고 결과를 즉시 반환하는 방식으로 동작합니다. 요청 간의 상태를 유지하지 않기 때문에 외부 데이터 소스나 캐시를 통해 필요할 때마다 데이터를 가져와야 합니다.
  • 메시지 전송 빈도: 상태없는 서비스는 이벤트를 수신할 때마다 해당 이벤트에 맞는 모든 구독자에게 메시지를 브로드캐스트해야 합니다. 각 이벤트는 새로운 작업으로 처리되므로, 이벤트 생성 빈도와 메시지 전송 빈도가 거의 일치하게 됩니다.
  • 비효율성:
    • 매 이벤트마다 메시지를 모든 구독자에게 보내면, 구독자가 많을수록 브로드캐스트가 과도하게 발생해 네트워크와 CPU 리소스를 많이 소비하게 됩니다.
    • 이벤트 빈도가 높은 시스템에서는 상태없는 서비스의 브로드캐스트 방식이 성능에 큰 영향을 줄 수 있습니다.

2. 상태있는 액터 모델 기반 Pub/Sub

  • 특징: 상태있는 액터 모델은 개별 액터가 상태를 유지하며 이벤트를 처리할 수 있습니다. 액터 모델에서는 각 구독자나 구독 그룹을 개별 액터로 구현하고, 상태를 관리하면서 메시지를 전달할 수 있습니다.
  • 메시지 전송 빈도: 상태있는 액터 모델은 필요할 때만 구독자에게 메시지를 전송하도록 설정할 수 있습니다.
    • 예를 들어, 액터는 이벤트를 수신할 때 상태를 갱신하고, 상태 변화가 중요한 임계치나 특정 조건을 만족할 때만 구독자에게 브로드캐스트를 보낼 수 있습니다.
    • 이를 통해 모든 이벤트에 대해 메시지를 전송하는 대신, 필요한 시점에만 효율적으로 메시지를 전달할 수 있습니다.
  • 효율성 개선:
    • 상태있는 액터 모델에서는 구독자의 상태와 조건을 관리하면서 필터링된 이벤트 전송이 가능합니다. 이는 이벤트 발생 빈도가 높더라도 불필요한 브로드캐스트를 줄이고, 네트워크 자원을 절약할 수 있습니다.
    • 상태있는 액터는 이벤트의 중요도에 따라 구독자에게 전송할지 여부를 결정할 수 있기 때문에, 리소스 최적화에 유리합니다.

브로드캐스트 지양을 위한 설계 방안

이벤트를 생성할 때마다 브로드캐스트하지 않도록 하는 몇 가지 방법을 상태있는 액터 모델과 함께 적용할 수 있습니다.

  1. 필터링과 조건 기반 전송:

    • 액터가 상태를 기반으로 중요한 이벤트만 구독자에게 전송하도록 설정할 수 있습니다. 예를 들어, 구독자가 설정한 특정 조건을 만족할 때만 메시지를 보냄으로써 메시지 빈도를 줄입니다.
  2. 이벤트 집계 (Aggregation):

    • 여러 이벤트를 하나로 집계한 후 일정 간격으로 구독자에게 전송하는 방식입니다. 이를 통해 이벤트가 자주 발생해도 전송 빈도를 줄일 수 있습니다.
    • 예를 들어, 일정 시간 내 발생한 이벤트들을 한 번에 묶어 전송하거나, 이벤트 빈도가 일정 수준에 도달할 때만 브로드캐스트를 실행하는 방식입니다.
  3. 스냅샷 기반 전송:

    • 특정 상태 변화가 발생했을 때만 구독자에게 상태 스냅샷을 전송하는 방법입니다. 예를 들어, 데이터가 임계치를 넘었을 때만 전체 상태를 보내는 방식으로 메시지 전송 빈도를 줄일 수 있습니다.

결론

  • 상태없는 서비스는 이벤트 발생마다 브로드캐스트를 수행하기 때문에 메시지 전송 빈도가 높아지고, 리소스를 많이 소비할 수 있습니다. 빈번한 이벤트가 발생하는 Pub/Sub 시스템에서는 비효율적입니다.
  • 상태있는 액터 모델은 상태를 관리하여 필터링과 조건에 따른 전송이 가능하므로, 이벤트 발생 빈도와 관계없이 메시지 전송 빈도를 줄일 수 있습니다. 이벤트 브로드캐스트를 지양하는 시스템에서는 상태있는 액터 모델을 활용한 조건부 전송, 집계, 스냅샷 방식을 통해 효율적인 메시지 처리를 구현할 수 있습니다.

따라서 이벤트 발생 빈도가 높거나 구독자가 많은 시스템에서는 상태있는 액터 모델을 사용하는 것이 효율적이며, 브로드캐스트 빈도를 줄여 네트워크와 시스템 리소스를 절약할 수 있습니다.


가정 및 설정 추가

  • 스케일아웃을 위한 중간 장치 10대: 각 장치는 필터링을 통해 수신한 이벤트 중 일부만 최종 사용자에게 전송할 수 있습니다.
  • 상태없는 Pub/Sub: 모든 이벤트가 모든 사용자에게 전송되므로, 중간 장치들은 단순히 이벤트를 전달하는 역할만 수행한다고 가정합니다.
  • 상태있는 액터 클러스터Akka 분산 Pub/Sub: 중간 장치가 이벤트 필터링을 추가적으로 수행하여, 각 장치가 받은 이벤트 중 10%만 최종 사용자에게 전송한다고 가정합니다.

이를 바탕으로 이벤트가 발생할 때마다 중간 장치에서 최종 사용자까지의 네트워크 전송 수를 다시 계산해 보겠습니다.

네트워크 이벤트 전송 수 계산

  1. 상태없는 Pub/Sub:

    • 모든 이벤트가 모든 사용자에게 전송되므로, 중간 장치를 거쳐 최종 사용자까지의 네트워크 전송 수가 단순히 사용자 수에 비례하여 증가합니다.
    • 네트워크 전송 수 = (이벤트 수 × 사용자 수) + (중간 장치 수 × 이벤트 수)
    • 전송 수=(이벤트 수×10,000)+(10×이벤트 수)\text{전송 수} = (\text{이벤트 수} \times 10,000) + (10 \times \text{이벤트 수})전송 =(이벤트 ×10,000)+(10×이벤트 )
  2. 상태있는 액터 클러스터: 액터가 아니더라도 BroadCast를 사용하지 않고 중간 필터가능

    • 각 이벤트의 10%만 중간 장치를 통해 최종 사용자에게 전송됨.
    • 네트워크 전송 수 = (이벤트 수 × 사용자 수 × 0.1) + (중간 장치 수 × 이벤트 수)
    • 전송 수=(이벤트 수×10,000×0.1)+(10×이벤트 수)\text{전송 수} = (\text{이벤트 수} \times 10,000 \times 0.1) + (10 \times \text{이벤트 수})전송 =(이벤트 ×10,000×0.1)+(10×이벤트 )
  3. Akka 분산 Pub/Sub:

    • 이벤트의 5%만 최종 사용자에게 전송되며, 중간 장치는 이벤트의 10%를 필터링 후 최종 사용자에게 전달함.
    • 네트워크 전송 수 = (이벤트 수 × 사용자 수 × 0.05 × 0.1) + (중간 장치 수 × 이벤트 수)
    • 전송 수=(이벤트 수×10,000×0.05×0.1)+(10×이벤트 수)\text{전송 수} = (\text{이벤트 수} \times 10,000 \times 0.05 \times 0.1) + (10 \times \text{이벤트 수})전송 =(이벤트 ×10,000×0.05×0.1)+(10×이벤트 )

이를 바탕으로 단위별 비교 표를 작성하겠습니다.

이벤트 수상태없는 Pub/Sub 전송 수상태있는 액터 클러스터 전송 수Akka 분산 Pub/Sub 전송 수
110,0101,01051
10100,10010,100510
1001,001,000101,0005,100
1,00010,010,0001,010,00051,000
10,000100,100,00010,100,000510,000
100,0001,001,000,000101,000,0005,100,000

분석 및 결론

  • 상태없는 Pub/Sub는 여전히 모든 이벤트를 모든 사용자에게 브로드캐스트하기 때문에 네트워크 트래픽이 매우 높습니다. 중간 장치가 있더라도, 단순한 중계 역할만 하기 때문에 네트워크 전송 수에 큰 영향을 주지 않습니다.
  • 상태있는 액터 클러스터는 상태 기반 필터링을 통해 전체 이벤트의 약 10%만 최종 사용자에게 전달되므로, 상태없는 Pub/Sub에 비해 네트워크 전송 수가 크게 줄어듭니다.
  • Akka 분산 Pub/Sub는 필터링과 구독 그룹을 통해 추가적으로 10배의 네트워크 트래픽 절감 효과가 있으며, 중간 장치에서도 필터링이 적용되므로 네트워크 전송 수가 가장 낮습니다.

이와 같은 구성에서는 Akka 분산 Pub/Sub 방식이 가장 효율적이며, 특히 이벤트 발생 수가 많거나 네트워크 부하가 높은 환경에서 큰 절감 효과를 제공합니다.


다음장에서는 전송지점 어딘가에 브로드 캐스트가 항상 존재해야하는 전통적인 StateLess PubSub에서 상태기반의 분산처리 가능한 PubSub 을 알아보겠습니다.




  • No labels