# クラスター機能

## 概要

これは複数の Sora でクラスターを構成し、冗長化、負荷分散を行うための仕組みです。

ひとつのチャネル ID をクラスターに参加している複数の Sora で共有することができます。
そのため、Sora をクラスターに追加で参加させることで 1 チャネル単位での配信をスケールアウトさせることができます。

ここではクラスター機能について説明しています。

- クラスター機能を動かしてみる場合は [クラスター機能のチュートリアル](CLUSTER_TUTORIAL.html) をご確認ください
- クラスター機能の運用については [クラスター機能の運用](CLUSTER_OPS.html) をご確認ください
- クラスター機能利用時の録画については [クラスター機能での録画機能](CLUSTER_RECORDING.html) をご確認ください

## 用語

**ノード**
: クラスターに参加している Sora

**レギュラーノード**
: クラスター構成を維持するノード

**テンポラリーノード**
: クラスター構成を維持しないノード、ノードを停止すると強制的にクラスターから消去される

**リレー**
: ノード間で音声、映像、データを転送すること

**アフィニティ**
: 同一セッションの接続を同一ノードに集約すること

## クライアントへの複数シグナリング設定

Sora SDK にシグナリング URL を複数指定することで、いずれかの ノードが正常に動作していれば Sora に繋がります。

> **注釈**
>
> 最低 3 つ、レギュラーノードのシグナリング URL を渡す事を推奨しています。


## リレー機能

> **重要**
>
> リレー機能を利用するには [最大ノード数ライセンス](LICENSE.html#aee259) が必要になります。

> **注意**
>
> リレー機能を利用する際、ノード間通信には同一データセンター内のプライベートネットワークを利用してください。

Sora クラスターはリレーという仕組みで、
クラスターに参加しているノード同士で、クライアントの音声や映像、データを共有します。

例えば 3 ノードのクラスターがある場合、
すでに接続しているクライアントがいるノードとは異なるノードにクライアントが接続した場合、
Sora はその異なるノードにすでに接続しているクライアントの音声や映像、データをリレーします。

この機能を利用することで 1 チャネルでのスケールアウトを実現できるようになります。

リレー機能はクラスター機能が有効な場合に利用できます。

リレー機能はデフォルトで有効になっています。
無効にしたい場合は `sora.conf` の [cluster_relay](SORA_CONF.html#0ef660) を変更してください。

```ini
cluster_relay = false
```

### リレー発生のタイミング

リレー機能利用時、リレーが発生するタイミングは別ノードに同一チャネルへ参加しているクライアントがいる場合のみです。

同一ノードのみの場合は他のノードへのリレーは発生しません。

### リレー終了のタイミング

一度リレーが発生した場合、セッションが破棄されるまではリレーが破棄されることは基本的にありません。

### メッセージングのリレー

リレー機能は音声と映像だけでなく、メッセージもリレーを行います。そのため、リレー機能ではメッセージング機能も利用できます。

### 最大ノード数ライセンス

リレー機能は [最大ノード数ライセンス](LICENSE.html#aee259) が必要になります。もし [最大ノード数ライセンス](LICENSE.html#aee259) を利用せずに、
`sora.conf` の [cluster_relay](SORA_CONF.html#0ef660) を `true` にした場合、リレー機能は無効になります。

### 接続したノードが最大同時接続数に達していた場合

もし接続したノードがライセンスの最大同時接続数に達している場合は、
余裕のある別ノードへのリダイレクトを試みます。


## アフィニティ機能

> **重要**
>
> アフィニティ機能を利用するには [最大ノード数ライセンス](LICENSE.html#aee259) が必要になります。

Sora クラスターはリレー機能利用時に、同一セッションの接続を同一ノードに集約するアフィニティ機能を提供します。

同一セッションへの接続を同一ノードに集約することでレイテンシーを低減し、より低遅延な通信を実現できます。

アフィニティ機能はデフォルトで有効になっています
無効にしたい場合は `sora.conf` の [default_cluster_affinity](SORA_CONF.html#95466d) を変更してください

```ini
default_cluster_affinity = false
```

アフィニティはクラスター単位でのデフォルト値以外に、コネクション単位で指定することもできます。

コネクション単位の指定は `sora.conf` の [default_cluster_affinity](SORA_CONF.html#95466d) の値を上書きします
この仕組みを利用することで特定のコネクションだけアフィニティを行わず、最初に接続したノードで WebRTC の確立を試みることができるようになります。

> **注釈**
>
> リレー機能を検証したい場合は `sora.conf` の [default_cluster_affinity](SORA_CONF.html#95466d) を `false` に設定してください。

### 接続したノードが最大同時接続数に達していた場合

もし接続したノードがライセンスの最大同時接続数に達している場合は、
余裕のある別ノードへのリダイレクトを試みます。

### コネクション単位でのアフィニティ指定

認証ウェブフックの認証成功時の払い出しで `cluster_affinity` を指定することができます。

```javascript
{
   "allowed": true,
   "cluster_affinity": "<boolean>"
}
```

`"cluster_affinity": true` を指定した場合は、接続したノードがセッションを集約しているノードでなければ、
`"type": "redirect"` をクライアントへ返し、集約しているノードへの再接続を要求します。

`"cluster_affinity": false` を指定した場合は、ノードへの集約を行わず、接続したノードでの WebRTC 確立を試みます。

### クラスターアフィニティ利用時の 1 ノードセッション同時接続数基準値指定

> **注意**
>
> この設定を利用する場合は事前にサポートまでご相談ください

接続したノードのセッション単位での同時接続数が
`sora.conf` の [cluster_affinity_threshold](SORA_CONF.html#2abc6a) で指定した基準値を超えていた場合、
他のノードへのリダイレクトを試みるようになります。

全てのノードで基準値を超えた場合は、ノード単位での同時接続数が最も少ないノードにリダイレクトを試みます。

この設定はチューニング向けのため、基本的にはデフォルトから変更する必要はありません。

```ini
cluster_affinity_threshold = 20
```

### リレー機能利用時の認証について

リレー機能を利用している場合、認証成功時の払い出しでアフィニティを判断するため、
接続したノードで必ず認証を行います。

アフィニティが有効な場合は、別ノードへのリダイレクトが発生する場合があります。
その場合は別ノードでも認証が行われます。

アフィニティが無効な場合でも、接続を行ったノードがライセンスの上限に達していた場合で、
クラスター全体ではまだライセンスの上限に達していない場合は別ノードへのリダイレクトが発生します。
その場合は別ノードでも認証が行われます。

詳細は [クラスターリレー機能利用時に認証が 2 回行われる場合がある](AUTH_WEBHOOK.html#a1fc2c) をご確認ください。


## テンポラリーノード機能

> **重要**
>
> テンポラリーノード機能を利用するには [最大ノード数ライセンス](LICENSE.html#aee259) が必要になります。

テンポラリーノードはクラスター構成を維持するためのノードとして認識されず、
ノードを停止すると強制的にクラスターから消去されます。

そのため、クラスターを気軽にスケールアウト/スケールインさせることができます。

注意点としてテンポラリーノードが何ノード稼働していたとしても、
生存しているレギュラーノードが過半数を下回った場合、クラスターは利用できなくなります。

### 設定

`sora.conf` で `cluster_temporary_node` を `true` に設定することでテンポラリーノードとして扱われます。

```ini
cluster_temporary_node = true
```

### 登録

クラスターへの登録はレギュラーノードと同様 [RegisterClusterNode](API_CLUSTER.html#09ed96) API を利用してください。

テンポラリーノードはノードを停止したタイミングで、クラスターから強制的に消去されます。
そのため、再度クラスターに登録するには [RegisterClusterNode](API_CLUSTER.html#09ed96) API を利用して登録してください。

### 消去

テンポラリーノードはクラスターを維持するためのノードとして認識されず、
ノードを停止することでクラスターから強制的に消去されます。

そのためノード離脱時に [PurgeClusterNode](API_CLUSTER.html#13b35a) API でノードを消去する必要はありません。

### ネットワーク障害時

テンポラリーノードがネットワーク障害でクラスターから切断されたとしても、
ノードを停止しなければ自動でクラスターに復帰します。

### ノード障害時

テンポラリーノードが何かしらの障害で停止した場合は、必ず登録が必要です。

### 注意

- テンポラリーノードを利用するには [最大ノード数ライセンス](LICENSE.html#aee259) が必要です
- テンポラリーノードはクラスター構成を維持するためのノードとしては認識されません
- テンポラリーノードに障害が発生してもライセンスで決められた最大同時接続数の合計を維持する機能による接続数の維持は行われません
- テンポラリーノードはクラスターへの登録には [RegisterClusterNode](API_CLUSTER.html#09ed96) API を利用してください
- テンポラリーノードは停止時にクラスターから消去されます
- テンポラリーノードを再起動した場合、クラスターには自動で参加しません。再度 [RegisterClusterNode](API_CLUSTER.html#09ed96) API を利用して登録してください
- テンポラリーノードだけでクラスターを構築する事はできません
- テンポラリーノードは [PurgeClusterNode](API_CLUSTER.html#13b35a) API でノードを消去することはできません
- テンポラリーノードは [InitCluster](API_CLUSTER.html#621990) API で初期化することはできません

## シグナリングのリダイレクト機能

> **重要**
>
> Sora の SDK を利用している場合は、
> 基本的にここに書かれているリダイレクトの細かい仕様を把握する必要はありません。

`"type": "connect"` を送った際、認証処理前と認証処理後に、
`{"type": "redirect", "location": "wss://sora1.example.com/signaling"}` が Sora から送られてくる場合があります。

この場合は、送られてきた location の URL にシグナリング URL を切り替えて再度 `type: connect` を行ってください。
その際は `{type: connect, redirect: true}` のように `redirect: true` を追加情報として入れてください。

### 接続したノードが最大同時接続数に達していた場合

- リレー機能が無効な場合、チャネルのセッションが接続したノードに生成されている場合は接続に失敗します
- リレー機能が無効な場合、チャネルのセッションが接続したノードに生成されていない場合、余裕のあるノードへリダイレクトを試みます
- リレー機能が有効な場合、余裕のあるノードへリダイレクトを試みます

## リダイレクト機能によるノード選択

### リレー機能有効かつアフィニティ機能有効時

アフィニティ機能が有効な場合はできるだけ同一セッションを同一ノードに集約します。

すでに、そのチャネル ID がクラスター内部で利用されている場合は、
そのチャネル ID のセッションが存在するノードへ接続先の割り当てを試みます。

また、そのチャネル ID がクラスター内部で利用されているが、
そのチャネル ID のセッションが存在するノードのセッション単位の同時接続数が、
[cluster_affinity_threshold](SORA_CONF.html#2abc6a) の基準値を超えていた場合は、
他のノードへの割り当てを試みます。

ちなみに、全てのノードで [cluster_affinity_threshold](SORA_CONF.html#2abc6a) の基準値を超えた場合は、
一番余裕のあるノードへの割り当てを試みます。

割り当てられたノードの同時接続数がライセンスの上限に達していた場合、
一番余裕のあるノードへの割り当てを試みます。

### リレー機能有効かつアフィニティ機能無効時

すでに、そのチャネル ID がクラスター内部で利用されている場合でも、
接続したノードへの割り当てを試みます。

接続したノードが同時接続数のライセンスの上限に達成していた場合、
一番余裕のあるノードへの割り当てを試みます。

### リレー機能無効時

すでに、そのチャネル ID がクラスター内部で利用されている場合は、
そのチャネル ID のセッション ID が存在するノードへ割り当てを試みます。

ただし、そのノードの同時接続数がライセンスの上限に達していた場合、
接続に失敗します。

## ロードバランス機能

クラスターを構築しているノードの中で `ライセンスの最大同時接続数` を `現在の同時接続数`
で引いた値が一番小さいノードに、チャネル ID の新規セッションを割り当てます。
この値がすべて同じ場合は、接続しに行ったノード自身が新規チャネル ID のセッションを担当します。

また、接続したノードがライセンスの最大同時接続数に達していた場合、
他のノードへのリダイレクトを試みます。

### アフィニティ機能を無効にしている場合

アフィニティ機能を無効にしている場合は接続を試みたノードに接続し、リダイレクトは一切発生しません。
ただし、ライセンスの最大同時接続数を超えていた場合、他のノードへのリダイレクトを試みます。

## HTTP API のリダイレクト機能

チャネル ID を指定する HTTP API を利用する場合、リダイレクトが発生することがあります。
指定されたチャネル ID を他ノードが担当している場合に、ステータスコード 307 (Temporary Redirect)の
HTTP 応答により、クライアントにそのノードへのリダイレクトを要求します。

ステータスコード 307 の HTTP 応答受信時の処理は、HTTP のクライアントとして使用するツールやライブラリにより異なります。
必要に応じて、リダイレクト応答の Location ヘッダーへアクセスを継続するようにしてください。

[HTTPie](https://httpie.io/) の場合は  `--follow` を指定することで、リダイレクト先への再要求を行います。


## クラスターへの登録

クラスターへノードを登録する機能です。
既存のクラスターに登録する際に、クラスターに登録させるノードで [RegisterClusterNode](API_CLUSTER.html#09ed96) API を実行します。

また、そのノードが一度でもクラスターに参加したことがある場合は、クラスターへの参加を自動で試みます。

## クラスター自動復旧

クラスターで利用しているネットワークに障害が発生した際には、個々のノードはクラスターを構成する他のノードへの接続を試みて、復旧処理を行います。

## ネットワーク分断時の接続受け付けの停止

ネットワーク分断時には Sora のノード間で通信ができずに、クラスター内の情報の整合性が取れなくなる可能性があります。
この情報の不整合を回避するために、Sora は自分がクラスター内の半数以下のグループに属した場合には、既存の接続を切断し、
さらに新規接続の受け付けも停止します。

## 特定ノードへの新規チャネル ID の割り当て停止

[モード機能](MODE.html) の [新規セッションブロックモード](MODE.html#041e1c) または [新規コネクションブロックモード](MODE.html#0b67d3) になっている場合は、
そのノードに対して新規チャネル ID の割り当てを行いません。


## ライセンスで決められた最大同時接続数の合計を維持する機能

> **重要**
>
> ライセンスで決められた最大同時接続数の合計を維持する機能を利用するには [最大ノード数ライセンス](LICENSE.html#aee259) が必要になります。

クラスターを構築している場合、特定のノードがハードウェア障害などで利用できなくなった場合、
残りのノードで障害が発生したレギュラーノードの同時接続数を引き継ぎます。

例えば 100 同時接続のレギュラーノードを 3 つでクラスターを構築している場合、
1 つのレギュラーノードに障害が発生した場合は 2 つのノードで 300 同時接続を維持できます。

割り当ては 1 つのノードが 150 までの同時接続数を許容できるようになります。

障害が発生したレギュラーノードが復旧したとしても 100 を超えている接続を切断することはありません。
ただし 1 ノードの最大同時接続数の 100 までに戻ります。

### 切り上げ

レギュラーノードが 3 ノード、テンポラリーノードが 1 ノードあり、
それぞれが 100 同時接続数をできる場合、
レギュラーノードが 1 ノード障害が発生した場合、残りの 3 ノードがそれぞれ +34 同時接続数を一時的に引き継ぎます。

残りのノード数で割った結果の小数点以下は切り上げます。

### テンポラリーノード利用時の注意

この機能はテンポラリーノードには適用されません。

例えばレギュラーノードが 3 ノード、テンポラリーノードが 1 ノードあり、
それぞれが 100 同時接続数をできる場合、テンポラリーノードが 1 ノード障害が発生したとしても、
テンポラリノードの同時接続数分を維持することはありません。

レギュラーノード 1 ノードに障害が発生した場合、テンポラリーノードは同時接続数分の維持を行います。

## 設定

クラスター機能を利用するには以下の設定を行ってください。

- [cluster](SORA_CONF.html#b2cd99)
- [node_name](SORA_CONF.html#748d89)
- [external_signaling_url](SORA_CONF.html#d6bf68)
- [external_api_url](SORA_CONF.html#8b922a)

### cluster

**必須設定**

クラスター機能を利用する場合、 `sora.conf` にて [cluster](SORA_CONF.html#b2cd99) を `true` を設定する必要があります。

デフォルトでは `cluster` は有効になっていません。

> **重要**
>
> cluster = true にして Sora を起動した場合、
> Sora は [InitCluster](API_CLUSTER.html#621990) API を実行するか、クラスターに参加して過半数のグループになったタイミングでのみ利用可能になります。

```ini
cluster = true
```

### node_name

**必須設定**

クラスター機能を利用する際のノード名を指定して下さい。

この名前はクラスター内のノードの識別に使われます。
具体的には、クラスターノード間の通信相手の特定や、
[RegisterClusterNode](API_CLUSTER.html#09ed96) API、
[PurgeClusterNode](API_CLUSTER.html#13b35a) API で指定する名前として利用します。

> **重要**
>
> `node_name` はクラスター内のすべてのノードでユニークである必要があります

ノード名の @ の前には、正規表現 `[0-9A-Za-z_\\-]+` にマッチする任意の文字列を指定してください。
また @ の後ろには、他のノードからアクセス可能なこのノードのドメイン名（FQDN）や、IP アドレスを指定してください。

@ の後ろにホスト名（"." を含まない文字列）のみを指定すると、ノード間で通信が行えなくなってしまいますので、ご注意ください。

```ini
# ドメイン名を指定する例
node_name = node01@node01.example.com
```

```ini
# IP アドレスを指定する例
node_name = node02@192.0.2.1
```

### external_signaling_url

**必須設定**

`sora.conf` にてシグナリングの URL を指定して下さい。
この URL はクラスター機能を利用した際に `"type": "redirect"` の `"location"` の値として払い出されます。

```ini
external_signaling_url = wss://node1.example.com/signaling
```

例えば、上記の設定の場合は、シグナリングのリダイレクトが必要な際に Sora から `{"type": "redirect", "location": "wss://node1.example.com/signaling"}` として払い出されます。

### external_api_url

**必須設定**

`sora.conf` にて API の URL を指定して下さい。
この URL はクラスター機能を利用した際に、API を適切なノードに HTTP 307 でリダイレクトさせる場合の
HTTP の `location` ヘッダーの値として払い出されます。

この URL は外部に公開する必要はなく、プライベートなアドレスでも問題ありません。

```ini
# IP アドレスを指定する例
external_api_url = http://192.0.2.10:3000/

# ドメイン名を指定する例
external_api_url = https://node1.example.com/api
```

### cluster_relay

> **重要**
>
> クラスターリレー機能を利用する場合は [最大ノード数ライセンス](LICENSE.html#aee259) が必要になります。

クラスターリレー機能を利用する場合、 `sora.conf` にて [cluster](SORA_CONF.html#b2cd99) を `true` を設定する必要があります。

`cluster_relay` のデフォルトは `true` になっています。

```ini
cluster = true
```

### default_cluster_affinity

> **重要**
>
> クラスターアフィニティ機能を利用する場合は [最大ノード数ライセンス](LICENSE.html#aee259) が必要になります。

クラスターリレー機能でアフィニティ機能を利用する場合、 `sora.conf` にて [cluster](SORA_CONF.html#b2cd99) と [cluster_relay](SORA_CONF.html#0ef660) を `true` を設定する必要があります。

`default_cluster_affinity` のデフォルトは `true` になっています。

```ini
default_cluster_affinity = true
```

### cluster_affinity_threshold

クラスターリレー機能でアフィニティ機能で、他のノードへのリレーが発生する 1 ノード 1 セッションあたりの同時接続数を指定します。チューニング目的の設定となります。

- 片方向で大規模配信であれば 100 などの大きめの設定をお勧めします
- 双方向で 1 チャネルで最大 5 接続までなら 5 を設定することをお勧めします

```ini
cluster_affinity_threshold = 5
```

### cluster_listen_{min,max}_port

`sora.conf` にてクラスター情報の同期に利用する TCP の受信ポートの範囲を指定してください。

デフォルトでは 49010 - 49020 が指定されています。

```ini
cluster_listen_min_port = 49010
cluster_listen_max_port = 49020
```


## API

### クラスター初期化 API

クラスターを構築するときは、まずクラスターの初期化を行います。

> **重要**
>
> クラスターの初期化はクラスターを初めて構築する際と、クラスターが破綻した際に行う必要があります。

クラスターの初期化を実行する場合は `20221221.InitCluster` API をクラスター構築するいずれかのノードで実行します。
`node_name_list` にはクラスターを構成するノードを、 `20221221.InitCluster` API を実行するノードを含め指定してください。

API の詳細は [InitCluster](API_CLUSTER.html#621990) API をご確認ください。

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20221221.InitCluster \
    node_name_list:='["node-02@192.0.2.7", "node-03@192.0.2.8", "node-01@192.0.2.5"]' \
    -vvv
POST / HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 83
Content-Type: application/json
Host: 127.0.0.1:3000
User-Agent: HTTPie/3.2.2
x-sora-target: Sora_20221221.InitCluster

{
    "node_name_list": [
        "node-02@192.0.2.7",
        "node-03@192.0.2.8",
        "node-01@192.0.2.5"
    ]
}


HTTP/1.1 200 OK
access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept, x-sora-target
access-control-allow-methods: POST, OPTIONS
access-control-allow-origin: *
access-control-max-age: 1000
content-length: 80
content-type: application/json
date: Wed, 06 Dec 2023 06:52:47 GMT
server: Cowboy

{
    "node_name_list": [
        "node-02@192.0.2.7",
        "node-03@192.0.2.8",
        "node-01@192.0.2.5"
    ]
}
```

> **重要**
>
> クラスターのノードが恒久的に破損し、クラスターを構成するノードが半数以下になった場合には、
> 全てのノードが接続を受け付けなくなるため、クラスターの再構築手順に従って、
> 再度クラスターの初期化を行う必要があります。
>
> 詳しくは [クラスター破綻からの再構築手順](CLUSTER_OPS.html#7c3099) をご確認ください。

### クラスター登録 API

初期化後のクラスターにノードを追加するときは、すでにクラスターに参加しているノードを指定して、登録するノードに API を実行します。

API の詳細は [RegisterClusterNode](API_CLUSTER.html#09ed96) API をご確認ください。

下記は、既存クラスター内の **node-01@192.0.2.5** ノードを `contact_node_name` に指定して、新規に登録するノードで [RegisterClusterNode](API_CLUSTER.html#09ed96) APIを実行しています。

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20211215.RegisterClusterNode \
    contact_node_name=node-01@192.0.2.5
HTTP/1.1 200 OK
content-length: 80
content-type: application/json
date: Tue, 16 Nov 2021 08:42:25 GMT
server: Cowboy

{
    "node_name_list": [
        "node-01@192.0.2.5",
        "node-02@192.0.2.7",
        "node-03@192.0.2.8"
    ]
}
```

### クラスターノード完全消去 API

あるノードが障害で再度の参加が難しい、またはスケールインのためにノード破棄する場合は、
破棄するノードを停止後に [PurgeClusterNode](API_CLUSTER.html#13b35a) API を利用して、そのノード情報を完全消去してください。

`20220629.PurgeClusterNode` API は、クラスターに参加している破棄するノード以外のどのノードでもかまわないので 1 回だけ実行します。
この API で完全消去した結果はクラスター内部で共有されます。

> **警告**
>
> PurgeClusterNode API はクラスターからノードを完全に消去するための API です。
> 再度参加するノードに対しては基本的に使用しないでください。
> この API を含めた運用の手順は [クラスター機能運用](CLUSTER_OPS.html) をご確認ください。

詳細は [PurgeClusterNode](API_CLUSTER.html#13b35a) API をご確認ください。

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20220629.PurgeClusterNode \
    target_node_name=node-01@192.0.2.5
HTTP/1.1 200 OK
access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept, x-sora-target
access-control-allow-methods: POST, OPTIONS
access-control-allow-origin: *
access-control-max-age: 1000
content-length: 40
content-type: application/json
date: Wed, 06 Dec 2023 07:16:06 GMT
server: Cowboy

{
    "target_node_name": "node-01@192.0.2.5"
}
```

### クラスターノード一覧 API

詳細は [ListClusterNodes](API_CLUSTER.html#a70901) API をご確認ください。

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20211215.ListClusterNodes
HTTP/1.1 200 OK
content-length: 1006
content-type: application/json
date: Tue, 16 Nov 2021 08:36:25 GMT
server: Cowboy

[
    {
        "external_api_url": "http://192.0.2.5:3000/",
        "license_max_connections": 600,
        "license_max_nodes": 10,
        "license_serial_code": "ABCDEF-SRA-E001-203801-400",
        "license_type": "Experimental",
        "node_name": "node-01@192.0.2.5",
        "connected": true,
        "mode": "normal",
        "external_signaling_url": "wss://node-01.example.com/signaling",
        "version": "2023.2.0"
    },
    {
        "external_api_url": "http://192.0.2.7:3000/",
        "license_max_connections": 600,
        "license_max_nodes": 10,
        "license_serial_code": "ABCDEF-SRA-E002-203801-400",
        "license_type": "Experimental",
        "node_name": "node-02@192.0.2.7",
        "connected": true,
        "mode": "normal",
        "external_signaling_url": "wss://node-02.example.com/signaling",
        "version": "2023.2.0"
    },
    {
        "external_api_url": "http://192.0.2.8:3000/",
        "license_max_connections": 600,
        "license_max_nodes": 10,
        "license_serial_code": "ABCDEF-SRA-E003-203801-500",
        "license_type": "Experimental",
        "node_name": "node-03@192.0.2.8",
        "connected": true,
        "mode": "normal",
        "external_signaling_url": "wss://node-03.example.com/signaling",
        "version": "2023.2.0"
    }
]
```

### クラスターチャネル割り当て一覧 API

詳細は [ListClusterChannels](API_CLUSTER.html#0a4459) API をご確認ください。

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20211215.ListClusterChannels
HTTP/1.1 200 OK
content-length: 461
content-type: application/json
date: Tue, 16 Nov 2021 08:42:25 GMT
server: Cowboy

[
    {
        "channel_id": "sora",
        "owners": [
           {
               "node_name": "node-01@192.0.2.5",
               "connected": true
           }
        ]
    },
    {
        "channel_id": "lemon",
        "owners": [
           {
               "node_name": "node-01@192.0.2.5",
               "connected": true
           }
        ]
    },
    {
        "channel_id": "hisui",
        "owners": [
           {
               "node_name": "node-03@192.0.2.5",
               "connected": true
           }
        ]
    },
    {
        "channel_id": "zakuro",
        "owners": [
           {
               "node_name": "node-03@192.0.2.5",
               "connected": true
           }
        ]
    }
]
```

## 接続時の挙動

### リレーあり / アフィニティあり

- 新規セッションでの接続- 一番空いてるノードへリダイレクト
- 既存セッションへの接続- 既にセッションがあるノードへリダイレクト
- 新規セッションでの接続 (接続したノードの同時接続数がライセンス上限に達している場合)- 一番空いてるノードへリダイレクト
- 既存セッションへの接続 (接続したノードの同時接続数がライセンス上限に達している場合)- 一番空いてるノードへリダイレクト

### リレーあり / アフィニティなし

- 新規セッションでの接続- 接続したノードを選択
- 既存セッションへの接続- 接続したノードを選択
- 新規セッションでの接続 (接続したノードの同時接続数がライセンス上限に達している場合)- 一番空いてるノードへリダイレクト
- 既存セッションへの接続 (接続したノードの同時接続数がライセンス上限に達している場合)- 一番空いてるノードへリダイレクト

### リレーなし

- 新規セッションでの接続- 一番空いてるノードへリダイレクト
- 既存セッションへの接続- 既にセッションがあるノードへリダイレクト
  - 既にセッションがあるノードがライセンス上限に達していた場合、接続失敗
- 新規セッションでの接続 (接続したノードの同時接続数がライセンス上限に達している場合)- 一番空いてるノードへリダイレクト
- 既存セッションへの接続 (接続したノードの同時接続数がライセンス上限に達している場合)- 既にセッションがあるノードへリダイレクト
  - 既にセッションがあるノードがライセンス上限に達していた場合、接続失敗

### クラスターなし

- 新規セッションでの接続- 接続したノードを選択
- 既存セッションへの接続- 接続したノードを選択
- 新規セッションでの接続 (接続したノードの同時接続数がライセンス上限に達している場合)- 接続失敗
- 既存セッションへの接続 (接続したノードの同時接続数がライセンス上限に達している場合)- 接続失敗

## リレーの終了とアフィニティ機能

リレー機能を利用した場合、一度リレーがノード間で発生すると、セッションが破棄されるまでリレーが行われます。
ノードへの接続数が 0 になったとしてもリレーが発生します。これは Sora の仕様です。

アフィニティ機能を有効にした状態であれば、基本的に同一セッションのコネクションは同一ノードに割り当てられます。
もし別ノードにリレーが発生したとしても、そのリレーが発生したノードに割り当てるようになります。

アフィニティ機能を無効にして、接続を各ノードに分散させた場合は、
リレーが発生したノードで、接続数が 0 になったとしてもリレーが行われ続けますが、これは仕様です。

## シーケンス図

> **注釈**
>
> 3 ノードのクラスターですが Sora3 を省略しています

### リレー機能かつアフィニティ機能が有効

- アフィニティ機能により寄せが発生し、リダイレクトを試みる場合がある
- アフィニティ機能は認証成功時の払い出しで判断するため必ず認証は行う
- リダイレクトが発生した場合は、リダイレクト先のノードでも認証を行う

```mermaid
sequenceDiagram
    participant C1 as クライアント1
    participant C2 as クライアント2
    participant S1 as Sora1
    participant S2 as Sora2
    participant A as アプリケーションサーバー
    C1->>S1: "type": "connect"
    S1->>+A: 認証ウェブフック
    A-->>-S1: 200 OK<br>"allowed": true<br>"cluster_affinity": true
    S1->>+A: セッションウェブフック<br />"type": "session.created"
    A-->>-S1: 200 OK
    S1->>C1: "type": "offer"
    C1->>S1: "type": "answer"
    note over C1, S1: WebRTC 接続
    S1->>+A: イベントウェブフック<br />"type": "connection.created"
    A-->>-S1: 200 OK
    C2->>S2: "type": "connect"
    S2->>+A: 認証ウェブフック
    A-->>-S2: 200 OK<br>"allowed": true<br>"cluster_affinity": true
    note left of S2: アフィニティ機能により Sora1 へリダイレクト
    S2->>C2: "type": "redirect"
    C2->>S1: "type": "connect", "redirect": true
    S1->>+A: 認証ウェブフック
    A-->>-S1: 200 OK<br />{"allowed": true}
    S1->>C2: "type": "offer"
    C2->>S1: "type": "answer"
    note over C2, S1: WebRTC 確立
    S1->>+A: イベントウェブフック<br />"type": "connection.created"
    A-->>-S1: 200 OK
```

### リレー機能が有効だが、アフィニティ機能が無効

- アフィニティ機能による寄せが発生しない

```mermaid
sequenceDiagram
    participant C1 as クライアント1
    participant C2 as クライアント2
    participant S1 as Sora1
    participant S2 as Sora2
    participant A as アプリケーションサーバー
    C1->>S1: "type": "connect"
    S1->>+A: 認証ウェブフック
    A-->>-S1: 200 OK<br>"allowed": true<br>"cluster_affinity": false
    S1->>+A: セッションウェブフック<br />"type": "session.created"
    A-->>-S1: 200 OK
    S1->>C1: "type": "offer"
    C1->>S1: "type": "answer"
    note over C1, S1: WebRTC 接続
    S1->>+A: イベントウェブフック<br />"type": "connection.created"
    A-->>-S1: 200 OK
    C2->>S2: "type": "connect"
    S2->>+A: 認証ウェブフック
    A-->>-S2: 200 OK<br>"allowed": true<br>"cluster_affinity": false
    S2->>C2: "type": "offer"
    C2->>S2: "type": "answer"
    note over C2, S2: WebRTC 確立
    S2->>+A: イベントウェブフック<br />"type": "connection.created"
    A-->>-S2: 200 OK
```

### リレー機能が有効で接続したノードがライセンスの上限

```mermaid
sequenceDiagram
    participant C1 as クライアント1
    participant S1 as Sora1
    participant S2 as Sora2
    participant A as アプリケーションサーバー
    note over S1: ライセンス余裕なし
    C1->>S1: "type": "connect"
    S1->>+A: 認証ウェブフック
    A-->>-S1: 200 OK<br>"allowed": true<br>"cluster_affinity": false
    note over S1: ライセンス余裕なし
    S1->>C1: "type": "redirect", "location": "wss://sora2..."
    C1->>S2: "type": "connect", "redirect": true
    S2->>+A: 認証ウェブフック
    A-->>-S2: 200 OK<br>"allowed": true<br>"cluster_affinity": false
    note over S2: ライセンス余裕あり
    S2->>+A: セッションウェブフック<br />"type": "session.created"
    A-->>-S2: 200 OK
    S2->>C1: "type": "offer"
    C1->>S2: "type": "answer"
    note over C1, S2: WebRTC 確立
    S2->>+A: イベントウェブフック<br />"type": "connection.created"
    A-->>-S2: 200 OK
```

### リレー機能を利用しない場合

- 他のノードが余裕がある場合、新規セッションの場合はリダイレクトを試みる

```mermaid
sequenceDiagram
    participant C as クライアント
    participant S1 as Sora1
    participant S2 as Sora2
    participant A as アプリケーションサーバー
    C->>S1: "type": "connect"
    note over S1: このノードは同時接続が多かったので、<br>Sora2 へリダイレクト提案
    S1->>C: "type": "redirect"
    C->>S2: "type": "connect", "redirect": true
    S2->>+A: 認証ウェブフック
    A-->>-S2: 200 OK<br />{"allowed": true}
    S2->>C: "type": "offer"
    C->>S2: "type": "answer"
    note over C, A: WebRTC 確立
    S2->>+A: イベントウェブフック<br />"type": "connection.created"
    A-->>-S2: 200 OK
```
