MQTT 수신 확인 뒤에도 남는 업무 처리의 책임

두 메시지 봉투와 수신함 옆 별도 도장과 데이터베이스를 표현한 삽화

메시지를 보냈고 수신 확인도 받았다면 일이 끝났다고 생각하기 쉽다. 하지만 통신 상대가 메시지를 받았다는 말과 우리 프로그램이 그 내용을 업무에 반영했다는 말은 다르다. MQTT의 전달 품질을 읽을 때에도 이 둘을 구분해야 한다. 프로토콜의 보장을 데이터베이스의 처리 보장으로 넓혀 읽지 않는 것이 출발점이다.

두 메시지 봉투와 수신함 옆 별도 도장과 데이터베이스를 표현한 삽화
주제를 표현한 개념 삽화. Google Gemini 이미지 기능을 사용했습니다.

10월 28일 DEV의 MQTT 설명 글을 읽다가 이런 경계가 중요하다고 느꼈다. 가벼운 메시징 구조를 소개하는 입문 글이지만 작은 헤더의 크기를 패킷 전체의 크기처럼 읽거나 전달 보장을 업무 전체의 무손실로 받아들이면 곤란하다. 이 글에서는 핵심 의미를 OASIS MQTT 5.0 표준의 전달 품질과 흐름 설명에 다시 대조했다.

참고 원문: 관련 원문을 직접 읽기

확인은 어느 두 지점 사이의 말인가

MQTT는 메시지를 발행하고 구독하는 방식으로 서로의 관심 주제를 연결한다. 전달 품질 수준은 그 통신 흐름의 약속이다. 구독 애플리케이션이 받은 내용을 데이터베이스에 한 번만 기록했다는 뜻까지 자동으로 포함하지 않는다. 표준에서도 수신 확인을 보내기 전에 애플리케이션 전달을 완료해야 한다고 일반화하지 않는다.

가상의 재고 관찰 프로그램을 생각해 보자. 장치가 “상자 한 개 이동”이라는 이벤트를 보낸다. 프로그램은 그 이벤트를 받아 수량 기록을 갱신하려 한다. 이 예시는 실제 창고나 상품 자료를 사용한 시험이 아니다. 전달과 업무 처리가 나뉘는 지점을 설명하기 위한 가정이다.

메시지를 받았다는 확인이 먼저 나가고 기록은 아직 반영되지 않은 순간이 있을 수 있다. 반대로 기록은 끝났는데 응답을 확인하지 못해 다시 같은 업무를 시도하는 상황도 생각할 수 있다. 어떤 전달 품질을 썼다는 표지만으로 그 두 상황의 처리 방법이 정해지지는 않는다. 애플리케이션이 알고 있는 상태와 프로토콜이 알고 있는 상태가 다르기 때문이다.

패킷의 번호와 업무의 이름

프로토콜이 쓰는 식별자를 영구적인 업무 번호로 취급하면 문제가 생길 수 있다. 번호가 재사용되는 범위와 시점이 업무 기록의 수명과 다르기 때문이다. 가상의 이벤트에는 통신과 별개로 업무상 구분 가능한 식별자가 있다고 가정해 보자. 그 식별자의 생성과 중복 범위는 장치와 서버가 함께 합의해야 한다.

그다음에는 “이 이벤트를 이미 반영했는가”와 “수량 변경을 저장했는가”가 어긋나지 않도록 설계할 필요가 있다. 예시로 같은 트랜잭션 안에서 처리 기록과 수량 변경을 함께 다루는 방식을 검토할 수 있다. 실제 데이터베이스의 기능과 격리 수준, 장애 조건에 따라 방법이 달라진다. 여기서 특정 구현을 실행하거나 무중복 처리를 입증한 것은 아니다.

무조건 같은 내용의 메시지를 버리는 것도 정답은 아니다. 같은 값이 두 번 관찰된 것이 정상적인 별도 사건일 수 있다. 내용이 같다는 것과 같은 사건이라는 것은 다르다. 따라서 중복의 단위를 업무 문장으로 적어야 한다. 통신 라이브러리의 기본 설정이 그 의미를 대신 결정해 주지는 않는다.

고장을 세는 작은 표

시험을 계획한다면 메시지 수신 전, 수신 후 기록 전, 기록 후 확인 전처럼 경계를 나눠 볼 수 있다. 각 구간에서 중단되면 다시 연결했을 때 무엇을 기대할지 적는다. 정상 흐름만 실행한 뒤 전달 품질의 이름으로 모든 장애를 통과했다고 할 수는 없다. 실제 운영 자료 대신 가상 입력을 쓰는 분리된 시험을 제안한다.

예를 들어 서로 다른 이벤트 E1과 E2를 보내고 E1을 다시 전달한 경우를 가정하자. 기대 결과가 두 사건의 반영이라면 E1의 재전달은 별도로 식별돼야 한다. 하지만 순서가 중요한 업무라면 이 시험만으로 충분하지 않다. 늦게 도착한 사건을 어떻게 해석할지도 정해야 한다. 기대 결과를 먼저 적어야 시험이 무엇을 놓쳤는지 보인다.

보관된 메시지와 세션 상태도 같은 관점으로 읽을 수 있다. 새 구독자에게 어떤 값이 전달되는지, 연결이 끊긴 동안 무엇이 남는지는 설정과 기능에 따른다. “MQTT는 가볍다”라는 소개만으로 보관 정책을 알 수는 없다. 표준의 범위와 실제 브로커 버전, 클라이언트 설정을 대조할 이유다.

입문 설명의 단순화는 개념을 처음 이해하는 데 도움을 준다. 다만 서비스의 보장을 적는 문서로 옮길 때에는 생략한 조건을 되돌려 놓아야 한다. 가장 작은 고정 헤더를 설명하는 숫자와 실제 업무 메시지의 전체 크기도 구분해야 한다. 여기서는 처리량이나 배터리 절감 수치를 내 환경의 결과처럼 제시하지 않는다.

가상 시험의 결과 표에는 최종 수량 외에 처리된 이벤트 식별자도 남길 수 있다. 숫자가 맞아도 한 사건을 두 번 반영하고 다른 사건을 빠뜨렸다면 우연히 합계만 같아질 수 있기 때문이다. 최종 값과 사건별 처리 기록을 함께 확인해야 기대한 의미에 가까워진다. 이 역시 실제 업무 규칙을 정한 뒤 검토할 제안이며 프로토콜만의 보장은 아니다.

재전달이 정상 경로의 일부라는 사실을 알고 있으면 오류 로그도 다르게 읽게 된다. 같은 식별자가 다시 나타났다는 사실과 같은 업무가 다시 실행됐다는 사실을 각각 기록하는 것이 출발점이다.

나는 수신 확인 표시를 보면 한 가지 질문을 더 붙이고 싶다. 누가 무엇을 확인했으며 아직 끝나지 않은 일은 어디에 남아 있는가. 그 답이 있으면 전달 품질과 업무 처리를 각각 검토할 수 있다. 지금의 프로그램에서 “받았다”와 “반영했다”를 구분하는 기록은 어디에 있을까?

기술 근거: OASIS MQTT 5.0 표준 — 전달 품질과 메시지 흐름

함께 읽기: SQLite와 실패 경계