본문 바로가기
1. 개발/1.8. ActiveMQ

Topic 모델에서 '지속성 구독자(Durable Subscriber)'의 원리는?

by 엉짱 2026. 3. 10.
반응형

토픽(Topic) 모델 기반의 Publish-Subscribe 메시징 패턴에서 '지속성 구독자(Durable Subscriber)'는 구독자가 브로커와의 연결을 끊었을 때도 메시지를 유실하지 않고 보관했다가 재연결 시 전달받을 수 있도록 보장하는 메커니즘입니다. 일반적인 토픽 구독(Non-durable Subscription)은 구독자가 활성화된 상태에서만 메시지를 수신하며, 연결이 끊긴 동안 발행된 메시지는 모두 휘발됩니다. 반면 지속성 구독은 브로커가 구독자의 '상태'를 영구적으로 관리함으로써 메시징의 신뢰성을 보장합니다.

지속성 구독자의 식별 원리

브로커가 특정 구독자를 유일하게 식별하고 그 상태를 유지하기 위해서는 반드시 고유한 식별자 조합이 필요합니다. 일반적으로 JMS(Java Message Service) 표준에서는 '클라이언트 아이디(ClientID)'와 '구독자 이름(Subscription Name)'의 조합을 사용합니다.

  1. 클라이언트 아이디(ClientID): 메시징 시스템 전체에서 해당 연결을 식별하는 고유 값입니다. 브로커는 이 아이디를 통해 현재 접속한 프로세스가 누구인지 확인합니다.
  2. 구독자 이름(Subscription Name): 하나의 클라이언트가 여러 개의 토픽을 구독할 수 있으므로, 각 구독 건에 대해 부여하는 고유 명칭입니다.

브로커는 내부적으로 이 두 정보를 결합하여 일종의 '가상 큐(Virtual Queue)' 또는 '구독 포인터'를 생성합니다. 구독자가 오프라인 상태가 되어도 브로커의 메모리 또는 저장소(Journal/DB)에는 이 식별자 정보가 삭제되지 않고 남게 됩니다.

메시지 보관 및 전달 프로세스

메시지가 토픽으로 발행될 때 브로커 내부에서 일어나는 과정은 다음과 같습니다.

먼저, 발행자(Publisher)가 특정 토픽으로 메시지를 전송합니다. 브로커는 해당 토픽을 구독하고 있는 모든 구독자 목록을 조회합니다. 이때 현재 활성화된 구독자에게는 메시지를 즉시 푸시(Push) 방식으로 전달합니다. 문제는 비활성 상태인 지속성 구독자입니다. 브로커는 지속성 구독자 명단을 확인하고, 현재 연결되어 있지 않더라도 해당 구독자의 전용 저장 영역(또는 메시지 참조 인덱스)에 메시지를 기록합니다.

이때 메시지의 '지속성(Persistence)' 설정이 중요합니다. 메시지 자체가 영구 저장(Persistent) 설정으로 발행되었다면 브로커는 이를 디스크의 저널 파일이나 데이터베이스에 물리적으로 기록합니다. 구독자가 다시 연결되면 브로커는 해당 클라이언트 아이디와 구독자 이름을 대조하여 보관되어 있던 미수신 메시지들을 순차적으로 재전송합니다.

내부 저장소 및 오프셋 관리

지속성 구독의 핵심은 '어디까지 읽었는가'를 관리하는 오프셋(Offset) 또는 시퀀스 번호(Sequence Number) 관리입니다.

브로커는 각 지속성 구독자별로 메시지 커서(Cursor)를 유지합니다. 메시지가 발행될 때마다 전역적인 시퀀스 번호가 증가하며, 각 구독자별 커서는 자신이 마지막으로 확인 응답(Acknowledgement)을 보낸 위치를 가리킵니다. 구독자가 오프라인인 동안 새로운 메시지들이 들어오면 브로커는 해당 구독자의 커서와 최신 메시지 번호 사이의 간극을 파악하고, 그 사이의 메시지 참조 정보를 디스크에 유지합니다.

이 방식은 크게 두 가지 형태로 구현됩니다.

  1. 메시지 복사 방식: 각 구독자마다 메시지의 복사본을 별도로 저장하는 방식입니다. 관리가 명확하지만 구독자가 많아질수록 디스크 사용량이 급격히 증가하는 단점이 있습니다.
  2. 참조 관리 방식(Reference Counting): 메시지 원본은 하나만 저장하고, 각 구독자는 해당 메시지를 가리키는 포인터만 가집니다. 모든 지속성 구독자가 메시지를 수신하고 확인 응답을 보냈을 때 비로소 브로커는 물리적인 메시지 원본을 삭제합니다. 현대적인 메시지 브로커(ActiveMQ Artemis 등)는 대부분 효율성을 위해 이 참조 관리 방식을 채택합니다.

JMS 1.1과 JMS 2.0에서의 진화

전통적인 JMS 1.1에서는 지속성 구독자가 '유일성'에 고착되어 있었습니다. 즉, 특정 클라이언트 아이디와 구독자 이름의 조합은 단 하나의 연결만 허용했습니다. 이는 고가용성(HA) 환경에서 제약으로 작용했습니다. 서버 한 대가 해당 구독자 정보를 선점하고 있으면, 동일한 애플리케이션의 다른 인스턴스는 동일한 이름으로 구독할 수 없었기 때문입니다.

이를 해결하기 위해 JMS 2.0에서는 '공유 지속성 구독(Shared Durable Subscription)' 개념이 도입되었습니다. 이 모델에서는 여러 개의 컨슈머가 동일한 클라이언트 아이디와 구독자 이름을 공유하여 메시지를 분산 처리할 수 있습니다. 즉, 토픽의 '브로드캐스트' 특성과 큐의 '부하 분산' 특성이 결합된 형태입니다. 브로커는 동일한 구독자 그룹 내의 컨슈머들에게 라운드 로빈 등의 방식으로 메시지를 배분하면서도, 그룹 전체가 오프라인이 되었을 때의 메시지 유실을 방지합니다.

라이프사이클 관리 및 주의사항

지속성 구독은 강력한 신뢰성을 제공하지만, 관리되지 않을 경우 시스템 전체의 장애 원인이 될 수 있습니다.

가장 빈번한 문제는 '죽은 구독자'입니다. 애플리케이션이 더 이상 사용되지 않거나 구독 설정이 변경되었음에도 불구하고, 과거에 생성된 지속성 구독 정보가 브로커에 남아 있는 경우입니다. 브로커는 해당 구독자가 언젠가 돌아올 것이라고 믿고 메시지를 계속해서 디스크에 쌓아둡니다. 이는 결국 디스크 풀(Disk Full) 상태를 유발하여 브로커 전체의 가동을 중단시킬 수 있습니다.

이를 방지하기 위해 다음과 같은 관리 전략이 필요합니다.

  1. 명시적 구독 해지(Unsubscribe): 애플리케이션이 더 이상 해당 메시지를 받을 필요가 없을 때는 반드시 unsubscribe 메소드를 호출하여 브로커 내의 구독 정보와 보관된 메시지를 삭제해야 합니다.
  2. 만료 시간(TTL) 설정: 메시지 자체에 유효 기간을 설정하여, 일정 시간 동안 구독자가 가져가지 않은 메시지는 자동으로 삭제되도록 구성합니다.
  3. 오프라인 구독자 정리 정책: 브로커 설정에서 특정 기간 동안 재접속하지 않는 지속성 구독자를 강제로 제거하는 정책을 운영해야 합니다.

큐 모델과의 차이점 및 선택 기준

지속성 토픽 구독과 큐(Queue)는 유사해 보이지만 명확한 차이가 있습니다. 큐는 메시지가 들어오는 시점에 수신자가 없어도 메시지를 보관합니다. 하지만 토픽은 구독자가 생성(Create)되기 전의 메시지는 보관하지 않습니다. 즉, 지속성 구독은 '구독이 한 번이라도 시작된 이후'의 메시지에 대해서만 오프라인 보관을 보장합니다.

만약 시스템 설계 시 한 명의 수신자만 메시지를 받아야 한다면 큐를 사용하는 것이 옳습니다. 하지만 동일한 데이터(예: 상품 가격 업데이트 정보)를 여러 서비스(통계 서비스, 캐시 갱신 서비스, 알림 서비스 등)가 동시에 받아야 하면서도, 각 서비스가 일시적으로 다운되었을 때 데이터 유실이 없어야 한다면 지속성 구독 토픽 모델이 유일한 해답이 됩니다.

성능 영향 요소

지속성 구독을 처리할 때 브로커의 성능에 영향을 주는 요소는 다음과 같습니다.

  1. 디스크 I/O: 비활성 구독자를 위해 메시지를 저널에 쓰는 작업은 메모리 작업보다 수십 배 느립니다. 특히 동기식 쓰기(Sync Write) 설정이 되어 있다면 전체 시스템의 처리량(Throughput)이 급감합니다.
  2. 커서 관리 오버헤드: 수천 명의 지속성 구독자가 존재할 경우 브로커는 각 메시지 발행 시마다 수천 개의 포인터를 업데이트해야 합니다. 이는 CPU 연산 비용을 증가시킵니다.
  3. 확인 응답(ACK) 처리: 구독자가 다시 연결되어 대량의 누적 메시지를 처리할 때 발생하는 확인 응답 트래픽은 네트워크 대역폭을 점유하며, 브로커는 각 ACK를 처리할 때마다 디스크의 인덱스를 갱신해야 합니다.

결론적으로 지속성 구독자는 분산 시스템에서 메시지의 신뢰성 있는 전파를 위한 핵심 기술입니다. 브로커는 클라이언트 아이디와 구독자 이름이라는 논리적 식별자를 통해 물리적인 저장소와 메시지 시퀀스를 매핑하며, 이를 통해 네트워크 불확실성 속에서도 데이터의 정합성을 유지합니다. 개발자는 이 메커니즘이 자원을 점유하는 방식과 라이프사이클을 정확히 이해하고, 시스템 장애로 이어지지 않도록 적절한 운영 정책을 병행해야 합니다.

반응형