지난 글에서는 Broker가 뭔지, client가 어떤 흐름과 packet형식들을 통해서 구독하고, 메시지를 받고 하는지 알아보았다.
이 글에서는 토픽 계층 구조, 와일드카드 사용법, 그리고 토픽 명명에 대한 모범 사례를 알아볼 것이다.
그리고 적절한 토픽 설계가 MQTT 시스템의 확장성, 필터링 기능, 그리고 유지 관리성을 어떻게 향상시키는지 알아볼 것이다.
(MQTT Topics, Wildcards, & Best Practices – MQTT Essentials: Part 5)
이것만으로는 아쉬우니 QoS 관련 지식도 알아보려고 한다.
(What is MQTT Quality of Service (QoS) 0,1, & 2? – MQTT Essentials: Part 6)
목표
1. Topics, Wildcard와 사용법에 대해 이해한다.
2. QoS에 대해 이해한다.
1. What Are MQTT Topics and Their Role in MQTT Message Filtering?
Topic에 대해서는 이전 글에서부터 계~속 들어왔다. 토픽(topic)은 메시지가 게시되고 구독되는 문자열이다.
topic(토픽) 은 계층적이고 파일 경로처럼 슬래쉬(/)로 여러 레벨을 포함할 수 있다. (예 : myhome/kitchen/smartdishwasher)
여기서 "/" 를 topic 에서 토픽 레벨 구분자(topic level separator) 라고 한다.
메시지 큐와 비교했을 때 MQTT 토픽은 매우 가볍다. 클라이언트는 원하는 토픽을 미리 생성하지 않고도 메시지를 발행하거나 구독할 수 있으며 브로커는 사전 초기화 없이 모든 유효한 토픽을 수락한다.
MQTT Topic Names vs. Topic Filters
발행자(publisher)는 메시지를 보내기 위해 topic name을 사용하고, 구독자(subscriber)는 어느 토픽으로부터 메시지를 받을지 구체화하는 topic filter를 사용한다.
topic filter는 또한 다양한 토픽을 매치시키기 위해 wildcards(예: "+" 혹은 "#") 를 포함할 수 있다.
2. Examples of MQTT Topics
말 그대로 예시다. 우리가 어떻게 topic을 구성해서 사용하면 될 지 안내한다.
myhome/groundfloor/livingroom/temperature:
1층에 위치한 거실의 온도를 나타내는 토픽
USA/California/San Francisco/Silicon Valley:
미국 캘리포니아주 샌프란시스코 실리콘 밸리 지역의 데이터나 이벤트를 추적하고 교환하기 위한 계층형 토픽
5ff4a2ce-e485-40f4-826c-b1a5d81be9b6/status:
고유 식별자(UUID)를 통해 특정 기기나 시스템의 상태를 모니터링하는 데 사용되는 토픽
Germany/Bavaria/car/2382340923453/latitude:
독일 바이에른 지역에 있는 특정 차량의 위도 좌표를 공유하는 데 활용되는 토픽 구조
적절하게 토픽을 정의하고 구조화하는것은 효율적은 데이터 교환과 mqtt 기반 시스템을 다루는데 중요하다.
와일드카드에는 single-level과 multi-level이 있다.
single level wildcard(+)는 하나의 레벨에서만, multi level wildcard(#)는 여러 레벨에서 사용된다.
3. Why and When to Use MQTT Topics Beginning with $
MQTT에서 $로 시작하는 경우는 좀 다른데, multi-level wildcard(#)를 사용 할 때 이것들은 구독하지 않는다.
$로 시작하는 Topic들의 경우 내부 static 용으로 예약된다.
그래서, topic이 $로 시작하는 메시지를 발행하는건 못하니 이를 숙지하자.
$로 시작하는 toipc의 경우 몇 가지 예시가 있다.
- $SYS/broker/clients/connected: 현재 MQTT 브로커에 연결되어 있는 클라이언트 수를 나타냅니다.
- $SYS/broker/clients/disconnected: MQTT 브로커에서 연결이 해제된 클라이언트 수를 보여줍니다.
- $SYS/broker/clients/total: MQTT 브로커와 상호작용한 적이 있는 전체 클라이언트(현재 연결됨 및 연결 해제됨 모두 포함) 누적 수를 의미합니다.
- $SYS/broker/messages/sent: MQTT 브로커가 송신한 총 메시지 수를 나타냅니다.
- $SYS/broker/uptime: MQTT 브로커가 시작된 후 현재까지 가동된 시간(Uptime)을 보여줍니다.
4. MQTT Topics Best Practices
최소 한 글자 이상:
토픽은 최소 한 글자 이상의 문자를 포함해야 합니다.
대소문자 구분:
토픽은 대소문자를 엄격하게 구분하므로 작성 시 주의해야 합니다.
예시: myhome/temperature와 MyHome/Temperature는 서로 다른 토픽으로 간주됩니다.
맨 앞 슬래시(/) 생략:
불필요한 0바이트 빈 문자열 레벨 생성을 막기 위해 토픽 시작 부분에 슬래시를 사용하지 마세요.
예시: /myhome/groundfloor/livingroom처럼 앞에 슬래시가 붙는 형태는 비권장합니다.
공백 사용 금지:
가독성 저하와 디버깅 어려움을 방지하기 위해 토픽 이름에 띄어쓰기를 절대 포함하지 마세요.
짧고 간결하게:
네트워크 트래픽과 리소스 절약을 위해 토픽 길이는 최대한 짧게 유지하세요.
ASCII 문자만 사용:
문자 인코딩 문제나 식별 오류를 방지하기 위해 출력 가능한 표준 ASCII 문자만 사용하세요.
클라이언트 ID 포함:
메시지 출처 파악 및 권한 제어를 위해 토픽에 고유 식별자나 클라이언트 ID를 포함하세요.
예시: client1 ID를 가진 클라이언트는 client1/status에는 발행할 수 있지만 client2/status에는 발행할 수 없도록 권한을 제어합니다.
전체 와일드카드(#) 구독 지양:
클라이언트 과부하 방지를 위해 전체 메시지 구독은 피하고, 브로커 수준의 확장 기능을 활용하세요.
예시: 모든 메시지를 DB에 저장할 때 클라이언트에서 와일드카드로 구독하는 대신, HiveMQ 등의 확장 기능을 사용해 비동기로 DB에 저장하도록 처리합니다.
확장성을 고려한 설계:
향후 기기나 데이터가 추가되더라도 전체 계층 구조를 변경하지 않도록 유연하게 설계하세요.
예시: 스마트홈 솔루션에 새로운 센서가 추가될 때, 전체 구조 변경 없이 기존 토픽 트리에 자연스럽게 추가할 수 있어야 합니다.
구체적인 토픽 사용:
단일 포괄적 토픽 대신 세분화된 개별 토픽을 사용하여 명확성을 높이고 Retained 메시지 기능을 활용하세요.
예시: 거실 센서를 myhome/livingroom 하나로 묶지 말고, myhome/livingroom/temperature, myhome/livingroom/brightness 등으로 구체적으로 나눕니다.
문서화:
원활한 협업과 온보딩을 위해 토픽의 목적, 페이로드 구조 및 명명 규칙을 상세히 문서화하세요.
지속적인 개선:
시스템 요구사항과 환경 변화에 맞춰 토픽 구조를 정기적으로 리뷰하고 최적화하세요.
보안 고려:
토픽 이름 자체에 민감한 정보가 노출되지 않도록 주의하고, 적절한 접근 제어 및 인증을 적용하세요.
이것을 읽으면서 필자가 궁금했던 부분은 topic은 string인데 이걸 어떻게 topic으로 다 관리한다는 거지? 했다.
이에 대해 Gemini에게 물어봤고 궁금하면 다음을 열어보면 된다.
키메라:
Topic 은 단순 String인데 이게 어떻게 이런 best practices들을 어떻게 해결한다는 말이야?
Gemini:
맞습니다. 데이터 타입 관점에서 보면 MQTT 토픽은 단순한 **UTF-8 문자열(String)**에 불과합니다.
그런데 이 단순한 문자열 하나로 어떻게 계층 구조, 권한 제어, 와일드카드 매칭 같은 복잡하고 세부적인 처리가 가능할까요? 그 핵심은 토픽 자체에 특별한 기능이 있는 것이 아니라, 이 문자열을 수신하여 파싱하고 처리하는 **MQTT 브로커(Broker)의 내부 라우팅 아키텍처**와 **구분자(`/`)의 활용**에 있습니다.
REST API의 URI 경로(`GET /api/v1/users/123`)를 떠올려 보시면 원리가 같습니다. URI 역시 단순한 문자열이지만, 서버(예: Spring의 DispatcherServlet)가 이를 `/` 기준으로 파싱하여 계층적인 자원으로 인식하고 적절한 컨트롤러로 라우팅합니다. MQTT 역시 이와 동일한 논리적 구조를 사용합니다.
브로커 내부에서 이 문자열이 어떻게 세부적인 제어로 이어지는지 원리를 나누어 설명해 드리겠습니다.
### 1. 파싱을 통한 트리(Tree) 기반 라우팅 맵 구축
MQTT 브로커는 클라이언트로부터 구독(Subscribe) 요청을 받으면, 토픽 문자열을 단순한 하나의 텍스트로 저장하지 않습니다. 슬래시(`/`)를 기준으로 문자열을 쪼개어(Tokenize), 내부 메모리에 **트리(Tree) 또는 트라이(Trie)** 형태의 라우팅 테이블을 구축합니다.
* `myhome/livingroom/temperature`라는 문자열은 브로커 내부에서 루트(myhome) -> 자식 노드(livingroom) -> 손자 노드(temperature)로 이어지는 계층적 노드로 변환됩니다.
* 이 때문에 "구체적인 토픽을 사용"하고 "앞에 슬래시를 빼라"는 규칙이 존재하는 것입니다. 문자열을 트리 구조로 파싱할 때 불필요한 공백 노드가 생기거나, 하나의 노드에 트래픽이 병목되는 현상을 막기 위함입니다.
### 2. 와일드카드(`+`, `#`) 패턴 매칭 알고리즘
토픽 문자열이 트리 구조로 인덱싱되어 있기 때문에, 고성능 패턴 매칭이 가능해집니다.
클라이언트가 `myhome/+/temperature`라는 와일드카드로 구독을 요청하면, 브로커는 수신된 메시지를 트리에 흘려보낼 때 두 번째 레벨의 문자열 값은 검사하지 않고 무조건 통과시킵니다. 즉, 단순 문자열 비교(equals)가 아니라 구조적인 그래프 탐색 알고리즘을 통해 메시지를 복제하고 라우팅합니다.
### 3. 브로커 레벨의 권한 인가 (ACL, Access Control List)
"클라이언트 ID를 포함해 권한을 제어한다"는 것은 토픽 자체의 기능이 아니라 브로커의 보안 메커니즘을 활용하는 것입니다.
클라이언트가 연결될 때 브로커는 해당 세션의 식별자(Client ID)를 기억합니다. 이후 클라이언트가 특정 토픽 문자열에 메시지를 발행(Publish)하려 할 때, 브로커의 인터셉터가 이를 가로챕니다. 그리고 요청한 문자열이 허용된 규칙(예: `startsWith("client1/")`)에 부합하는지 검사하여, 일치하지 않으면 연결을 끊거나 메시지를 드롭(Drop)시킵니다.
### 요약
결국 토픽 안에 복잡한 메타데이터가 숨어있는 것이 아닙니다. **브로커가 문자열을 쪼개고 라우팅하는 규칙**을 철저히 분석하여, 단순한 문자열이 **브로커 메모리 상에서 가장 효율적이고 안전한 자료구조로 매핑될 수 있도록** 구조를 설계하는 과정입니다. 앞서 말씀드린 Best Practice들은 바로 이 '문자열의 정규화 및 아키텍처적 약속(Convention)'을 의미합니다.
5. MQTT Topic Design Patterns for Industrial Applications
해당 섹션을 읽으면 뭐... 그냥 구조적으로 잘 짜야지 추후에 문제가 없다는 것을 강조하고 싶었나보다.
best practice에 이어서 실제 어떻게 하고 있는지 예시를 가져온건데, 분류를 잘 해서 topic으로 써야함을 강조한다.
wildcard를 남발하지 않고 세분화 하는 것이다.
6. QoS Key Takeways
QoS 0 (At most once):
전송 여부를 확인하지 않는 Fire-and-Forget 방식으로, 데이터 유실을 감수할 수 있는 비임계(Non-critical) 데이터나 매우 안정적인 네트워크에 적합합니다.
QoS 1 (At least once):
수신 측의 ACK를 통해 메시지 전달을 보장하지만, 네트워크 지연으로 ACK가 유실될 경우 재전송으로 인한 메시지 중복 수신이 발생할 수 있는 IoT 환경의 표준 레벨입니다.
QoS 2 (Exactly once):
4-Way Handshake 과정을 거쳐 메시지 유실과 중복을 모두 방지하고 정확히 한 번만 처리되도록 보장하는 가장 높은 수준의 신뢰성 모델입니다.
QoS 협상 (Negotiation):
최종 메시지 전달의 QoS 수준은 발행자(Publisher)가 부여한 QoS 레벨과 구독자(Subscriber)가 요청한 QoS 레벨 중 더 낮은(Downgraded) 값으로 결정됩니다.
Trade-off (효율성 vs 신뢰성):
QoS 레벨이 높아질수록 메시지 전송의 신뢰성은 강력하게 보장되지만, 패킷 교환 횟수(Handshake)가 늘어나 네트워크 대역폭(Overhead)과 지연 시간(Latency)이 필연적으로 증가합니다.
QoS 설명 표
| QoS Level | Guarantee | Handshake Steps | Use Case |
|
QoS 0
|
At most once
|
1 (Fire and forget)
|
Frequent, non-critical data (e.g., telemetry)
|
|
QoS 1
|
At least once
|
2 (PUBACK)
|
Critical data where duplicates are acceptable
|
|
QoS 2
|
Exactly once
|
4 (PUBLISH, PUBREC, PUBREL, PUBCOMP).
|
Mission-critical commands (e.g., alarms)
|

QoS 0는 단순하다. 쏘고 잊는다!(fire and forget!)

QoS 1은 적어도 한번 메시지를 보내고 응답받는다.
이를 위해 보내는 client는 PUBACK 응답을 받을 때 까지 기억한다. 만약 PUBACK을 못받으면 다시 보내는것이다.

특히 필자는 QoS 2 관련 흐름을 봐야 한다고 생각했다.
(1)수신자는 송신자로부터 QoS 2 PUBLISH 패킷을 수신하면 해당 게시 메시지를 적절히 처리하고 PUBLISH 패킷 수신을 확인하는 PUBREC 패킷으로 송신자에게 응답한다.
(2)송신자가 수신자로부터 PUBREC 패킷을 수신하지 못하면 수신 확인을 받을 때까지 중복(DUP) 플래그를 붙여 PUBLISH 패킷을 다시 전송한다.
(3)송신자가 PUBREC 패킷을 받으면 송신자는 PUBLISH 패킷을 버리고 PUBREC패킷을 저장한 다음 PUBREL 패킷을 반환한다.
(4)수신자는 모든 저장된 상태를 버리고 PUBCOMP 으로 응답한다.
7. Downgrade of QoS
또 재미있는 부분이 하나 있는데, QoS의 다운그레이드 라는게 있다.
송신자와 수신자가 정의하는 QoS 레벨은 서로 다를 수 있다고 한다.
메시지를 브로커로 보내는 클라이언트가 QoS 레벨을 정의하는 반면, 브로커는 수신자가 구독 시 정의한 QoS 레벨을 사용한다.
예를 들어, 송신자가 QoS 2를 사용하고 수신자가 QoS 1로 구독하는 경우, 브로커는 QoS 1로 메시지를 수신자에게 전달합니다. 이로 인해 동일한 메시지가 수신자에게 여러 번 전달될 수 있다.
해당 부분이.. QoS 설정을 하면서 신경써야 할 부분으로 보인다. 나는 분명 QoS 2로 햇는데 왜 이거 QoS1이지? 이런걸 수 있으니... 무조건 구독 QoS 에 따라서 정해진다는 사실을 기억하자.
현재 HiveMQ 공식사이트에 있는 MQTT Essentials 에 대해 읽고 있는데, 5,6 장을 읽어 보았다.
이번 5,6장은 이전에서부터 계속 나오던 부분이라 크게 어렵지 않았고, 열심히 읽는다고 감이 잡힐 부분은 아니라고 판단된다.
topic 설정과 QoS설정은 작업을 하면서 그때그때 문서로 정리하면서 작업을 하는게 실수를 안하는 방법이라 생각한다.
그래서, 많은 부분들을 축약 혹은 생략했으니 궁금하면 글을 직접 읽는걸 추천한다. 생각보다 쉽다!
마지막으로... 필자는 이제 MQTT 문서를 적당히 읽었으니 실제로 프로젝트를 만들어보면서 적용할 예정이다.
모르는 것이 나오면 그 때 그때 공부하는게 빠르다고 생각한다.
출처:
MQTT Topics, Wildcards, & Best Practices – MQTT Essentials: Part 5
What is MQTT Quality of Service (QoS) 0,1, & 2? – MQTT Essentials: Part 6
'CS + 인프라 > Messaging' 카테고리의 다른 글
| [Messaging] MQTT 활용하기 - 1탄 : MQTT Broker선정과 진행상황 설명 및 구현 (0) | 2026.04.22 |
|---|---|
| [Messaging] MQTT 이해하기 - 3탄 : Broker와 Server Connection + Subscribe & Unsubscribe (0) | 2026.04.15 |
| [Messaging] MQTT 이해하기 - 2탄 : Pub/Sub 구조 (0) | 2026.04.14 |
| [Messaging] MQTT 이해하기 - 1탄 : MQTT란 무엇인가? + mqtt introduction (0) | 2026.04.14 |