# メッセージ再送信

メッセージ再送信はMQTTプロトコル仕様の一部です。

このプロトコルでは、通信当事者である**サーバー**と**クライアント**が送信するPUBLISHパケットは、以下のような**QoS（サービス品質）レベル**の要件を満たす必要があります。

- QoS 1: **メッセージが少なくとも1回は配信されること**を意味します。つまり、送信者はピアからの確認を受け取るまで常にメッセージを再送信します。このため、同じQoS 1メッセージがMQTTプロトコルの上位層（サービスのアプリケーション層）で複数回受信される可能性があります。
- QoS 2: **メッセージがちょうど1回だけ配信されること**を意味します。つまり、上位層でメッセージは一度だけ受信されます。

QoS 1およびQoS 2のPUBLISHパケットはMQTTプロトコルスタック層で再送信されますが、以下の点に注意してください。

- QoS 1メッセージの再送信が発生した場合、再送信されたPUBLISHパケットもMQTTプロトコルスタックの上位層で受信されます。
- QoS 2メッセージはどのように再送信されても、MQTTプロトコルスタックの上位層では1つのPUBLISHパケットのみが受信されます。

## 基本設定

メッセージが再送信されるシナリオは以下の2つです。

1. PUBLISHパケットをピアに送信後、指定時間内に応答がない場合、パケットを再送信する。
2. セッションを維持しつつクライアントが再接続した後、EMQXは未応答のメッセージを自動的に再送信し、正しいQoS処理を保証する。

設定ファイルで以下のように設定可能です。

| 設定項目         | 型       | オプション値 | デフォルト値 | 説明                                         |
| -------------- | -------- | -------- | -------- | ------------------------------------------ |
| retry_interval | duration | -        | 30s      | タイムアウト間隔を待ち、応答がなければメッセージを再送信する時間間隔 |

一般的には上記内容のみを意識すれば十分です。

EMQXがMQTTプロトコルの再送信をどのように処理するかの詳細は、本記事の後半をご参照ください。

## プロトコル仕様と設計

### 再送信対象

まず、EMQXの再送信メカニズム設計を理解する前に、プロトコルにおけるQoS 1およびQoS 2の送信プロセスを理解している必要があります。未確認の場合は[MQTTv3.1.1 - QoS 1: 少なくとも1回配信](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718101)および[MQTTv3.1.1 - QoS 2: ちょうど1回配信](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718102)を参照してください。

ここでは簡単に復習し、異なるQoSにおける再送信対象を示します。

#### QoS 1

QoS 1はメッセージが少なくとも1回配信されることを要求するため、送信者が確認メッセージを受け取るまでMQTTプロトコル層でメッセージが継続的に再送信される可能性があります。

プロセスの概略図は以下の通りです。

```
               PUBLISH
#1 送信者  --------------->  受信者       (*)
               PUBACK
#2 送信者  <---------------  受信者
```

- 2つのパケットが関与し、送信者と受信者それぞれ1回ずつ送信動作があります。両パケットは同じPacketIdを保持します。
- 行末に*がある場合、確認メッセージの待機がタイムアウトした際に送信者が再送信を開始する可能性を示します。

つまり、**QoS 1メッセージはPUBLISHメッセージのみ再送信されます。**

#### QoS 2

QoS 2はメッセージがちょうど1回配信されることを要求するため、より複雑なプロセスが必要です。概略図は以下の通りです。

```
               PUBLISH
#1 送信者  --------------->  受信者       (*)
               PUBREC
#2 送信者  <---------------  受信者
               PUBREL
#3 送信者  --------------->  受信者       (*)
               PUBCOMP
#4 送信者  <---------------  受信者
```

- 4つのパケットが関与し、送信者と受信者それぞれ2回ずつ送信動作があります。これら4つのパケットはすべて同じPacketIdを保持します。
- 行末に*がある場合、確認メッセージの待機がタイムアウトした際に送信者が再送信を開始する可能性を示します。

つまり、**QoS 2メッセージはPUBLISHパケットとPUBRELパケットのみ再送信されます。**

まとめると：

- **再送信動作**は、メッセージ送信後に指定時間内に期待される応答が受信できなかった場合にトリガーされます。
- **再送信対象**は以下の3種類のみです。
    * QoS 1のPUBLISHパケット
    * QoS 2のPUBLISHパケット
    * QoS 2のPUBRELパケット

EMQXがPUBLISHメッセージの受信者の場合、再送信操作は不要です。

### インフライトウィンドウと最大受信値

この概念の定義と説明は[Inflight Window and Message Queue](./inflight-window-and-message-queue.md)を参照してください。

これらの概念を導入する目的は以下の理解のためです。

1. EMQXが送信者の場合、再送信されるメッセージはインフライトウィンドウに格納されているメッセージでなければならない。
2. EMQXが受信者で、送信者がメッセージを再送信した場合：
    - QoS 1では、EMQXは直接PUBACKで応答する。
    - QoS 2では、EMQXは*最大受信メッセージ*キューに格納されたPUBLISHまたはPUBRELパケットを解放する。

### メッセージの順序

上記の概念は理解すれば十分ですが、特に**QoS 1メッセージの再送信後のメッセージ順序の変化**に最も注意してください。例として：

現在のインフライトウィンドウが2に設定されているとし、EMQXがクライアントのあるトピックに4つのQoS 1メッセージを配信しようとします。途中でクライアントプログラムまたはネットワークに問題が発生した場合、送信プロセスは以下のようになります。

```
#1  [4,3,2,1 || ]   ----->   []
#2  [4,3 || 2, 1]   ----->   [1, 2]
#3  [4 || 3, 2]     ----->   [1, 2, 3]
#4  [4 || 3, 2]     ----->   [1, 2, 3, 2, 3]
#5  [ || 4]         ----->   [1, 2, 3, 2, 3, 4]
#6  [ || ]          ----->   [1, 2, 3, 2, 3, 4]
```

このプロセスは6ステップあり、左側はEMQXのメッセージキューとインフライトウィンドウを`||`で区切って示しています。右側はクライアントが受信したメッセージの順序を示します。各ステップは以下を意味します。

1. ブローカーは4つのメッセージをメッセージキューに格納する。
2. ブローカーは順に`1`と`2`を送信し、**インフライトウィンドウ**に入れる。クライアントはメッセージ`1`にのみ応答し、送信ストリームに問題があるため以降の応答は送信されない。
3. ブローカーはメッセージ`1`の応答を受け取り、インフライトウィンドウから`1`を削除し、`3`を送信。`2`と`3`の応答を待つ。
4. 応答待ちがタイムアウトし、ブローカーはメッセージ`2`と`3`を再送信。クライアントは再送信された`2`と`3`を受信し正常に応答。
5. ブローカーはインフライトウィンドウから`2`と`3`を削除し、メッセージ`4`を送信。クライアントは`4`を受信し応答。
6. すべてのメッセージ処理が完了。クライアントが受信したメッセージの順序は`[1, 2, 3, 2, 3, 4]`であり、MQTTプロトコルスタックの上位層に順次報告される。

重複メッセージが存在しますが、これはプロトコル仕様に完全に準拠しています。各メッセージの初回出現は順序通りであり、繰り返し受信されたメッセージ`2`と`3`は再送信メッセージであることを示す識別ビットを持ちます。

MQTTプロトコルとEMQXはこのトピックを`Ordered Topic`として扱います。詳細は[MQTTv3.1.1 - メッセージ順序](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718105)を参照してください。

これにより、同じトピックとQoSの下でメッセージが順序通りに配信・応答されることが保証されます。

また、すべてのトピックのQoS 1およびQoS 2メッセージを厳密に順序付けたい場合は、インフライトウィンドウの最大長を1に設定する必要がありますが、クライアントのスループットは低下します。

### 関連設定

以下に、上記メカニズムで使用されるすべての設定を示します。すべて設定ファイルに含まれています。

| 設定項目            | 型       | オプション値     | デフォルト値 | 説明                                                    |
| ----------------- | -------- | -------------- | -------- | ----------------------------------------------------- |
| mqueue_store_qos0 | bool     | `true`, `false` | true     | QoS 0メッセージをメッセージキューに保存するかどうか       |
| max_mqueue_len    | integer  | >= 0           | 1000     | メッセージキューの長さ                                    |
| max_inflight      | integer  | >= 0           | 0        | インフライトウィンドウのサイズ。デフォルト`0`は無制限を意味する |
| max_awaiting_rel  | integer  | >= 0           | 0        | 最大受信数。デフォルト`0`は無制限を意味する                  |
| await_rel_timeout | duration | > 0            | 300s     | `最大受信`での解放待機の最大タイムアウト値。超過するとメッセージは破棄される |
