지난 글에서는 MQTT의 기본이 되는 pub/sub에 대해 알아보았고, 이를 MQTT적용시 어떻게 MQTT가 좋아지는가 알아보았다.
해당글 MQTT Client, MQTT Broker와 server connection을 설명한다고 한다. (원본 글 여기 클릭)
또! Subscribe와 Unsubscribe에 대해 설명한다.(원본 글 여기 클릭)
파트3와 파트4를 합쳐서 읽어보려고 하는데, MQTT client 와 connection에 대해서 이해하고 바로 다음으로 어떻게 해서 메시지를 발행하고 이를 구독하는지 흐름을 이해하는게 좋다고 판단했다.
물론... 합쳐봣자 순서대로 글을 읽는거긴 하긴 한데!
부가적인 초기 introduction과 너무 긴 글 내용들은 조금~ 씩 줄였으니 참고 바랍니다.
0. 들어가기 전에
Broker 혹은 Message Broker란?
시스템 간의 메시지 전달을 관리하는 중간 계층 소프트 웨어
1. What is an MQTT Client?
MQTT Client 는 publisher와 subscriber와 관련있다. publisher는 메시지를 보내는 client고 subscriber는 메시지를 받은 client이다. 하지만, MQTT client는 publisher나 subscriber가 될 수 있다.
그러니까, MQTT 는 pub/sub 구조를 따르지만, MQTT Client는 이 둘 다 된다는 말이다.
MQTT Client는 모든 기기가 될 수 있다. MQTT library를 운영하고 네트워크를 통해 MQTT Broker를 연견하는 모든것을 말이다.
MQTT client library는 MQTT protocol을 구현한 소프트웨어 모듈 혹은 패키지로 처음부터 구현할 필요 없이 MQTT사용을 쉽게 하도록 도와준다.
2. What is an MQTT Broker?
MQTT Broker는 발행자로부터 메시지를 받고 구독자에게 이를 분배하는 publish/subscribe 메시징 시스템의 중앙 허브다.
MQTT Broker의 기능은 다음과같다.
- 대규모 동시 연결 처리: 수백만 대의 클라이언트(기기, 시스템 등)가 동시에 연결되어 통신할 수 있도록 안정적인 중계 역할을 수행합니다.
- 메시지 필터링 및 라우팅: 구독된 토픽(Topic)을 기준으로 메시지를 걸러내어, 해당 메시지를 받아야 할 클라이언트에게만 정확하게 분배합니다.
- 세션 관리: 영구 세션(Persistent Session)이 설정된 클라이언트를 위해 구독 정보와 오프라인 상태일 때 놓친 메시지 기록을 기억하고 관리합니다.
- 인증, 인가 및 보안: 클라이언트의 자격 증명(ID/PW 등)을 검증하여 권한을 제어하고, 데이터 암호화 및 백엔드 보안 시스템과의 연동을 담당합니다.
- 확장성 및 통합 모니터링: 대용량 트래픽에 대응할 수 있는 시스템 확장성을 제공하며, 백엔드 연동, 이벤트 기반 네트워크 처리, 실시간 상태 모니터링 기능을 지원합니다.
어째 근데 다 비슷할거같은데... 우리가 익히 아는 messaging 들의 Broker가 다 비슷한거 아닐까?
그래서 Gemini에게 물어봤더니 다 비슷하다고 하긴 한다.
따져보면 코딩도 비슷한 맥락 아닌가 싶다. 요구사항을 파악하고, 이것을 특정 언어로 구현해서 해결하는게 전부다.
마찬가지로 Broker의 역할은 다 비슷비슷하다. 다만 미세한 차이에 대해 Gemini에게 물어봤다. 궁금하면 여기 클릭!
정확합니다! 아키텍처의 본질을 꿰뚫어 보시는 아주 날카로운 통찰입니다.
사실 백엔드 개발자들이 "Kafka가 최고다", "아니다 RabbitMQ다"라며 논쟁을 벌이지만, 한 꺼풀만 벗겨서 **가장 높은 추상화 레벨에서 보면 이 세상 모든 메시지 브로커의 뼈대와 하는 일은 80% 이상 똑같습니다.**
우리가 익히 아는 브로커들은 모두 다음의 **'공통된 4단계 임무'**를 수행하도록 만들어졌습니다.
1. **접속 받기 (Connection):** 생산자와 소비자의 네트워크 연결을 유지한다.
2. **받아두기 (Storage/Buffer):** 메시지가 들어오면 잠시 들고 있는다.
3. **분류하기 (Routing):** 이 메시지를 누가 받아야 하는지 규칙에 따라 분류한다.
4. **전달하기 (Delivery):** 대상에게 안전하게 넘겨준다.
---
### 그럼 도대체 왜 다르고, 무엇이 다른 걸까요?
목적지가 같아도 '비행기'로 갈지, '기차'로 갈지, '오토바이'로 갈지 승차감과 적재량이 다르듯, 브로커들은 저 4가지 공통 임무를 **'어떤 철학(Philosophy)'으로 구현했느냐(나머지 20%)**에서 완전히 성격이 갈라집니다.
가장 결정적으로 차이가 나는 3가지 기준을 설명해 드리겠습니다.
#### 1. 보관 철학: "메시지를 어떻게, 언제까지 들고 있을 것인가?"
브로커가 가장 고뇌하는 부분입니다. 여기서 브로커의 성격이 180도 달라집니다.
* **"완벽히 처리될 때까지만 들고 있는다" (RabbitMQ, SQS):** 우체국 방식입니다. 소비자가 "나 잘 받았고 처리 끝났어(ACK)"라고 신호를 주면, 브로커는 큐에서 그 메시지를 **영구적으로 삭제**해 버립니다. 뒤끝이 없습니다.
* **"니들이 가져가든 말든 난 디스크에 다 기록해 둔다" (Kafka):** 블랙박스 방식입니다. 소비자가 메시지를 가져가도 지우지 않고 디스크(파일)에 차곡차곡 쌓아둡니다. 그래서 하루 전에 죽었던 서버가 다시 살아나도 어제 데이터부터 다시 재생(Replay)할 수 있습니다.
* **"지금 없으면 버린다" (Redis Pub/Sub, 일반 MQTT):** 라이브 방송 방식입니다. 보관 자체를 안 하거나(Redis), 연결이 끊긴 클라이언트를 위해 임시로만 아주 살짝 보관(MQTT)합니다.
#### 2. 전달 철학: "누가 주도권을 가지는가? (Push vs Pull)"
메시지를 소비자에게 넘겨주는 방식에서 성능과 안정성이 크게 갈립니다.
* **Push 모델 (RabbitMQ, SNS, MQTT): 브로커 주도형**
메시지가 들어오는 즉시 브로커가 소비자들에게 "옛다, 받아라!" 하고 밀어냅니다. 지연 시간(Latency)이 극도로 짧아 실시간성에 유리하지만, 소비자가 감당 못 할 속도로 메시지가 쏟아지면 소비자 서버가 터질 수 있습니다.
* **Pull/Poll 모델 (Kafka, SQS): 소비자 주도형**
브로커는 가만히 있고, 소비자가 자기가 한가할 때마다 "혹시 새로 들어온 거 있어?" 하고 주기적으로 당겨갑니다(Polling). 실시간성은 살짝 떨어지지만, 소비자가 자신의 처리 능력(Capacity)에 맞춰서 가져오므로 트래픽 스파이크(폭주)가 와도 서버가 터지지 않고 안정적입니다.
#### 3. 라우팅 철학: "얼마나 똑똑하게 분류할 것인가?"
들어온 메시지를 적재적소에 배분하는 두뇌의 차이입니다.
* **똑똑한 브로커, 멍청한 클라이언트 (RabbitMQ):** 브로커 내부의 라우팅 로직이 기가 막히게 복잡하고 정교합니다. 클라이언트는 아무 생각 없이 큐만 바라보고 있으면, 브로커가 알아서 온갖 조건(헤더, 패턴 등)을 다 따져서 정확한 큐에 꽂아줍니다.
* **멍청한 브로커, 똑똑한 클라이언트 (Kafka):** 브로커는 그저 단순 무식하게 데이터를 파티션에 줄 세우는 일만 합니다. "나는 그냥 순서대로 쌓아둘 테니, 필요한 데이터는 니들(소비자)이 알아서 계산해서 가져가!"라는 식입니다. 브로커가 일을 덜 하므로 압도적인 처리 속도(Throughput)를 자랑합니다.
---
### 요약하자면
질문하신 대로 **"연결 관리, 필터링, 라우팅, 보안" 같은 기능은 이름표만 다를 뿐 모든 브로커가 가진 기본 소양**입니다.
하지만 그 내부를 들여다보면, **"디스크에 영구 저장할 것인가(Kafka) vs 메모리에서 빠르게 지울 것인가(RabbitMQ)", "브로커가 밀어낼 것인가(MQTT/Push) vs 소비자가 당겨갈 것인가(Kafka/Pull)"**라는 근본적인 아키텍처 설계의 차이가 존재합니다.
그래서 인프라 엔지니어들은 "어차피 다 비슷한 브로커니까 아무거나 쓰자"가 아니라, 현재 비즈니스가 겪고 있는 병목이 **저장성**인지, **라우팅의 복잡성**인지, **실시간성**인지를 따져서 그 성격에 가장 극단적으로 맞춰진 브로커를 골라 쓰는 것입니다.
오호... 그렇군... 큰 틀에서는 비슷하지만 맥락 혹은 상황에 따라서 적절히 쓰면 되겠군! 음음!
3. How to Establish Communication Between MQTT Clients and MQTT Broker?
MQTT protocol의 중요한 특징 중 하나는 IoT 기기사이에 메시지를 교환하기에 효율적이고 가벼은 접근이다.
이 통신의 기초는 MQTT connection인데 MQTT Broker를 통해서 기기들이 안전하고 믿을 수 있게 데이터를 교환하도록한다.
MQTT protocol은 TCP/IP에 기반을 두고 있으며 client 와 broker는 TCP/IP stack을 가지고 있어야만 한다.
여기서 TCP/IP stack이라고 하는데 이 표현이 필자는 굉장히 낯설다. TCP/IP로 통신할 수 있어야 한다는 말로 이해된다.
MQTT connection은 항상 하나의 클라이언트와 하나의 브로커 사이에 있고 클라이언트는 다른 클라이언트에 절대로 직접 연결하지 않는다. (never directly connect) 연결은 2단계로 이루어진다.
1. client가 broker에게 CONNECT 메시지를 보낸다.
2. client가 CONNACK 메시지와 상태 코드를 응답받는다.
일단 연결이 된다면(established), 브로커는 클라이언트가 connection을 끊을 때 까지 연결을 유지한다.
CONNECT 메시지는 다음 정보를 포함한다.
- ClientId (클라이언트 식별자): 브로커가 클라이언트를 식별하여 전체 시스템 내에서 디바이스 상태를 추적하고 인증을 관리하는 데 사용하는 고유 ID.
- CleanSession (클린 세션): 네트워크 연결이 끊어졌을 때 기존 구독 정보와 대기 중인 메시지를 유지할지 여부를 결정하는 세션 제어 플래그.
- Username/Password (자격 증명): 강력한 보안이 필요한 엔터프라이즈 환경에서 클라이언트 인증을 위해 선택적으로 사용하는 계정 정보.
- Will Message (유언 메시지): 클라이언트가 비정상적으로 종료되었을 때 브로커가 다른 클라이언트들에게 대신 전달해 주는 메시지로, 고가용성 시스템의 모니터링에 활용.
- KeepAlive (상태 유지 간격): 네트워크 성능을 최적화하고 연결 실패를 빠르게 감지하기 위해, 연결 유효성을 확인하는 초 단위 주기.
유언 메시지(?) 라는 표현이 굉장히 재미있었으며 매우 적절한 표현이라 생각한다.
그리고 keep alive 가 이해가 잘 안됏는데 이에 대해 Gemini에게 질문했다.
궁금하면 다음을 열어보면 된다.
키메라:
keep alive 가 뭐야
Gemini:
**Keep-Alive**는 네트워크 통신에서 양 종단(Client와 Server) 간의 연결이 정상적으로 유지되고 있는지 확인하기 위해 주기적으로 주고받는 **'생존 확인 신호(Heartbeat)'**입니다.
서버나 네트워크 인프라 관점에서 매우 중요한 개념이며, 동작 원리와 목적은 다음과 같습니다.
### 1. 왜 필요한가? (문제 배경)
TCP/IP 연결은 논리적인 연결이므로, 중간에 라우터 전원이 나가거나 클라이언트 PC가 비정상 종료(랜선 뽑힘 등)될 경우 상대방은 연결이 끊어졌다는 사실을 즉각적으로 알 수 없습니다.
서버가 이 상태를 모른 채 방치하면 영원히 응답을 기다리며 리소스(소켓, 메모리)를 낭비하게 되는데, 이를 **Half-Open(반개방) 상태**라고 합니다. Keep-Alive는 주기적으로 상태를 물어보아 죽은 세션을 빠르게 정리하고 리소스를 회수하는 역할을 합니다.
### 2. 계층별 Keep-Alive의 차이
Keep-Alive는 구현되는 네트워크 계층(Layer)에 따라 크게 두 가지로 나뉩니다.
* **TCP Keep-Alive (L4/OS 수준):**
* 운영체제 커널(OS Kernel)이 담당합니다. 일정 시간 데이터 교환이 없으면 페이로드(데이터)가 없는 더미 ACK 패킷을 보내 연결을 확인합니다.
* 소켓 옵션(예: `SO_KEEPALIVE`)을 통해 켜고 끌 수 있습니다.
* 한계: OS는 살아있지만 실제 애플리케이션 프로세스가 데드락(Deadlock)에 빠지거나 멈춰있는 상황은 감지하지 못합니다.
* **Application Keep-Alive (L7 수준):**
* 이전에 보신 MQTT의 `KeepAlive`나 HTTP/1.1 커넥션 유지, WebSocket의 Ping/Pong 프레임이 여기에 해당합니다.
* 애플리케이션 계층에서 직접 메시지를 조립해서 보냅니다. (예: MQTT 클라이언트가 브로커에게 PINGREQ 전송 -> PINGRESP 수신)
* 장점: 애플리케이션의 비즈니스 로직 스레드가 정상적으로 동작하고 있는지까지 완벽하게 검증할 수 있습니다. 고성능 분산 시스템에서는 보통 TCP Keep-Alive에만 의존하지 않고 L7 Keep-Alive를 직접 구현합니다.
### 3. 인프라 관점 (AWS 등 클라우드 환경)
AWS의 ALB(Application Load Balancer), NAT Gateway, 방화벽 같은 네트워크 장비들은 내부적으로 메모리 관리를 위해 **Idle Timeout(유휴 시간 초과)** 설정을 가지고 있습니다. 아무 통신 없이 이 시간이 지나면 장비가 임의로 연결을 끊어버리는데(Connection Drop), Keep-Alive는 주기적으로 작은 패킷을 발생시켜 네트워크 장비들이 이 세션을 '활성화된 정상 세션'으로 인식하고 끊지 않도록 방지하는 역할도 합니다.
4. How Does MQTT Client Initiate a Connection with the CONNECT Message?

다음 MQTT Packet을 보면서 각각 항목들에 대해 이해해보자.
위에 정리된 것 보다 좀 더 자세하기 읽어보면 또 좋다.
1. ClientId (클라이언트 식별자)
- 브로커에 연결하는 각 MQTT 클라이언트를 구별하고 현재 상태를 추적하기 위해 사용하는 고유 식별자이다.
- 충돌을 막기 위해 클라이언트와 브로커마다 고유한 값을 가져야 한다.
- 상태 유지가 필요 없는 경우 빈(Empty) ClientId를 사용할 수 있지만(MQTT 3.1.1 기준), 이 경우 반드시 CleanSession 플래그를 true로 설정해야 하며 그렇지 않으면 브로커가 연결을 거부한다.
2. CleanSession (클린 세션)
- 클라이언트가 브로커와 지속적인 세션(Persistent Session)을 맺을지 결정하는 플래그이다.
- false로 설정하면 브로커는 클라이언트의 모든 구독 정보와 수신하지 못한 메시지(QoS 1, 2 기준)를 저장한다.
- true로 설정하면 브로커는 클라이언트에 대한 어떤 정보도 보관하지 않고 이전 세션 상태를 모두 폐기한다.
3. Username/Password (자격 증명)
- 클라이언트 인증 및 권한 부여를 위해 선택적으로 포함할 수 있는 계정 정보이다.
- 평문으로 전송하면 보안 위험이 크므로 TLS 등을 활용한 암호화나 해싱 적용을 강력히 권장한다.
- 일부 브로커는 SSL 인증서 기반 인증을 지원하여 아이디/비밀번호 방식을 완전히 대체할 수 있다.
4. Will Message (유언 메시지)
- 클라이언트가 예기치 않게 연결이 끊어졌을 때 다른 클라이언트들에게 알리기 위한 LWT(Last Will and Testament) 메시지이다.
- CONNECT 메시지 내에 특정 토픽과 메시지를 미리 지정해 둔다.
- 해당 클라이언트가 비정상적으로 종료되면, 브로커가 클라이언트를 대신하여 설정된 유언 메시지를 발행한다.
5. Keep Alive (상태 유지)
- 연결 수립 시 클라이언트가 지정하는 초 단위의 시간 간격으로, 메시지 전송 없이 브로커와 통신을 유지할 수 있는 최대 시간을 의미한다.
- 연결이 활성 상태인지 확인하기 위해 클라이언트는 주기적으로 PING 요청(PING Request)을 보내고 브로커는 PING 응답(PING Response)을 반환한다.
- 이를 통해 양측 모두 상대방의 네트워크 가용 상태를 파악한다.
유언 메시지는 아무리봐도... 재미있는 표현이다. 유언은 그치... 나 자신이 죽으면 내 유언을 적어둬야지 다른사람이 가져다가 공개를 한다고 생각하면 이해가 잘 된다.
4. MQTT Broker Response With a CONNACK Message
브로커가 CONNECT message를 받으면, CONNACK 메시지로 응답하는게 의무다.
TCP 연결시에 three hand shake 를 많이 들어봤을 것이다. 특히 syn, ack에 대해 잘 알텐데 CONNACK은 연결이 되었다는 응답으로 이해한다. connection + acknowledge 해서 말이다.
CONNACK 메시지는 두개의 데이터 entry 를 포함한다.
session present flag(세션 존재 플래그)
이전 세션이 브로커에서 사용할 수 있는지 여부를 알려준다. client가 clean session을 원하면 flag는 false로 될 것이고 이전 세션이 없음을 말한다.
하지만 클라이언트가 이전 세션을 재개하도록 요청하는 경우, 브로커에 세션 정보가 저장되어 있으면 해당 플래그는 true가 된다.
이 플래그는 클라이언트가 토픽을 다시 구독해야 하는지 또는 브로커에 이전 세션의 구독 정보가 여전히 남아 있는지 여부를 판단하는 데 도움이 된다.
connect return code(연결 반환 코드)
returnCode는 클라이언트에게 연결 시도의 성공 또는 실패 여부를 알려주는 상태 코드다.
이 코드는 잘못된 자격 증명이나 지원되지 않는 프로토콜 버전과 같은 다양한 유형의 오류를 나타낼 수 있다.
응답코드에 대한 상세한 설명은 다음을 참고하면 됀다. (3.2.2.3 Coninect Return Code)

5. What is an MQTT PUBLISH Message?
MQTT에서 client 가 broker에 연결하면 즉시 메시지를 발행할 수 있다.(publish)
메시지는 topic 기반으로 필터링하고 각각 메시지는 broker가 관심 있는 client에게 메세지를 전달하기 위한 토픽을 포함해야 한다.
이 부분 뭔가 본적 있지 않은가? topic-based filtering... 이전 글에서 subject-based filtering을 봤는데 MQTT는 이를 채택했다고 기억한다. topic 기반이 결국 이와 동일함을 인지하자.
그리고 이 글들이 잘못 적힌게... 왜 자꾸 정리를 안해주고 풀어쓰는지 모르겟지만... 그래서 MQTT PUBLISH Messag가 뭔데? 한줄로 정리해봐! 하면 읽고 솔직히 정리를 못하게끔 장황하게 풀었다.
MQTT PUBLISH 메시지는 클라이언트가 특정 토픽을 구독(Subscribe) 중인 다른 대상들에게 '실제 데이터(Payload)'를 전달하기 위해 발행하는 핵심 통신 패킷 이다

여기서 Packet에 대해서는 우리가 TCP쪽에서 많이 들었을 것이다.
OSI 7-Layer에서 7가지 계층이 있는데, L3 네트워크 계층의 Packet이 우리가 익히 알고 있다.
IP 기반으로 목적지까 길을 찾는 역할을 하는데, 여기의 Packet은 뭔가 의미가 약간 다르다.
OASIS MQTT Version 3.1.1에서 확인하면 2장에서 MQTT Control Packet format에 대해 설명한다.
MQTT 프로토콜은 정의된 방향으로 일련의 MQTT Control Packet를 교환함으로 발생한다.
MQTT Control Packet에는 Fixed Header, Variable Header, Payload가 있다.

뭔가 우리가 아는 Packet이랑 정말 다른데? 구조도 이상하고.

이전에 TCP/IP 스택 관련해서 3. How to Establish Communication Between MQTT Clients and MQTT Broker? 섹션에서 언급이 됐다.
사실 이미 이렇게 그림으로 설명을 해놨다. 이걸 물론 봣을 당시에는 이해를 못했는데,
무언가 일치하지 않음을 알고 찾아보니 그렇다.

OSI 7 Layer에서 5-7를 MQTT가 담당한다면 결국 MQTT 에서 설명하는 Packet이란 MQTT자체에서 응답하기 위해 만든 Packet인 것이다.
이를 mqtt-v3.1.1 문서에서는 MQTT Control Packet 으로 소개하고 있다.
다만, 여기서 놓치면 안되는 것이 MQTT Control Packet은 처음 연결을 위해서는 다음 과정을 거친다. (feat. Gemini)
실제 시스템 레벨에서 일어나는 전체 동작 흐름을 순서대로 나열하면 다음과 같다. 이 흐름을 이해하고 글을 읽으면 더욱 잘 이해가 될 것이다.
a. TCP 3-Way Handshake (L4 전송 계층, 선행 조건)
- MQTT는 TCP/IP 위에서 동작하므로, 애플리케이션에서 MQTT CONNECT 패킷을 만들어 보내기 전에 반드시 OS 커널 및 네트워크 장비 단에서 소켓 연결이 먼저 수립되어야 한다.
b. CONNECT 제어 패킷 전송 (클라이언트 -> 서버)
- TCP 스트림 통로가 열린 직후, 클라이언트가 가장 먼저 바이트 버퍼에 담아 서버로 던지는 패킷이다.
- 앞서 살펴본 ClientId, CleanSession, KeepAlive, 인증 정보 등이 여기에 담겨서 서버로 넘어간다.
c. CONNACK 제어 패킷 응답 (서버 -> 클라이언트)
- 브로커(서버)가 CONNECT 패킷을 디코딩하고 검증한 뒤, 연결 승인 여부를 반환한다.
- 이 패킷의 Return Code가 0(연결 수락)이어야 비로소 애플리케이션 레벨의 MQTT 논리적 세션이 맺어진 것이다.
d. SUBSCRIBE / SUBACK 제어 패킷 교환 (구독 단계)
- 클라이언트가 서버로부터 PUBLISH 메시지를 수신하려면, 통신망에 진입한 후 자신이 어떤 토픽을 받을 것인지 서버에 알려주어야(SUBSCRIBE) 한다.
- 서버는 구독 처리를 완료하고 SUBACK 패킷으로 응답한다. (단, CleanSession이 false이고 이전 구독 정보가 남아있다면 이 과정은 생략될 수 있다.)
e. PUBLISH 제어 패킷 전송 (양방향 데이터 통신)
- 연결과 세션 구성이 완료된 후, 실제 비즈니스 데이터(Payload)를 주고받는 단계이다.
- 센서나 기기가 브로커로 데이터를 발행할 수도 있고(클라이언트 -> 서버), 반대로 브로커가 구독 중인 클라이언트들에게 데이터를 중계할 수도 있다(서버 -> 클라이언트).
이제 packetId, topicName, qos, retainFlag, payload, dupFlag들이 무엇인지 알아보자.
6. packetId, topicName, QoS, Retain Flag, payload, dupFlag
너무 길어서 요약을 했다.
- Packet Identifier (PacketId)
- QoS 1 이상에서 메시지의 순서를 보장하고 유실 및 중복을 방지하기 위해 사용하는 고유 식별자이다.
- 송신 측에서 할당하며, 수신 측의 응답 패킷(PUBACK, PUBREC 등)과 짝을 이루어 전송 단계를 추적하는 데 필수적으로 사용된다.
- Topic Name (토픽 이름)
- 메시지를 분류하고 수신자를 라우팅하기 위해 슬래시(/)로 계층을 구분하는 문자열이다.
- +(단일 계층)와 #(다중 계층) 같은 와일드카드를 지원하지만, 남용할 경우 브로커의 리소스를 크게 소모하므로 구체적인 토픽 필터링이나 공유 구독(Shared Subscription)을 활용하는 것이 좋다.
- QoS (Quality of Service)
- 메시지 전달의 신뢰성 수준을 3단계로 정의한다.
- QoS 0: 최대 한 번 (전송 보장 없음, 유실 가능)
- QoS 1: 최소 한 번 (전송 보장, 중복 수신 가능)
- QoS 2: 정확히 한 번 (가장 높은 신뢰성, 높은 네트워크 지연 및 오버헤드 발생)
- Retain Flag (보존 플래그)
- 브로커가 특정 토픽의 가장 마지막 메시지를 '최근 정상 값'으로 저장하도록 지시하는 플래그이다.
- 새로운 클라이언트가 해당 토픽을 구독하면, 발행을 기다릴 필요 없이 브로커에 보존된 이 메시지를 즉시 수신하게 된다.
- Payload (페이로드)
- 실제 전송되는 데이터 본문이다.
- MQTT는 데이터의 형식을 따지지 않으므로 텍스트, 이미지, 바이너리 등 어떤 형태든 담을 수 있으나, 고빈도 통신 시 네트워크와 메모리 성능을 위해 크기를 최대한 작게 유지하는 것이 권장된다.
- DUP Flag (중복 플래그)
- 원본 메시지에 대한 확인 응답(ACK)을 받지 못해 재전송된 '중복 메시지'임을 나타내는 플래그이다.
- QoS 1 이상에서만 사용되며, 수신자(브로커 또는 클라이언트)는 이미 처리한 동일한 PacketId의 메시지라면 이 패킷을 무시해야 한다.
7. How MQTT brokers handle messages from clients?
그러면 MQTT Broker는 client로부터 메시지를 관리하는지 알아보자.
client가 메시지를 MQTT Broker에게 발행하면 broker는 메시지가 특정 CLIENT에게 QoS 레벨에 따라서 잘 전송됬는지 몇가지 작업을 수행한다.
- Message reception (메시지 수신): 브로커가 클라이언트로부터 전송된 메시지를 읽어 들이고 프로토콜의 구문과 형식이 올바른지 검증합니다.
- Acknowledgment (확인 응답): 브로커가 설정된 QoS(품질 서비스) 수준에 맞추어 클라이언트에게 메시지를 잘 받았다는 확인 응답(ACK)을 보냅니다.
- Processing (처리 및 라우팅): 브로커가 해당 토픽을 구독 중인 클라이언트들을 찾아 메시지를 복제하여 전달하며, 보존(Retain) 플래그 설정 여부에 따라 최신 값을 저장해 둡니다.
- Feedback (결과 피드백): 발행 클라이언트는 브로커로부터 '발행 성공' 확인만 받을 뿐, 실제로 몇 명의 구독자가 메시지를 받았는지에 대한 피드백은 알 수 없습니다.
8. How to Subscribe to MQTT Topics
그러면 MQTT Topics들을 어떻게 구독하는지 알아보자.
그래! client가 메시지를 publish하면 broker 가 이를 구독한 clients 들에게 전송한다.
그런데 publish를 하면 메시지가 전송이 된건지 안된건지는 모른다.
여기서 subscribe가 작용하기 시작한다(comes into play)
client가 MQTT 브로커에게 메시지를 발행하면, 메시지는 관심이 있는 client에게 가야만 한다.
관심 있는 주제에 대한 메시지를 수신하려는 클라이언트는 브로커에게 SUBSCRIBE 메시지를 보낸다.
SUBSCRIBE 메시지는 간단하며 고유한 패킷 식별자와 구독 목록을 포함한다.

- Packet Identifier (패킷 식별자): 클라이언트와 브로커 간의 메시지 흐름을 고유하게 식별하고 추적하기 위해 내부적으로 할당하는 번호이다.
- List of Subscriptions (구독 목록): 단일 패킷으로 여러 토픽(와일드카드 포함)과 각각의 요구 QoS를 동시에 구독할 수 있으며, 구독 조건이 겹칠 경우 가장 높은 QoS 레벨이 우선 적용된다.
여기서 필자가 헷갈린 부분은 분명 PUBLISH message에 대해 설명할 때 Identifier에 대해 언급해서, 그것과 이것이 같은줄 알앗는데, 아니라고 한다.
이에 대해 Gemini에게 질문한 결과를 첨부한다.
질문에 대한 답변: PUBLISH의 Packet ID와 SUBSCRIBE의 Packet ID는 같은 것인가?
결론부터 말하면 "아니다. 서로 완전히 다른 번호이다."
Packet ID라는 필드명(이름)만 같을 뿐, 서로 연관된 번호가 아니다. 각각의 제어 패킷이 수행하는 '독립적인 작업'이 잘 처리되었는지 확인하기 위해 그때그때 새로 발급하는 **'영수증 추적 번호'**라고 이해하면 쉽다.
- PUBLISH 패킷의 Packet ID:
- 목적: "내가 방금 보낸 '센서 데이터' 잘 받았어?"
- 흐름: PUBLISH 패킷 전송 ↔ PUBACK (또는 PUBREC 등) 수신
- 의미: 비즈니스 메시지(Payload)가 유실되지 않고 잘 전달되었는지 추적하기 위해 사용한다. (QoS 1, 2에서만 사용)
- SUBSCRIBE 패킷의 Packet ID:
- 목적: "내가 방금 보낸 '구독 신청서' 잘 처리됐어?"
- 흐름: SUBSCRIBE 패킷 전송 ↔ SUBACK 수신
- 의미: 구독 요청이라는 행위 자체가 네트워크 상에서 유실되지 않고 브로커에 잘 등록되었는지 추적하기 위해 사용한다.
요약하자면: PUBLISH를 할 때 썼던 번호를 기억해두었다가 SUBSCRIBE 할 때 다시 꺼내 쓰는 것이 아니다. 클라이언트가 브로커에게 "반드시 응답을 받아야 하는 중요한 요청(발행이든, 구독이든)"을 보낼 때, 그 요청이 무사히 도착했는지 짝을 맞추어 확인하기 위해 내부적으로 사용하는 일련번호일 뿐이다.
정리하자면 Packet ID는 데이터를 추적하기 위한 ID일 뿐이고, 실제로 이게 어느 토픽을 구독하고 했는지, 즉 라우팅을 위한 구독 정보 파악은 List of Subscriptions을 통해서 알 수 있다.
클라이언트가 "나 이 토픽 구독할래!" 하고 SUBSCRIBE 패킷을 브로커에게 던지면, 브로커는 그 정보를 자신의 서버 내부(메모리)에 단단히 기록해 둔다. 쉽게 말해, 브로커 내부에 거대한 '구독자 명부(라우팅 테이블)'를 만들어 관리하는 것이다.
일단 client가 MQTT Broker에게 요구하는 토픽과 QoS의 목록을 가진 SUBSCRIBE 메시지를 전송하면 브로커는 구독을 확인하고 브로커가 제공할 최대 QoS 레벨을 나타내는 SUBACK 메시지로 응답한다.
그러면 SUBACK 은 뭔데?
9. What is MQTT SUBACK?
클라이언트가 토픽과 해당 QoS 레벨을 명시한 SUBSCRIBE 메시지를 브로커로 전송하면, 브로커는 클라이언트에게 SUBACK 메시지를 보내 구독 요청을 승인한다.
SUBACK 메시지는 SUBSCRIBE 메시지 수신을 확인하고 브로커가 각 구독을 수락했는지 또는 거부했는지 여부를 나타낸다.

그림을 보면 Client 가 구독 요청을 한다(1). 그리고 Broker가 SUBACK로 결과를 전송한다(2).
구독에 성공하면 다른 client가 보내는 메시지를 broker에게 전송한다면(3) 이 내용이 구독자에게 발행된다(4)
10. How to use Unsubscribe in MQTT to Revoke Subscriptions?
그렇다면 구독은 어떻게 취소할까?
이것도 broker에게 메시지를 보내면 된다.

SUBSCRIBE할 때는 list of topics + qos 를 전송했지만 UNSUBSCRIBE시에는 list of topics만 전송한다.

무언가를 broker에게 보내면? 그에 따른 ACK를 보낸다.
CONNECT 도 그렇고, SUBSCRIBE도 그렇고, UNSUBSCRIBE도 그렇다.
간단히 요약하자면, MQTT는 클라이언트와 브로커 간 메시지 발행에 있어 유연하고 데이터 유형에 구애받지 않는 접근 방식을 제공한다.
토픽을 사용하여 메시지를 필터링함으로써 클라이언트는 관심 있는 콘텐츠를 빠르고 쉽게 구독할 수 있다.
각 메시지의 페이로드는 클라이언트의 특정 요구 사항에 맞게 사용자 정의할 수 있으며, MQTT는 다양한 데이터 유형을 지원하므로 여러 사용 사례에 적용 가능한 다목적 솔루션이다.
또한, QoS 수준 및 유지 플래그와 같은 PUBLISH 메시지의 속성을 이해하면 클라이언트와 브로커가 메시지를 효율적이고 안정적으로 전달할 수 있다!
다 내용을 보면 현재까지는 broker에 연결을 요청하고, 구독을하고 메시지를 받고, 구독을 취소하는 작업을 주거니 받거니 할 수 있다.
다만, 각각 상황에 맞게 조금씩 packet의 모양이 다른 것 뿐이다.
여기서 packet은 우리가 익히 아는 osi-7layer에서 4번째 계층의 tcp의 packet과 다름을 말한다.
MQTT Broker, Server Connection, SUBSCRIBE, UNSUBSCRIBE와 그 흐름들을 큰 맥락에서 보면 좋겠다.
이번 글은 MQTT Essentials : Part3, Part4를 이렇게 합쳐보았다. 다들 열공하세요~!
출처:
https://www.hivemq.com/blog/mqtt-essentials-part-3-client-broker-connection-establishment/
https://www.hivemq.com/blog/mqtt-essentials-part-4-mqtt-publish-subscribe-unsubscribe/
https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718018
'CS + 인프라 > Messaging' 카테고리의 다른 글
| [Messaging] MQTT 활용하기 - 1탄 : MQTT Broker선정과 진행상황 설명 및 구현 (0) | 2026.04.22 |
|---|---|
| [Messaging] MQTT 이해하기 - 4탄 : Wildcards & Best Practices + QoS (0) | 2026.04.16 |
| [Messaging] MQTT 이해하기 - 2탄 : Pub/Sub 구조 (0) | 2026.04.14 |
| [Messaging] MQTT 이해하기 - 1탄 : MQTT란 무엇인가? + mqtt introduction (0) | 2026.04.14 |