MQTTのv5.0では送信順序通りにPUBLISHが処理されるがv3.1.1では確実ではない

表題のとおりである。これは、過去記事で述べたように、v5.0では再接続時以外でのPUBLISH (PUBREC, PUBREL) の再送が許されないのに対し、v3.1.1では明示的に禁止されていないからである。

damedameo.hatenadiary.com

まずTCPから考えると、TCPは送信順序(シーケンス番号)を受信側で保証する。言い換えると、TCPのパケットは受信側に異なる順序で到達することもあるが、受信側で届いたパケットを送信した順に正しく並べ替える。これにより、上位のアプリケーションは、TCPソケットから順番通りにデータを受け取ることができる。もっといえば、到達パケットを格納するバッファの最前列に歯抜けのパケットがある場合、そのパケットが到達するまでTCPソケットはアプリケーションにデータを渡さないことになる。

ここで改めてMQTTについて考えると、MQTTサーバ (MQTTブローカー) は、PUBLISHを、MQTTクライアントから送信された順番通りに、TCPソケットから読み取れるということになる。これはつまり、MQTTクライアントがPUBLISHのデータをTCPソケットに書き込んだ順番通り、MQTTサーバがデータを処理することができるということである。

また、MQTTサーバ側からMQTTクライアント (SUBSCRIBE元) へPUBLISHする際にも、MQTTクライアント (PUBLISH元) からの到達順にPUBLISHを配信することが必須とされているので、MQTTクライアント(A)から送信されたPUBLISHは、それをSUBSCRIBEするMQTTクライアント(B)においても同じ順番で処理されるはずである。以下、この点についてv3.1.1およびv5.0の仕様を抜粋する。

v3.1.1 https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718105

When a Server processes a message that has been published to an Ordered Topic, it MUST follow the rules listed above when delivering messages to each of its subscribers. In addition it MUST send PUBLISH packets to consumers (for the same Topic and QoS) in the order that they were received from any given Client

v5.0 https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901240

When a Server processes a message that has been published to an Ordered Topic, it MUST send PUBLISH packets to consumers (for the same Topic and QoS) in the order that they were received from any given Client

一度TCPソケットに送信を依頼されたPUBLISHは、そのTCPコネクションにおいて同じシーケンス番号で取り扱われ続けるので、仮にロスが発生しても同じシーケンス番号で再送される。よって、仮に後発の新しいPUBLISHがロスなく送信されて先に対向へ到達したとしても、TCPは送信順序を保証するためにロスしたPUBLISHの到達を待つので、最終的には正しい順番でMQTTアプリケーションに渡ることになる。

ここでようやく表題の話ができる。v5.0では再接続時のみ再送を許すため、データの取り扱いがTCPコネクションにすべて委任され、対向側で送信順序どおりに処理されることが保証される。一方v3.1.1の場合、MQTTレベルでの再送が発生すると、同じPUBLISH (同じPacket Identifier) が異なるシーケンス番号で送信されてしまう。例えばQoS1だとすると、重複の可能性を踏まえて以下のような違いが生まれる可能性がある。

MQTTレベルで再送される場合にありえる処理順序

<- | PUB1 (ID: 1) | PUB2 (ID: 2) | PUB3 (ID: 3) | PUB1 (ID: 1) | <-

MQTTレベルで再送しない (TCPに任せる) 場合の処理順序

<- | PUB1 (ID: 1) | <- ... (PUBACKを受け取らないままコネクション切断)...   
<- | PUB1 (ID: 1) | PUB2 (ID: 2) | PUB3 (ID: 2) |  <-

※ 切断前にPUBACKさえ受け取れれば重複はしない理解でいる。

なお、コネクションが切断されて再接続した際の再送順序についても、以下の通り前回接続時に送信を試行した順とすることが義務付けられている。これにより、仮に途中でコネクションが切断されたとしても、当初の送信順序どおりに対向でデータが処理されることとなる。

V3.1.1 https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718105

When it re-sends any PUBLISH packets, it MUST re-send them in the order in which the original PUBLISH packets were sent (this applies to QoS 1 and QoS 2 messages) [MQTT-4.6.0-1]

V5.0 https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901240

When the Client re-sends any PUBLISH packets, it MUST re-send them in the order in which the original PUBLISH packets were sent (this applies to QoS 1 and QoS 2 messages) [MQTT-4.6.0-1]

ここまで話してきて、MQTTでは順序の取り扱いが思った以上に厳格であるということがわかる。コネクションの前提についても以下の通り言及がある。

https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Network_Connections

A Client or Server MUST support the use of one or more underlying transport protocols that provide an ordered, lossless, stream of bytes from the Client to Server and Server to Client [MQTT-4.2-1].