브로커 로드 밸런싱 | 개념

브로커 로드 밸런싱 | 개념 (Broker load balancing | Concepts)

Pulsar는 Pulsar 클러스터 전반의 리소스를 효율적으로 활용하도록 견고한 로드 밸런싱 지원을 제공해요. 로드 밸런싱은 메시지와 파티션을 브로커와 컨슈머 사이에 고르게 분산해 핫스팟을 막고 성능을 최적화해요. 이 페이지에서는 로드 밸런싱을 시작하기 전 알아야 할 핵심 구성 요소들(브로커, 프로듀서, 컨슈머, 번들, 번들 분할·언로드 전략 등)을 정리해요.

출처: 문서

본문

Pulsar는 Pulsar 클러스터 전반의 리소스를 효율적으로 활용하도록 견고한 로드 밸런싱 지원을 제공해요. Pulsar의 로드 밸런싱은 메시지와 파티션을 브로커와 컨슈머 사이에 고르게 분산해 핫스팟을 막고 성능을 최적화해요.

로드 밸런싱을 시작하기 전에 리소스가 효율적으로 활용되고 다양한 워크로드를 시스템이 효과적으로 처리할 수 있도록 핵심 구성 요소를 검토하는 게 중요해요.

브로커 (Brokers)

Pulsar 클러스터에서 브로커는 여러 토픽과 파티션의 메시지를 제공(serve)하는 역할을 해요. 브로커 로드 밸런싱은 각 브로커가 부하의 비례적 몫을 처리하도록 보장해요.

프로듀서 (Producers)

Pulsar의 프로듀서는 토픽에 메시지를 게시하는 역할을 해요. Pulsar 클라이언트(프로듀서)는 메시지를 게시하기 위해 브로커에 연결해요. 프로듀서 로드 밸런싱(즉 Pulsar의 연결 풀링 메커니즘)은 프로듀서가 브로커 전체에 분산되도록 해 단일 브로커에 너무 많은 연결이 몰리지 않게 해요.

컨슈머 (Consumers)

Pulsar의 컨슈머는 토픽에서 메시지를 소비하는 역할을 해요. 컨슈머 로드 밸런싱이 어떻게 구성되는지(즉 exclusive 또는 shared 컨슈머, 또는 자동 리밸런싱을 쓸지)에 따라 고른 부하 분산을 보장할 수 있어요.

토픽 (Topics)

토픽은 클라이언트가 메시지를 게시하고 소비하는 기본 단위예요. 관련 토픽은 네임스페이스로 논리적으로 묶여요. 메타데이터를 효율적으로 관리하고 시스템을 통과하는 모든 토픽을 추적하기 위해 Pulsar는 네임스페이스의 파티션을 기준으로 토픽을 그룹화해 토픽 번들을 만드는 전략을 사용해요.

번들 (Bundles)

번들은 Pulsar에서 특정 네임스페이스의 일부 파티션 범위를 나타내며, 네임스페이스의 전체 해시 범위 중 일부분을 구성해요.

번들은 Pulsar에서 중간 계층 그룹을 나타내기 위해 도입되었어요. 각 번들은 할당 단위(assignment unit)로, 토픽이 토픽 레벨이 아니라 번들 레벨에서 브로커에 할당된다는 뜻이에요. 네임스페이스가 가질 번들 수와 그 수를 고르는 방법은 Namespace bundles를 참고해요.

브로커 로드 밸런싱 (Broker load balancing)

브로커 로드 밸런서 구성 요소는 클라이언트와 브로커 사이에 있는 "교통 경찰(traffic cop)"과 같아요. 브로커 리소스 사용량(예: CPU, 메모리, 네트워크 IO)과 토픽/번들 부하(예: 처리량) 같은 동적 부하 데이터에 기반해 브로커 전체에 토픽 세션을 분산해요.

제대로 균형이 맞춰지면 브로커는 늘어난 트래픽을 처리하고 시스템이 성장하는 워크로드를 수용하도록 매끄럽게 확장할 수 있어요. 로드 밸런싱은 병목을 막고 클러스터 리소스가 최적으로 활용되도록 하여 더 나은 처리량과 더 낮은 메시지 처리 지연을 이끌어요.

토픽 번들링 (Topic bundling)

토픽 번들링은 토픽을 번들로 그룹화하는 과정을 말해요. Pulsar는 네임스페이스 안에서 토픽을 번들로 구성해요. 각 번들은 파티션의 범위이고, Pulsar는 이 번들을 브로커 전체에 자동으로 분산해 로드 밸런싱을 이룰 수 있어요. 브로커가 자신이 할당받은 번들을 독립적으로 관리할 수 있으므로 클러스터가 더 효율적으로 확장할 수 있어요.

예를 들어,

  • 토픽 부하 통계(예: 메시지 비율)는 번들 계층에서 집계되어 모니터링할 부하 샘플의 카디널리티를 줄여줘요.
  • 동적 토픽-브로커 할당에서 Pulsar는 이 매핑을 번들 레벨에 영속화해 동적 토픽-브로커 소유권을 저장하는 공간을 줄여줘요.

Pulsar는 변화하는 워크로드에 맞춰 브로커, 프로듀서, 컨슈머 수를 동적으로 확장할 수 있어요. 브로커가 추가되거나 제거되면 Pulsar가 파티션과 번들의 재분산을 자동으로 처리해요.

워크플로 (Workflow)

토픽을 번들로 그룹화하는 워크플로는 다음과 같아요.

1단계: 네임스페이스를 번들로 샤딩 (shard namespaces into bundles)

내부적으로 네임스페이스가 생성될 때 네임스페이스는 번들 목록으로 샤딩돼요. defaultNumberOfNamespaceBundles(기본값 32, 생성 시 숫자를 주지 않으면)이고, pulsar/system 네임스페이스는 defaultNumberOfSystemNamespaceBundles(기본값 64)예요. 이 기본값들은 Pulsar 5.0.0부터 적용돼요(5.0.0-M1/M2 마일스톤 이후). 그 마일스톤들은 여전히 브로커 생성 네임스페이스(pulsar/system 포함)에 defaultNumberOfNamespaceBundles=4를 사용해요. Namespace bundles를 참고해요.

2단계: 토픽을 번들에 할당 (assign topics to bundles)

토픽이 pub/sub 세션용으로 생성되거나 룩업될 때 브로커는 토픽 이름의 해시를 취해(예: hash("my-topic") = 0x0000000F) 그 해시가 어떤 번들에 속하는지 확인해 토픽을 특정 번들에 매핑해요.

여기서 "토픽"은 비파티션 토픽 또는 파티션 토픽의 한 파티션 중 하나를 뜻해요. 파티션 토픽의 경우 Pulsar는 내부적으로 파티션을 별개의 토픽으로 취급하므로 서로 다른 파티션을 서로 다른 번들·브로커에 할당할 수 있어요.

번들 할당 (Bundle assignment)

번들 할당은 변화하는 조건에 따라 번들을 브로커에 동적으로 할당하는 것을 말해요.

예를 들어 브로커 리소스 사용량(예: CPU, 메모리, 네트워크 IO)과 번들 부하(예: 처리량)에 기반해 번들을 특정 브로커에 동적으로 할당해요. 각 번들은 서로 독립적이므로 서로 다른 브로커에 독립적으로 할당돼요. 각 브로커는 번들(즉 네임스페이스의 토픽 하위 집합)의 소유권을 가져요.

번들 할당은 Pulsar 클러스터에서 효율적인 부하 분산과 확장성을 이루는 데 핵심 역할을 해요. 번들 할당의 목적은 균형 잡힌 리소스 활용을 보장하고 Pulsar 아키텍처 내에서 동적 확장을 촉진하는 것이에요.

워크플로 (Workflow)

동적 번들 할당의 워크플로는 다음과 같아요.

1단계: 번들을 브로커에 동적으로 할당 (assign bundles to brokers dynamically)

클라이언트가 아직 어떤 브로커에도 할당되지 않은 새 토픽(번들)을 사용하기 시작하면, 부하 조건에 따라 이 번들의 소유권을 획득할 가장 적합한 브로커를 고르는 프로세스가 트리거돼요.

2단계: 번들을 다른 브로커에 재할당 (reassign bundles to other brokers, 선택)

번들을 소유한 브로커가 충돌하면 해당 번들(토픽)이 다른 사용 가능한 브로커에 재할당돼요.

주어진 토픽의 현재 번들-브로커 소유권을 발견하기 위해 Pulsar는 클라이언트를 소유 브로커의 URL로 리다이렉트하는 서버 측 발견 메커니즘을 사용해요. 이 발견 로직에는 다음이 필요해요.

  • 주어진 네임스페이스의 번들 키 범위 — 토픽을 번들에 매핑하기 위함.
  • 번들-브로커 소유권 매핑 — 클라이언트를 현재 소유자로 안내하거나, 브로커가 할당되지 않았으면 새 소유권 획득을 트리거하기 위함.
  • 모든 번들 범위와 브로커-번들 소유권 매핑은 메타데이터 공간에 저장되고, 클라이언트가 소유 브로커를 발견하려 하면 브로커가 이를 조회해요. 성능을 위해 이 데이터는 브로커 인메모리 계층에도 캐시돼요.

번들 분할 (Bundle splitting)

번들 분할은 과부하된 번들을 식별하고 분할하는 과정을 말해요. 이는 핫스팟을 줄이고, 더 세분화된 로드 밸런싱을 달성하고, 리소스 활용을 개선하며, Pulsar 클러스터 내에서 더 세밀한 수평 확장을 가능하게 해요.

번들 분할 과정은 원래 번들을 더 작은 번들로 분해하는 것을 포함하며, 각 번들은 원래 파티션의 하위 집합을 담아요. 이로써 메시지와 처리 부하를 클러스터의 브로커 전체에 더 잘 분산할 수 있어요.

번들을 다음 방식으로 분할할 수 있어요.

  • 자동(Automatic): 네임스페이스의 워크로드가 크게 증가하거나 파티션 수가 단일 번들의 최적 용량을 초과할 때 Pulsar의 자동 번들 분할 프로세스를 활성화해요.
  • 수동(Manual): 기존 번들을 여러 더 작은 번들로 나누기 위해 번들 분할을 수동으로 트리거해요.
번들 분할 방법 정의 사용 시기
자동 (Automatic) 다양한 번들 분할 알고리즘에 따라 번들이 자동으로 분할돼요. 자동 번들 분할이 가장 흔히 사용돼요. 번들이 오랫동안 핫 상태인 경우 등 다양한 시나리오에서 사용할 수 있어요.
수동 (Manual) 지정된 위치에 따라 번들이 수동으로 분할돼요. 수동 번들 분할은 자동 번들 분할의 보조 접근 방식이에요. 자동 번들 분할이 활성화돼 있는데도 오랫동안 핫 상태인 번들이 있을 때, 또는 브로커가 과부하되기 전에 번들을 분할하고 트래픽을 고르게 재분산하고 싶을 때 사용할 수 있어요.

워크플로 (Workflow)

번들을 자동 또는 수동으로 분할하는 워크플로는 다음과 같아요.

  • 자동 번들 분할
  • 수동 번들 분할

[자동]

1단계: 대상 번들 찾기 (find target bundles)

자동 번들 분할이 활성화되어 있으면,

  • modular 로드 밸런서의 경우 리더 브로커가 어떤 번들의 부하가 임계값을 넘는지 확인해요.
  • extensible 로드 밸런서의 경우 로드 매니저가 각 소유 브로커의 번들 부하를 확인해요.

번들 분할 임계값은 다양한 조건에 따라 설정할 수 있어요. 임계값 중 하나라도 초과하는 기존 번들은 분할 후보가 돼요. 로드 밸런서는 새로 분할된 번들을 다른 브로커에 할당해 트래픽 분산을 돕습니다.

번들 분할을 활성화하고 분할 임계값을 자동으로 설정하는 방법은 TBD(문서 작성 중, 계속 지켜봐 주세요!)를 참고해요.

2단계: 번들 분할 경계 계산 (compute bundle splitting boundaries)

이제 분할이 필요한 대상 번들이 찾아졌어요. 분할 전에 소유 브로커는 번들 분할 알고리즘에 따라 분할 위치를 계산해야 해요.

3단계: 경계로 번들 분할 (split bundles by boundaries)

이제 소유 브로커가 대상 번들 분할을 시작하고 다시 파티셔닝해요.

분할 후 소유 브로커는 메타데이터 공간의 번들 소유권과 범위를 업데이트해요. 새로 분할된 번들은 소유 브로커에서 자동으로 언로드될 수 있어요.

예를 들어 번들 파티션이 [0x0000, 0x8000, 0xFFFF]이고 대상 번들 범위 [0x0000, 0x8000)의 분할 경계가 [0x4000]이라면,

분할 후 번들 파티션은 [0x0000, 0x4000, 0x8000, 0xFFFF]가 돼요.

그리고 분할 후 번들 범위는 [0x0000, 0x4000), [0x4000, 0x8000), [0x8000, 0xFFFF]가 돼요.

[수동]

1단계: 대상 번들 찾기 (find target bundles)

브로커 리소스 사용량(예: 토픽·세션 수, 메시지 비율, 대역폭)에 기반해 분할할 핫 번들을 고를 수 있어요.

2단계: 번들 분할 위치 경계 계산 (compute bundle splitting position boundaries)
  • specified_positions_divide 알고리즘을 사용하려면 분할 경계를 지정해야 해요.
  • specified_positions_divide 외 다른 번들 분할 알고리즘을 사용하려면 해당 알고리즘이 위치를 자동으로 계산해요.
3단계: 2단계의 특정 경계에서 번들을 분할 (split bundles at the specific boundaries)

번들을 수동으로 분할하는 방법은 split-bundle admin command를 참고해요.

예시:

토픽 수를 동일하게 나누는 위치에서 가장 큰 번들을 분할하고, 자식 번들을 즉시 언로드해요.

pulsar-admin namespaces split-bundle -b LARGEST -san topic_count_equally_divide -u my-tenant/my-namespace

이미 분할할 대상 번들을 알고 있다면 --bundle(-b) 플래그로 지정할 수 있어요.

pulsar-admin namespaces split-bundle --bundle 0x00000000_0xffffffff my-tenant/my-namespace

번들 분할 알고리즘 (Bundle splitting algorithms)

번들 분할 위치는 다양한 번들 분할 알고리즘으로 계산할 수 있어요.

아래는 번들 분할 알고리즘의 간단한 요약이에요.

번들 분할 알고리즘 정의 사용 시기 자동·수동 메서드에서 사용 가능? 사용 가능 버전
range_equally_divide 번들을 같은 해시 범위 크기의 두 부분으로 분할해요. 기본 번들 분할 알고리즘이에요. 토픽이 많은 경우에 사용해요. - 자동 - 수동 Pulsar 1.7 이상
topic_count_equally_divide 번들을 같은 수의 토픽을 가진 두 부분으로 분할해요. 토픽 수가 적은 경우에 사용해요. - 자동 - 수동 Pulsar 2.6 이상
specified_positions_divide 지정된 위치로 번들을 여러 부분으로 분할해요. 자동 번들 분할이 꺼져 있거나, 켜져 있어도 번들이 분할되지 않을 때 사용해요. 주의: 이 알고리즘을 쓸 때 조심하세요. 예를 들어 번들이 너무 많은 작은 부분으로 분할되면 그 번들을 해시 키가 맞지 않을 수 있어요. 현재 번들 압축(bundle compaction)은 지원되지 않아요. - 수동 Pulsar 2.11 이상
flow_or_qps_equally_divide 메시지 비율과 처리량에 기반해 번들을 여러 부분으로 분할해요. 트래픽에 비례해 번들을 분할할 때 사용해요. - 자동 - 수동 Pulsar 3.0 이상
range_equally_divide

range_equally_divide는 번들을 같은 해시 범위 크기의 두 부분으로 분할해요.

예를 들어 분할할 대상 번들이 (0x00000000, 0x80000000)이라면 번들 분할 경계는 [0x40000000]이에요.

topic_count_equally_divide

topic_count_equally_divide는 번들을 같은 수의 토픽을 가진 두 부분으로 분할해요.

예를 들어 대상 번들 [0x00000000, 0x80000000)에 토픽이 6개 있다면 번들 분할 경계를 0x50000000으로 설정해 좌우 토픽 수를 같게 할 수 있어요.

hash(topic1) = 0x10000000
hash(topic2) = 0x20000000
hash(topic3) = 0x35000000
hash(topic4) = 0x65000000
hash(topic5) = 0x70000000
hash(topic6) = 0x75000000

즉, 분할할 대상 번들은 [0x00000000, 0x80000000)이고, 번들 분할 경계는 [0x50000000]이에요.

구현 세부 사항은 PR-6241: support evenly distribute topics count when splitting bundles를 참고해요.

specified_positions_divide

specified_positions_divide는 번들을 지정된 위치로 여러 부분으로 분할해요.

예를 들어 같은 번들에 2개의 큰 토픽이 있다고 해요. Topic1은 0x30000000, topic2는 0x35000000에 있고 번들 범위가 [0x00000000, 0x40000000)이라면 번들 분할 경계를 0x33000000으로 설정할 수 있어요.

구현 세부 사항은 PIP-143: support split bundle by specified boundaries를 참고해요.

flow_or_qps_equally_divide

flow_or_qps_equally_divide는 메시지 비율(loadBalancerNamespaceBundleMaxMsgRate로 제어) 또는 메시지 처리량(loadBalancerNamespaceBundleMaxBandwidthMbytes로 제어)에 기반해 번들을 여러 부분으로 분할해요. 분할 위치는 메시지 비율 또는 메시지 처리량 중 하나의 임계값에 도달함으로써 결정돼요.

예를 들어 번들 범위 [0x00000000 to 0x80000000]에 아래처럼 6개의 토픽이 있다고 가정해요.

토픽 이름 해시 코드 메시지 비율 메시지 처리량
t1 0x10000000 100/s 10M/s
t2 0x15000000 200/s 20M/s
t3 0x24000000 300/s 30M/s
t4 0x39000000 400/s 40M/s
t5 0x58000000 500/s 50M/s
t6 0x76000000 600/s 60M/s

분할 위치는 메시지 비율 또는 메시지 처리량 값에 따라 달라져요.

사례 1: 메시지 비율이 먼저 임계값에 도달해 분할 위치가 결정됨

만약 다음을 설정한다면

loadBalancerNamespaceBundleMaxMsgRate = 450/s
loadBalancerNamespaceBundleMaxBandwidthMbytes = 200M/s

왜냐하면

100/s + 200/s < 450/s
100/s + 200/s + 300/s > 450/s

그래서 분할 경계는 t2와 t3 사이야. 즉:

splitStartPosition = 0x15000000
splitEndPosition = 0x24000000
splitPosition = (0x15000000 + 0x24000000) / 2 = 0x19500000

참고로 번들 분할은 아래처럼 계속돼요.

------ 2nd bundle splitting ------

왜냐하면

300/s < 450/s
300/s + 400/s > 450/s

그래서 분할 경계는 t3와 t4 사이야. 즉

splitStartPosition = 0x24000000
splitEndPosition = 0x39000000
splitPosition = 31500000 = (0x24000000 + 0x39000000) / 2
------ 3rd bundle splitting ------

왜냐하면

400/s < 450/s
400/s + 500/s > 450/s

그래서 분할 경계는 t4와 t5 사이야. 즉

splitStartPosition = 0x39000000
splitEndPosition = 0x58000000
splitPosition = 48500000 = (0x39000000 + 0x58000000) / 2
------ 4th bundle splitting ------

왜냐하면

500/s > 450/s
600/s > 450/s

그래서 분할 경계는 t5와 t6 사이야. 즉

splitStartPosition = 0x58000000
splitEndPosition = 0x76000000
splitPosition = 67000000 = (0x58000000 + 0x76000000) / 2
사례 2: 메시지 처리량이 먼저 임계값에 도달해 분할 위치가 결정됨

만약 다음을 설정한다면

loadBalancerNamespaceBundleMaxMsgRate = 1900/s
loadBalancerNamespaceBundleMaxBandwidthMbytes = 90M/s

왜냐하면

10M/s + 20M/s + 30M/s < 90M/s
10M/s + 20M/s + 30M/s + 40M/s > 90M/s

그래서 분할 경계는 t3와 t4 사이야. 즉:

splitStartPosition = 0x24000000
splitEndPosition = 0x39000000
splitPosition = (0x24000000 + 0x39000000) / 2 = 0x31500000

참고로 번들 분할은 계속돼요:

------ 2nd bundle splitting ------

왜냐하면

40 + 50 ≤ 90
40 + 50 + 60 > 90

그래서 분할 경계는 t5와 t6 사이야. 즉:

splitStartPosition = 0x58000000
splitEndPosition = 0x76000000
splitPosition = (0x58000000 + 0x76000000) / 2 = 0x67000000
사례 3: 메시지 비율과 메시지 처리량이 동시에 임계값에 도달해 분할 위치가 결정됨

만약 다음을 설정한다면

loadBalancerNamespaceBundleMaxMsgRate = 1100
loadBalancerNamespaceBundleMaxBandwidthMbytes = 110
  • 메시지 비율 측면에서:
    100/s + 200/s + 300/s + 400/s < 1100/s
    100/s + 200/s + 300/s + 400/s + 500/s > 1100/s
    
    그래서 분할 경계는 t4와 t5 사이야. 즉:
    splitStartPosition = 0x39000000
    splitEndPosition = 0x58000000
    splitPosition = (0x39000000 + 0x58000000) / 2 = 0x48500000
    
  • 메시지 처리량 측면에서:
    10M/s + 20M/s + 30M/s + 40M/s < 110M/s
    10M/s + 20M/s + 30M/s + 40M/s + 50M/s > 110M/s
    
    그래서 분할 경계는 t4와 t5 사이야. 즉:
    splitStartPosition = 0x39000000
    splitEndPosition = 0x58000000
    splitPosition = (0x39000000 + 0x58000000) / 2 = 0x48500000
    

요약하면 분할 위치는 0x48500000이에요.

구현 세부 사항은 PIP-169: support split bundle by flow or QPS를 참고해요.

번들 언로드 (Bundle unloading)

번들 언로드(셰딩)는 부하 조건에 따라 번들(토픽)을 닫고, 소유권을 해제하고, 과부하된 브로커에서 덜 부하된 브로커로 번들(토픽)을 재할당하는 과정을 말해요.

번들 언로드는 브로커 전체의 워크로드를 균형 맞추고 클러스터의 리소스 활용을 최적화해요. 예를 들어 Pulsar 클러스터가 변화하는 워크로드나 확장 이벤트(예: 브로커 추가·제거)를 겪을 때 번들 언로드는 파티션이 고르게 분산되고 어떤 브로커도 과부하되지 않도록 보장해요.

번들을 다음 방식으로 언로드할 수 있어요.

  • 자동(Automatic): 브로커가 과부하될 때 Pulsar의 자동 번들 언로드 프로세스를 활성화해요.
  • 수동(Manual): Pulsar 클러스터 내에서 한 브로커에서 다른 브로커로 번들을 언로드하기 위해 번들 언로드를 수동으로 트리거해요.
번들 언로드 방법 정의 사용 시기
자동 (Automatic) 로드 밸런서가 특정 브로커가 과부하됨을 인식하면 과부하된 브로커에서 일부 번들의 트래픽을 강제로 언로드해, 언로드된 번들(토픽)이 할당 과정을 통해 덜 부하된 브로커에 재할당될 수 있게 해요. 시간에 따라 변동하는 트래픽이 있을 때 사용해요.
수동 (Manual) 언제든 수동으로 번들 언로드를 트리거할 수 있어요. 수동 번들 언로드는 자동 번들 언로드의 보조 접근 방식이에요. 자동 언로드가 (예: 잘못된 구성 때문에) 작동하지 않으면 수동 언로드를 트리거해 부하 불균형 문제를 완화할 수 있어요. 수동 언로드 작업을 피하려면 클러스터의 트래픽에 맞게 로드 밸런스 구성을 올바르게 튜닝해야 해요.

워크플로 (Workflow)

번들을 자동 또는 수동으로 언로드하는 워크플로는 다음과 같아요.

  • 자동 번들 언로드
  • 수동 번들 언로드

[자동]

1단계: 대상 브로커 찾기 (find target brokers)

모든 브로커에서 수집한 브로커 부하 정보로 리더 브로커는 번들 언로드 전략에 따라 각 브로커의 리소스 사용량을 계산해요.

2단계: 번들 언로드 (unload bundles)

리더 브로커가 브로커가 과부하된 것을 발견하면 과부하된 번들을 계산하고, 과부하된 브로커에게 토픽의 일부 번들을 언로드하도록 요청하며, 대상 번들의 소유권을 제거하고 토픽 세션과 클라이언트 연결을 닫아요.

3단계: 새 소유자 할당 (assign new owners)

언로드된 번들은 덜 부하된 브로커에 할당되고, 클라이언트는 그곳에 연결해요.

  • modular 로드 밸런서의 경우 클라이언트가 룩업 요청을 보낼 때 번들이 사용 가능한 브로커에 사후 할당(post-assigned)돼요.
  • extensible 로드 밸런서의 경우 언로드 시 번들이 사용 가능한 브로커에 사전 할당(pre-assigned)돼요.

언로드가 발생하면 클라이언트는 토픽이 재할당되는 동안 약간의 지연 블립을 경험해요.

[수동]

1단계: 대상 번들 찾기 (find target bundles)

브로커 리소스 사용량(예: CPU, 네트워크, 메모리 사용량)에 기반해 언로드할 핫 번들을 고를 수 있어요.

2단계: 핫 번들 언로드 (unload hot bundles)

핫 번들을 사용 가능한 브로커로 언로드해요. 대상 번들의 소유권이 전송되고 토픽 연결이 닫혀요.

번들을 수동으로 언로드하는 방법은 unload admin command를 참고해요.

예시:

특정 번들 언로드(향후 토픽 룩업은 번들을 새 소유 브로커에 할당해요)

pulsar-admin namespaces unload my-tenant/my-namespace -b 0x00000000_0xffffffff

특정 번들을 대상 브로커로 언로드

pulsar-admin namespaces unload my-tenant/my-namespace -b 0x00000000_0xffffffff -d broker-1

네임스페이스의 모든 번들 언로드

pulsar-admin namespaces unload my-tenant/my-namespace

번들 언로드 전략 (Bundle unloading strategies)

필요에 따라 다양한 번들 언로드 전략을 선택할 수 있어요.

아래는 번들 언로드 전략의 빠른 요약이에요. 이 전략들은 번들을 자동으로 언로드할 때만 적용돼요.

번들 언로드 전략 정의 사용 시기 사용 가능 버전
OverloadShedder 브로커의 최대 리소스 사용량이 구성된 임계값을 초과하면 그 브로커의 번들을 언로드해요. 브로커 사용량을 임계값 아래로 설정하고 싶을 때 사용해요. Pulsar 1.18 이상. 이 전략은 modular 로드 밸런서에서만 사용할 수 있어요.
ThresholdShedder 브로커의 평균 사용량이 클러스터 평균 사용량 + 구성된 임계값보다 크면 번들을 언로드해요. 클러스터 평균 사용량에 기반해 모든 브로커에 부하를 고르게 퍼뜨리고 싶을 때 사용해요. Pulsar 2.6 이상. Pulsar 2.10~4.x 및 5.0.0-M1/M2에서 modular 로드 밸런서의 기본 전략이었어요. 이 전략은 modular 로드 밸런서에서만 사용할 수 있어요.
UniformLoadShedder 최소·최대 부하에 기반해 모든 브로커에 부하를 균일하게 분산해요. 최소와 최대 부하를 가진 브로커를 비교하고 싶을 때 사용해요. Pulsar 2.10.0 이상. 이 전략은 modular 로드 밸런서에서만 사용할 수 있어요.
TransferShedder 브로커 부하 분포의 표준 편차가 구성된 임계값 아래로 내려올 때까지 최고 부하 브로커에서 최저 부하 브로커로 번들을 언로드해요. extensible 로드 밸런서의 기본 전략이에요. 언로드 시 대상 브로커를 사전 할당해요. Pulsar 3.0 이상. 이 전략은 extensible 로드 밸런서에서만 사용할 수 있어요.
AvgShedder 브로커 리소스 사용량 범위를 구성된 임계값 내로 유지하도록 번들을 언로드하고, 언로드된 각 번들의 대상을 사전 계획해요. Pulsar 5.0.0부터(5.0.0-M1/M2 마일스톤 이후) modular 로드 밸런서의 기본 전략이에요. 훌륭한 서비스 안정성과 부하 전환 정확도를 달성하고 싶을 때 사용해요. Pulsar 3.0.6, 3.2.4, 3.3.1 이상. 2.9보다 크고 3.0 미만 버전을 실행 중이라면 리포지토리에 쉽게 체리픽할 수 있어요: https://github.com/apache/pulsar/pull/22946. 이 전략은 modular 로드 밸런서에서만 사용할 수 있어요.
OverloadShedder

OverloadShedder 전략은 브로커의 최대 리소스 사용량이 구성된 임계값(loadBalancerBrokerOverloadedThresholdPercentage)을 초과하면 그 브로커의 번들을 셰딩해요.

브로커가 과부하되고 최소 두 개의 번들이 할당되어 있다면, 동시에 그 브로커에 아직 언로드되지 않은 번들이 하나 이상 있다면 그 번들(들)이 언로드돼요. 메시지 처리량이 더 높은 번들이 더 낮은 처리량의 번들보다 먼저 언로드돼요.

브로커가 언제 과부하됐는지의 판단은 CPU, 네트워크, 메모리 사용량의 임계값에 기반해요. 이 메트릭 중 하나라도 임계값(기본값은 85%)에 도달하면 시스템이 번들 언로드를 트리거해요.

ThresholdShedder

ThresholdShedder 전략은 브로커의 평균 사용량이 클러스터 평균 사용량 + 구성된 임계값보다 크면 번들을 셰딩해요.

워크플로 (Workflow)
  • ThresholdShedder는 먼저 전체 클러스터에 대한 브로커들의 평균 리소스 사용량(즉 클러스터 평균 사용량)을 계산해요.

    각 브로커의 리소스 사용량은 LocalBrokerData#getMaxResourceUsageWithWeight 메서드로 계산해요.

    과거 관측치는 브로커의 loadBalancerHistoryResourcePercentage 설정에 기반해 이동 평균에 포함돼요.

  • ThresholdShedder는 각 브로커의 평균 리소스 사용량(현재·과거 리소스 사용량 기반)을 클러스터 평균 사용량과 비교해요: a. 브로커의 리소스 사용량이 클러스터 평균 사용량 + 구성된 임계값보다 크면 ThresholdShedder는 언로드된 브로커를 클러스터 평균 사용량보다 5% 아래로 끌어내릴 만큼의 번들 제거를 제안해요. 최근에 언로드된 번들은 다시 언로드되지 않는다는 점을 유의해요. b. 브로커의 리소스 사용량이 클러스터 평균 사용량보다 작거나, 클러스터 평균 사용량 + 구성된 임계값보다 작으면 어떤 번들도 언로드되지 않아요.

예를 들어 브로커 3개가 있고 각 브로커의 평균 사용량이 아래와 같다고 가정해요.

브로커 이름 브로커의 평균 사용량
broker1 40%
broker2 10%
broker3 10%

그래서 클러스터 평균 사용량은 20% = (40% + 10% + 10%) / 3이에요.

임계값(loadBalancerBrokerThresholdShedderPercentage)을 10%로 설정했다면, broker1의 리소스 사용량(40%)이 클러스터 평균 사용량(20%) + 구성된 임계값(10%)의 합(30%)보다 크므로 broker1의 일부 번들만 언로드돼요.

UniformLoadShedder

UniformLoadShedder 전략은 모든 브로커에 부하를 균일하게 분산해요. 가장 높은 부하 브로커와 가장 낮은 부하 브로커 사이의 부하 차이를 확인해요. 그 차이가 구성된 임계값, 즉 메시지 비율(loadBalancerMsgRateDifferenceShedderThreshold로 제어) 또는 처리량(loadBalancerMsgThroughputMultiplierDifferenceShedderThreshold로 제어)보다 높으면, 모든 브로커에 트래픽을 고르게 분산하도록 언로드할 수 있는 번들을 찾아요.

구현 세부 사항은 PR-12902: add uniform load shedder strategy to distribute traffic uniformly across brokers를 참고해요.

TransferShedder

TransferShedder 전략은 다음이 모두 참이 될 때까지 최고 부하 브로커에서 최저 부하 브로커로 번들을 언로드해요.

  • 브로커 부하 분포의 표준 편차가 구성된 임계값(loadBalancerBrokerLoadTargetStd, 기본값 0.25) 아래로 내려감.

  • 유의미하게 저부하된 브로커가 없음.

    어떤 브로커도 0 트래픽을 받지 않아요.

    어떤 브로커의 부하도 avgLoad * min(0.5, loadBalancerBrokerLoadTargetStd / 2)보다 작지 않아요.

  • 유의미하게 과부하된 브로커가 없음.

    어떤 브로커의 부하도 loadBalancerBrokerOverloadedThresholdPercentage보다 크고 avgLoad + loadBalancerBrokerLoadTargetStd보다 크지 않아요.

Pulsar는 extensible 로드 밸런서의 번들 전송 프로토콜을 활용하기 위해 TransferShedder를 도입했어요. 이 번들 전송 프로토콜로 번들 소유권은 소스 브로커에서 대상 브로커로 원활하게 전송될 수 있어요. 즉 TransferShedder는 클라이언트 룩업 대신 언로드 시 대상 브로커를 사전 할당해요. 그래서 언로드 후 새 소유자가 이미 할당되었으므로 클라이언트는 할당 과정을 건너뛸 수 있어요.

구현 세부 사항은 PIP-220: TransferShedder를 참고해요.

AvgShedder

AvgShedder는 Pulsar 5.0.0부터(5.0.0-M1/M2 마일스톤 이후) modular 로드 밸런서의 기본 셰딩·배치 전략이에요. 그 마일스톤은 여전히 LeastLongTermMessageRate 배치의 ThresholdShedder와 loadBalancerDistributeBundlesEvenlyEnabled=true를 사용해요. 이전 버전이거나 기본값을 재정의했다면 다음 매개변수를 구성해 사용해요.

loadBalancerLoadSheddingStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedder

loadBalancerLoadPlacementStrategy=org.apache.pulsar.broker.loadbalance.impl.AvgShedder

maxUnloadPercentage = 0.5
  • AvgShedder는 잘못된 셰딩·배치를 피하기 위해 셰딩과 배치 전략을 함께 묶어요. loadBalancerLoadSheddingStrategyloadBalancerLoadPlacementStrategy 구성이 같도록 보장해야 해요. Pulsar 5.0.0부터(5.0.0-M1/M2 마일스톤 이후) loadBalancerLoadSheddingStrategy만 다른 전략 중 하나로 설정한 구성도 계속 동작해요. 그 경우 브로커는 5.0 이전의 기본이었던 LeastLongTermMessageRate 배치 전략을 사용하고 경고를 기록해요.
  • maxUnloadPercentage를 0.5로 설정(Pulsar 5.0.0부터 기본; 5.0.0-M1/M2 및 이전 버전에서는 0.2)하면 AvgShedder가 먼저 가장 높고 낮은 부하 브로커를 골라낸 다음 그 사이에 트래픽을 고르게 분산한다는 뜻이에요.
  • loadBalancerDistributeBundlesEvenlyEnabled는 Pulsar 5.0.0부터(5.0.0-M1/M2 마일스톤 이후) 기본적으로 false예요. 활성화되면 배치가 먼저 후보에서 네임스페이스의 번들을 가장 많이 소유한 브로커를 제거하는데, 이는 AvgShedder가 번들에 계획한 대상을 버릴 수 있어요.

예를 들어 현재 클러스터의 브로커 등급이 20, 30, 52, 70, 80이고, 최고 부하 브로커(점수 80)의 메시지 비율이 1000이며 최저 부하 브로커(점수 20)의 메시지 비율이 500이라고 해요. 번들 언로드를 트리거할지 결정하는 임계값을 예를 들어 40으로 도입해요. 최고와 최저 부하 브로커의 점수 차이가 80-20=60으로 임계값 40보다 크므로 셰딩 전략이 트리거돼요.

최고와 최저 부하 브로커 사이에 트래픽을 고르게 분산하는 목표를 달성하기 위해 셰딩 전략은 두 브로커의 메시지 비율을 같게, 즉 (1000+500)/2=750으로 만들려고 해요. 셰딩 전략은 최고 부하 브로커에서 최저 부하 브로커로 250 메시지 비율을 언로드해요. 셰딩 전략이 완료된 후 두 브로커의 메시지 비율은 750으로 같아져요.

AvgShedder는 multiple hits 알고리즘으로 부하 지터를 처리하는데, 번들 언로드가 최종적으로 트리거되기 전에 임계값이 여러 번 트리거된다는 뜻이에요. 예를 들어 한 쌍의 브로커 사이의 차이가 임계값을 세 번 초과하면 로드 밸런싱이 트리거돼요.

클러스터 롤링 재시작이나 확장 상황에서는 종종 서로 다른 브로커 사이에 상당한 부하 차이가 있고, 우리는 로드 밸런싱을 더 빨리 완료하기를 원해요.

그래서 우리는 두 개의 임계값을 도입해요.

  • loadBalancerAvgShedderLowThreshold, 기본값 15
  • loadBalancerAvgShedderHighThreshold, 기본값 40

두 임계값은 두 개의 연속 히트 횟수 요구 사항에 대응해요.

  • loadBalancerAvgShedderHitCountLowThreshold, 기본값 8
  • loadBalancerAvgShedderHitCountHighThreshold, 기본값 2

한 쌍의 브로커 사이의 점수 차이가 loadBalancerAvgShedderLowThresholdloadBalancerAvgShedderHitCountLowThreshold 횟수만큼 초과하거나, loadBalancerAvgShedderHighThresholdloadBalancerAvgShedderHitCountHighThreshold 횟수만큼 초과하면 번들 언로드가 트리거돼요. 예를 들어 기본값으로 점수 차이가 15를 초과하면 연속 8번 트리거되어야 하고, 점수 차이가 40을 초과하면 연속 2번 트리거되어야 해요.

브로커 사이의 부하 차이가 클수록 번들 언로드를 트리거하는 데 걸리는 횟수가 적어지므로 클러스터 롤링 재시작이나 확장 같은 시나리오에 적응할 수 있어요.

구현 세부 사항은 PIP-364: Introduce a new load balance algorithm AvgShedder를 참고해요.

  • 포괄적 이해와 핵심 통찰은 Broker load balancing | Overview.
  • 다양한 사용 시나리오는 Broker load balancing | Use cases.
  • 기능 탐구는 Broker load balancing | Features.
  • 장점 이해는 Broker load balancing | Benefits.
  • 다양한 로드 밸런서 버전 검토는 Broker load balancing | Types.
  • 빠른 시작은 Broker load balancing | Quick start.
  • 한 브로커 로드 밸런서 유형에서 다른 유형으로의 마이그레이션은 Broker load balancing | Migration.

더 알아보기 (Learn more)

  • 로드 밸런싱 전반은 Overview·Features·Benefits 문서를 참고해요.
  • 로드 밸런서 유형과 마이그레이션은 Types·Migration 문서를 참고해요.
  • 실제 설정 실습은 Quick start 문서를 참고해요.
  • 네임스페이스 번들 구성은 Namespace bundles 문서를 참고해요.