MQTT QoS 0·1·2 차이: 메시지 손실·중복·Exactly Once 이해하기

MQTT QoS 0·1·2 차이: 메시지 손실·중복·Exactly Once 이해하기#

1장 MQTT에서 모든 메시지를 같은 방식으로 보내야 할까#

주차장에 다음 두 종류의 데이터가 있다고 생각해보겠습니다.

주차면 Sensor 상태

FREE
OCCUPIED
FREE
OCCUPIED

그리고:

입차 Event

결제 완료 Event

장비 장애 Event

두 데이터는 중요도가 다릅니다.

주차면 상태는 1초마다 계속 갱신된다면 한 번 정도 Message를 놓쳐도 다음 값으로 현재 상태를 다시 알 수 있을지도 모릅니다.

반대로 결제 완료나 입출차 기록 같은 Event가 사라지면 업무 데이터가 달라질 수 있습니다.

그래서 MQTT는 Message 전달 수준을 선택할 수 있도록 QoS, Quality of Service를 제공합니다.

기본적으로 세 단계입니다.

QoS 0
→ At most once

QoS 1
→ At least once

QoS 2
→ Exactly once

하지만 여기서 가장 먼저 기억해야 할 것이 있습니다.

MQTT QoS는 Application 업무가 정확히 한 번 처리되는 것을 보장하는 기능이 아닙니다.

MQTT Protocol에서 Message를 어떻게 전달할지를 정의하는 규칙입니다.


2장 QoS 0은 한 번 보내고 확인하지 않는다#

QoS 0은 가장 단순합니다.

Publisher가 Broker로 Message를 보냅니다.

Publisher
    │
    │ PUBLISH
    ▼
 Broker

그리고 MQTT Level에서 별도의 전달 확인 Packet을 기다리지 않습니다.

그래서 흔히:

At most once

라고 표현합니다.

즉 Message가 한 번 전달될 수도 있고 Network 장애가 발생하면 전달되지 않을 수도 있습니다.


2.1 주차면 Sensor에서는 왜 QoS 0을 고려할 수 있을까#

주차면 Sensor가 다음 상태를 계속 보낸다고 하겠습니다.

10:00:00
FREE

10:00:01
FREE

10:00:02
OCCUPIED

10:00:03
OCCUPIED

10:00:02 Message가 손실되더라도:

10:00:03
OCCUPIED

가 도착하면 현재 상태를 다시 알 수 있습니다.

이처럼 과거 Event 하나보다 최신 상태가 더 중요한 데이터에서는 QoS 0을 고려할 수 있습니다.

다만:

Sensor Data
=
무조건 QoS 0

이라는 규칙은 아닙니다.

Message 발생 빈도, 손실 허용 범위, Network 상태와 업무 요구사항을 함께 봐야 합니다.


3장 QoS 1은 수신 확인을 받는다#

QoS 1에서는 PUBLISH 후 상대가 PUBACK을 반환합니다.

기본 흐름:

Publisher
    │
    │ PUBLISH
    ▼
 Broker
    │
    │ PUBACK
    ▼
Publisher

Publisher가 PUBACK을 받으면 해당 QoS 1 전달 단계가 완료되었다고 판단할 수 있습니다.

그래서 QoS 1은:

At least once

라고 부릅니다.

최소 한 번 전달하도록 시도하지만 같은 Message가 두 번 이상 전달될 가능성이 있습니다.


3.1 QoS 1에서 왜 중복이 발생할까#

다음 상황을 보겠습니다.

Publisher
    │
    │ PUBLISH
    ▼
 Broker
    │
    │ PUBACK
    ▼
 Network에서 PUBACK 손실

Broker는 Message를 받았습니다.

하지만 Publisher는 PUBACK을 받지 못했습니다.

Publisher 입장에서는:

Broker가 Message를 받았는지 알 수 없음

입니다.

따라서 Protocol 규칙에 따라 PUBLISH를 다시 전송할 수 있습니다.

Publisher
    │
    │ PUBLISH 재전송
    ▼
 Broker

이 과정에서 Application 관점에서는 같은 Message가 다시 전달될 수 있습니다.

그래서 QoS 1 Consumer는 중복을 고려하는 편이 안전합니다.


4장 DUP Flag와 Packet Identifier는 무엇인가#

QoS 1과 QoS 2의 전달 과정에서는 Packet Identifier가 중요합니다.

예를 들어:

Packet Identifier
42

를 가진 PUBLISH가 있다고 하겠습니다.

재전송이 필요하면 MQTT PUBLISH Packet의 DUP Flag가 사용될 수 있습니다.

개념적으로:

PUBLISH
Packet ID = 42
DUP = 0

↓

응답 확인 실패

↓

PUBLISH 재전송
Packet ID = 42
DUP = 1

처럼 이해할 수 있습니다.

하지만 DUP Flag를 Application의 영구적인 Event 중복 판별 ID로 사용해서는 안 됩니다.

Packet Identifier는 MQTT Protocol의 전달 과정에서 사용하는 식별자입니다.

업무 Event 중복을 처리하려면 별도로:

eventId

transactionId

commandId

같은 Application 식별자를 두는 것이 좋습니다.


5장 QoS 2는 네 단계의 흐름을 사용한다#

QoS 2는 가장 많은 Protocol 교환을 사용합니다.

대표적인 흐름은:

Publisher
    │
    │ PUBLISH
    ▼
 Broker
    │
    │ PUBREC
    ▼
Publisher
    │
    │ PUBREL
    ▼
 Broker
    │
    │ PUBCOMP
    ▼
Publisher

즉:

PUBLISH
↓
PUBREC
↓
PUBREL
↓
PUBCOMP

4단계를 사용합니다.

이를 통해 MQTT Protocol 수준에서 Message가 한 번 전달되도록 처리합니다.

그래서:

Exactly once

라는 이름을 사용합니다.


5.1 QoS 2는 왜 이렇게 복잡할까#

QoS 1에서는:

받았는가?

를 확인하는 것이 핵심입니다.

QoS 2에서는 한 단계 더 나아가:

이 Message의 전달 과정이
이미 처리되었는가?

를 Protocol 상태로 관리해야 합니다.

Publisher와 Receiver가 Packet Identifier와 Protocol 상태를 유지하면서 중복 전달을 방지합니다.

그 대가로:

Network Packet 증가

Broker 상태 관리 증가

Client 상태 관리 증가

Latency 증가 가능성

Memory 사용 증가

가 발생합니다.

그래서 모든 Message에 QoS 2를 쓰는 것은 일반적으로 좋은 전략이 아닙니다.


6장 QoS 2의 Exactly Once를 업무 처리와 혼동하면 안 된다#

이 부분이 가장 중요합니다.

MQTT QoS 2로 다음 Message가 전달됐다고 하겠습니다.

{
  "eventId": "payment-1001",
  "type": "payment-complete"
}

Consumer가 Message를 한 번 받았습니다.

그리고 Database에 저장했습니다.

MQTT Message 수신
↓
DB INSERT 성공
↓
Consumer Process Crash

이후 Application 복구 과정에서 업무 처리 상태가 별도로 꼬일 수 있습니다.

또 Consumer 내부에서:

결제 처리

정산 처리

외부 API 호출

같은 작업을 수행한다면 MQTT QoS만으로 이 모든 작업이 정확히 한 번 실행된다고 보장할 수 없습니다.

따라서:

MQTT QoS
+
Application Idempotency
+
Database Transaction
+
Event ID

를 별도로 생각해야 합니다.

MQTT Exactly Once와 Business Exactly Once는 같은 개념이 아닙니다.


7장 Publisher에서 지정한 QoS가 끝까지 그대로 적용되는 것은 아니다#

MQTT에서 놓치기 쉬운 부분입니다.

다음 구조를 보겠습니다.

Publisher
    │
    │ MQTT
    ▼
 Broker
    │
    │ MQTT
    ▼
Subscriber

실제로는:

Publisher → Broker

와:

Broker → Subscriber

가 각각 별도의 MQTT 전달 구간입니다.

예를 들어 Publisher가:

QoS 2

로 Publish했다고 해도 Subscriber의 Subscription QoS와 Broker 정책 등에 따라 Subscriber에게 전달되는 QoS가 달라질 수 있습니다.

따라서:

Publisher가 QoS 2
=
모든 Subscriber에게 무조건 QoS 2

라고 단정하면 안 됩니다.

실제 전달 수준은 Publish QoS와 Subscription에서 허용된 QoS 등의 영향을 받습니다.


8장 QoS별 차이를 한 번에 비교해보자#

QoS 전달 의미 주요 Packet 중복 가능성 Overhead
QoS 0 At most once PUBLISH Protocol 재전송 없음 가장 작음
QoS 1 At least once PUBLISH → PUBACK 있음 중간
QoS 2 Exactly once PUBLISH → PUBREC → PUBREL → PUBCOMP MQTT 전달 수준에서 억제 가장 큼

간단하게 기억하면:

QoS 0
→ 빠르고 단순

QoS 1
→ 전달 확인 + 중복 가능

QoS 2
→ 강한 전달 제어 + 높은 비용

입니다.


9장 주차관제 데이터를 QoS 관점에서 나눠보자#

가상의 주차관제 시스템을 생각해보겠습니다.

주차면 현재 상태#

Topic:

parking/site01/slot05/state

Payload:

{
  "state": "occupied"
}

상태가 자주 갱신되고 최신 값이 중요하다면:

QoS 0
또는
QoS 1

을 검토할 수 있습니다.


장비 장애 Event#

{
  "eventId": "alarm-1001",
  "deviceId": "gate-01",
  "type": "motor-failure"
}

이 Message가 누락되면 장애 대응이 늦어질 수 있습니다.

이런 Event에서는:

QoS 1
+
eventId
+
중복 제거

같은 구조를 고려할 수 있습니다.


결제 완료 Event#

결제와 같은 업무 Event는 MQTT QoS만으로 신뢰성을 해결하려고 해서는 안 됩니다.

예를 들어:

QoS 1 또는 QoS 2
+
Payment ID
+
Idempotency
+
Database Transaction
+
Audit Log

처럼 Application Level의 처리 정책이 함께 필요합니다.

따라서:

결제
=
무조건 QoS 2

라는 단순한 기준보다 전체 업무 흐름을 봐야 합니다.


10장 최신 상태와 Event는 다르게 다뤄야 한다#

MQTT 설계에서 매우 중요한 구분입니다.

State#

예:

현재 주차면 상태

현재 Gate 상태

현재 Sensor Battery

이런 데이터는 과거 Message 하나보다 최신 값이 중요합니다.

FREE
↓
OCCUPIED
↓
FREE

중간 값 하나가 사라져도 최종 상태를 다시 알 수 있다면 설계 선택지가 넓어집니다.


Event#

예:

입차 1001

결제 1002

출차 1003

Event는 하나가 빠지면 업무 이력이 달라질 수 있습니다.

그래서:

Event ID

Sequence

Persistent Storage

Idempotency

Retry

가 더 중요해집니다.

QoS 선택도 State인지 Event인지부터 구분해서 판단하는 것이 좋습니다.


11장 Retained Message와 QoS는 서로 다른 기능이다#

MQTT를 처음 공부하면 QoS와 Retained Message를 혼동하기 쉽습니다.

QoS는:

Message를 어떤 전달 수준으로 보낼 것인가?

와 관련됩니다.

Retain은:

Broker가 해당 Topic의 마지막 Retained Message를 보관할 것인가?

와 관련됩니다.

예를 들어:

parking/site01/slot05/state

현재 상태:

{
  "state": "occupied"
}

를 Retained Message로 저장하면 새 Subscriber가 접속했을 때 마지막 상태를 바로 받을 수 있습니다.

따라서:

QoS
≠
Retain

입니다.

서로 별도의 설정입니다.


12장 Session과 QoS도 함께 봐야 한다#

Client가 Network 문제로 Broker와 연결이 끊겼다고 하겠습니다.

Subscriber
↓
Disconnect
↓
잠시 Offline
↓
Reconnect

Offline 동안의 Message를 이후 받을 수 있는지는 QoS 하나만으로 결정되지 않습니다.

다음과 같은 요소도 영향을 줍니다.

MQTT Version

Session 설정

Session Expiry

Subscription

QoS

Broker 정책

MQTT 3.1.1에서는 Clean Session, MQTT 5에서는 Clean Start와 Session Expiry Interval 같은 개념을 사용합니다.

따라서:

QoS 1이니까
Offline 중 Message가 무조건 나중에 온다.

라고 생각해서는 안 됩니다.

Session 정책까지 확인해야 합니다.


13장 Python으로 QoS를 바꿔가며 실험해보자#

시험 Broker에서 QoS를 비교해볼 수 있습니다.

import paho.mqtt.client as mqtt

client = mqtt.Client(
    mqtt.CallbackAPIVersion.VERSION2,
    client_id="parking-test-publisher"
)

client.connect(
    "broker-test.local",
    1883,
    60
)

client.loop_start()

info = client.publish(
    "parking/test/slot01/state",
    payload='{"state":"occupied"}',
    qos=1
)

info.wait_for_publish()

client.loop_stop()
client.disconnect()

핵심은:

qos=1

입니다.

이를:

qos=0

또는:

qos=2

로 바꾸어 시험할 수 있습니다.

단순히 성공 여부만 볼 것이 아니라:

전송 Message 수

지연

중복

Reconnect

Broker Log

Client Log

를 비교하면 차이를 더 잘 이해할 수 있습니다.


14장 mosquitto_pub과 mosquitto_sub로 비교할 수도 있다#

Subscriber:

mosquitto_sub \
  -h broker-test.local \
  -t "parking/test/#" \
  -q 1

Publisher:

mosquitto_pub \
  -h broker-test.local \
  -t "parking/test/slot01/state" \
  -m '{"state":"occupied"}' \
  -q 1

여기서:

-q 0

-q 1

-q 2

를 바꾸어 시험할 수 있습니다.

시험 Network에서 Packet Capture를 함께 보면:

QoS 0
→ PUBLISH

QoS 1
→ PUBLISH / PUBACK

QoS 2
→ PUBLISH / PUBREC / PUBREL / PUBCOMP

차이를 확인할 수 있습니다.


15장 Network 장애를 만들어보면 QoS가 더 잘 보인다#

시험 환경에서 일부러 Network Delay나 Packet Loss를 넣어보는 것도 좋은 실습입니다.

Linux 시험 환경이라면 tc netem 같은 도구를 사용할 수 있습니다.

예:

sudo tc qdisc add dev eth0 root netem loss 10% delay 100ms

시험이 끝나면 설정을 반드시 제거합니다.

sudo tc qdisc del dev eth0 root

이런 테스트는 반드시 시험망에서만 수행해야 합니다.

관찰할 항목:

QoS 0에서 Message 누락

QoS 1에서 재전송

QoS 1 중복 가능성

QoS 2 Handshake

Latency 변화

Broker Queue

Reconnect 이후 동작

입니다.


16장 QoS를 높이면 성능 비용도 증가한다#

같은 Message를 전송한다고 하겠습니다.

QoS 0:

PUBLISH

QoS 1:

PUBLISH
PUBACK

QoS 2:

PUBLISH
PUBREC
PUBREL
PUBCOMP

QoS가 올라갈수록 Protocol 교환이 많아집니다.

따라서:

Network 사용량

Latency

Broker 처리량

Client 상태 관리

Memory

에도 영향을 줄 수 있습니다.

수만 개 IoT Device가 있는 시스템에서는 이런 차이가 크게 누적될 수 있습니다.

가장 높은 QoS를 선택하는 것이 아니라 필요한 신뢰성을 충족하는 가장 적절한 QoS를 선택하는 것이 중요합니다.


17장 중복 처리는 Consumer가 준비해야 한다#

QoS 1 Event:

{
  "eventId": "entry-10001",
  "type": "vehicle-entry"
}

가 두 번 도착했다고 하겠습니다.

Consumer는:

eventId
entry-10001

을 확인합니다.

처음:

존재하지 않음
↓
처리
↓
처리 완료 기록

두 번째:

이미 처리됨
↓
업무 처리 생략

같은 방식으로 중복을 방어할 수 있습니다.

이것이 Application의 Idempotency 설계입니다.


Packet Identifier를 업무 ID로 사용하지 않는다#

MQTT Packet Identifier는 Protocol 전달을 위한 값입니다.

시간이 지나면 다시 사용될 수 있고 Connection·Session의 Protocol 상태와 관련된 식별자이므로 장기적인 업무 Event ID 역할에 적합하지 않습니다.

따라서:

Packet Identifier
→ MQTT Protocol

eventId
→ Application 업무

로 역할을 분리하는 것이 좋습니다.


18장 QoS 장애를 어떻게 진단할까#

QoS 0 Message가 가끔 사라진다#

QoS 0의 특성일 수도 있습니다.

먼저:

Network 품질

Publisher Log

Broker Log

Subscriber 상태

를 확인합니다.


QoS 1인데 Message가 두 번 왔다#

Protocol상 가능한 상황입니다.

Consumer가 중복을 처리할 수 있는지 확인합니다.


QoS 1인데 Offline 동안 Message가 안 왔다#

QoS만 확인하지 않습니다.

Session 설정

Subscription

Broker Queue 정책

Reconnect 방식

을 확인합니다.


QoS 2인데 업무가 두 번 처리됐다#

MQTT Packet 전달과 Application 업무 처리의 경계를 확인합니다.

Consumer Retry

Database Transaction

외부 API 재호출

Idempotency

문제일 수 있습니다.


QoS를 높였더니 지연이 커졌다#

추가 Handshake와 Broker·Network 상태 관리 비용을 확인합니다.

특히:

RTT

Broker 부하

Inflight Message

Connection 수

등을 함께 봅니다.


19장 QoS 선택 판단표#

데이터 성격 우선 고려할 사항 QoS 후보
자주 갱신되는 현재 Sensor 상태 최신 값 중요·일부 손실 허용 가능 0 또는 1
장비 상태 변화 Event 누락 방지·중복 처리 가능 1
Alarm Event 신뢰성·중복 처리·지연 1 중심 검토
중요한 제어 Command ACK·Idempotency·실제 상태 확인 1 또는 요구사항 기반
중요한 업무 Event DB·Idempotency까지 함께 설계 1 또는 2 검토
고빈도 Telemetry Network·Broker 부하 0 또는 1

이 표는 절대 규칙이 아닙니다.

같은 종류의 데이터라도 현장 요구사항에 따라 다른 선택이 가능합니다.


20장 흔히 하는 잘못된 판단#

QoS 0은 Message가 항상 손실된다#

아닙니다.

전달 확인과 MQTT Level 재전송 보장이 없다는 의미이지 항상 손실된다는 뜻은 아닙니다.

QoS 1은 Message가 한 번만 온다#

아닙니다.

At least once이므로 중복 가능성이 있습니다.

QoS 2는 업무도 Exactly Once다#

아닙니다.

MQTT 전달 수준과 Application Transaction은 별개입니다.

중요한 데이터는 무조건 QoS 2가 좋다#

항상 그렇지 않습니다.

Latency와 Resource 비용, Application Idempotency까지 고려해야 합니다.

Publisher가 QoS 2면 Subscriber도 무조건 QoS 2다#

아닙니다.

Broker에서 Subscriber로 전달될 때 Subscription QoS 등의 영향을 받습니다.

QoS 1이면 Offline 중 Message도 무조건 저장된다#

아닙니다.

Session과 Broker 정책을 함께 확인해야 합니다.

DUP Flag만 확인하면 업무 중복을 제거할 수 있다#

아닙니다.

업무 중복에는 별도의 Event ID와 Idempotency 정책을 사용하는 것이 좋습니다.


21장 QoS 설계 체크리스트#

□ 이 데이터는 State인가 Event인가?

□ Message 하나가 손실되어도 되는가?

□ 최신 값만 있으면 되는가?

□ 중복 Message를 처리할 수 있는가?

□ Event ID가 있는가?

□ Consumer가 Idempotent한가?

□ Publisher QoS는 무엇인가?

□ Subscriber Subscription QoS는 무엇인가?

□ 사용하는 MQTT Version은 무엇인가?

□ Session 정책을 정의했는가?

□ Offline Message가 필요한가?

□ Retained Message가 필요한가?

□ Packet Identifier와 업무 Event ID를 구분하는가?

□ QoS 1 중복 가능성을 테스트했는가?

□ QoS 2의 추가 Latency를 측정했는가?

□ Broker 처리량을 확인했는가?

□ Reconnect 이후 동작을 테스트했는가?

□ Database 업무 처리까지 별도로 검증했는가?

22장 자기 점검#

QoS 0은 어떤 의미인가#

At most once입니다. MQTT Level에서 별도의 전달 확인을 받지 않으므로 Message가 전달되지 않을 가능성을 허용합니다.

QoS 1에서 중복이 발생할 수 있는 이유는 무엇인가#

PUBACK을 받지 못한 Publisher가 PUBLISH를 다시 전송할 수 있기 때문입니다.

QoS 2에서는 어떤 Packet이 오가는가#

대표적으로 PUBLISH → PUBREC → PUBREL → PUBCOMP 순서의 Handshake를 사용합니다.

QoS 2라면 결제 업무도 정확히 한 번 처리되는가#

아닙니다. MQTT Message 전달과 Database·외부 결제·업무 Transaction은 별도로 설계해야 합니다.

QoS 1인데 Offline 동안 Message가 저장되지 않았다면 무엇을 확인해야 하는가#

QoS뿐 아니라 Session 설정, Subscription과 Broker의 Queue·Persistence 정책을 확인해야 합니다.

23장 이 글을 마치며#

MQTT QoS의 기본 구조는 다음과 같습니다.

QoS 0
PUBLISH
QoS 1
PUBLISH
↓
PUBACK
QoS 2
PUBLISH
↓
PUBREC
↓
PUBREL
↓
PUBCOMP

QoS가 올라갈수록 전달 제어는 강해지지만 비용도 증가합니다.

그래서 선택 기준은:

무조건 높은 QoS

가 아니라:

데이터의 중요도

State인지 Event인지

손실 허용 여부

중복 허용 여부

Latency

Network 비용

Broker 부하

Application Idempotency

입니다.

특히 다음 다섯 가지를 기억하면 됩니다.

QoS 0은 전달 확인 없이 보내는 At most once 방식이며 최신 상태가 반복해서 전달되는 데이터에 적합할 수 있습니다.

QoS 1은 PUBACK을 이용하는 At least once 방식이므로 중복 Message가 발생할 수 있고 Consumer의 Idempotency가 중요합니다.

QoS 2는 MQTT Protocol 수준에서 Exactly once 전달을 제공하지만 Database나 실제 업무까지 정확히 한 번 실행된다는 의미는 아닙니다.

Publisher→Broker와 Broker→Subscriber는 별도의 전달 구간이므로 Publisher의 QoS 값만 보고 최종 Subscriber 전달 수준을 판단해서는 안 됩니다.

QoS와 Retain·Session·Persistence는 서로 다른 기능이므로 Offline Message와 현재 상태 보존까지 필요하다면 이 설정들을 함께 설계해야 합니다.

결국 MQTT QoS를 이해한다는 것은 0·1·2 중 어떤 숫자가 더 좋은가를 고르는 것이 아닙니다.

메시지가 손실되거나 중복되고 연결이 끊겼다가 복구되는 현실적인 상황에서 어떤 데이터는 놓쳐도 되고, 어떤 데이터는 반드시 다시 처리해야 하며, 중복된 업무를 어떻게 안전하게 제거할 것인지 전체 흐름을 설계하는 것이 핵심입니다.

이 페이지의 목차