# ユーザー名別セッションクォータ

このプラグインはユーザー名ごとのセッションクォータを強制します。

- セッションカウンターはユーザー名ごとに管理され、クラスター全体で同期されます。
- 設定されたクォータに達した場合、認証は `quota_exceeded` で拒否されます。
- 既存の `clientid` での再接続は追加のクォータを消費しません。
- ユーザー名別のクォータオーバーライドにより、カスタム制限、無制限セッション、または接続ブロックが可能です。

:::: tip 注意
`client_attrs_init` 設定の `client_attrs.tns` を使用してユーザー名をネームスペースとして利用できる場合は、ネームスペースベースの制限でこの種のセッション制限を実施できます。
ネームスペースの割り当てが異なるスキームに従う場合にのみ、このプラグインを使用してください。
::::

## 設定

| フィールド | デフォルト | バリデーション | 説明 |
|-------|---------|------------|-------------|
| `max_sessions_per_username` | `100` | 正の整数（`>= 1`）である必要があります。1未満または数値以外の値は拒否されます。 | ユーザー名ごとのデフォルト最大同時セッション数。個別のユーザー名はオーバーライドAPIを通じてこの値を上書きできます。 |
| `snapshot_min_age_ms` | `300000` | `120000` から `900000` の範囲内である必要があります。範囲外の値はクランプされます。 | スナップショットの最小有効期間（ミリ秒）。これにより大規模クラスターでの頻繁な再構築を防ぎます。 |
| `snapshot_request_timeout_ms` | `5000` | 正の整数に変換可能な文字列も受け入れます。 | リストAPIのスナップショット要求処理のタイムアウト時間（ミリ秒）。 |

プラグイン設定は標準プラグイン設定APIで更新します：

`PUT /api/v5/plugins/<name-vsn>/config`

## ランタイムAPI

プラグインはプラグインAPIゲートウェイを通じてランタイムAPIを公開します。

ベースパス：`/api/v5/plugin_api/emqx_username_quota`

### セッション照会

- `GET /quota/usernames`: アクティブなセッションを持つすべてのユーザー名を一覧表示します。
- `GET /quota/usernames/:username`: 単一ユーザー名の詳細を取得します。
- `GET /metrics`: プラグインのメトリクスをPrometheusテキスト形式でエクスポートします。
- `POST /kick/:username`: 指定ユーザー名のすべてのセッションをキックします。

### スナップショット管理

- `DELETE /quota/snapshot`: スナップショットの強制再構築を行います。

### クォータオーバーライド

- `POST /quota/overrides`: ユーザー名ごとのクォータオーバーライドを設定します。
- `DELETE /quota/overrides`: ユーザー名ごとのクォータオーバーライドを削除します。
- `GET /quota/overrides`: すべてのクォータオーバーライドを一覧表示します。

### `GET /quota/usernames`

このエンドポイントは、毎回ライブのセッションデータをスキャンするのではなく、事前に構築されたスナップショットから結果を返します。

スナップショットとは、ユーザー名ごとのセッション数の時点コピーであり、効率的なカーソルベースのページネーションのためにセッション数でソートされています。スナップショットはバックグラウンドで非同期に構築され、キャッシュされます。現在のスナップショットが `snapshot_min_age_ms` より古い場合にのみ新しい構築がトリガーされます。

最初のリクエスト時にスナップショットが存在しない場合、サーバーは進行中の構築完了を待ちます。待機時間はリクエストのデッドラインから1秒を引いた時間までです。構築が間に合えば通常の `200` レスポンスを返し、間に合わなければ部分データ付きの `503` を返します。

クエリパラメータ：

- `limit`: 正の整数、最大100（デフォルト100）
- `used_gte`: **カーソルがない場合は必須**。これは最小セッション数フィルターです。この数以上のセッションを持つユーザー名のみが含まれます。正の整数（`>= 1`）である必要があります。
- `cursor`: オプションの不透明なカーソル。前回のリスト呼び出しで返されます。指定がなければ最初のページが返されます。

パラメータルール：

- `used_gte` のみ（`cursor`なし）：OK（最初のページ）
- `cursor` のみ（`used_gte`なし）：OK（`used_gte`はカーソルに埋め込まれている）
- `used_gte` と `cursor` の両方：**400** `BAD_REQUEST`。フィルターはカーソルに固定されているため。
- どちらもない：**400** `BAD_REQUEST`

動作：

- 結果は常にセッション数、次にユーザー名でソートされます。
- ページネーションはカーソルベースです。最初のページでは `cursor` を省略します。
- 各アイテムには `username`、リアルタイムの `used`、および `limit`（有効なクォータ）が含まれます。
- リアルタイムの `used` がスナップショットのカウントと異なる場合、呼び出し元がキャッシュされた値と現在の値の両方を確認できるように `snapshot_used` が含まれます。

成功時のレスポンス構造：

- `data`: ユーザー名クォータのエントリ
- `meta.limit`: ページサイズ（ページネーション制限）
- `meta.count`: このページのエントリ数
- `meta.total`: スナップショット内の総エントリ数
- `meta.next_cursor`: 次ページ用カーソル（存在する場合）
- `meta.snapshot`: スナップショットのメタデータ：
  - `node`
  - `generation`（インクリメンタルなスナップショットID）
  - `taken_at_ms`（スナップショットのタイムスタンプ、ミリ秒）

エラー応答：

- `400 BAD_REQUEST`: `used_gte` がない、または `used_gte` と `cursor` が同時に指定された場合
- `400 INVALID_CURSOR`: カーソルが存在しないノードを参照しているか、形式が不正
- `503 SERVICE_UNAVAILABLE`: スナップショットが再構築中
  - ボディには `snapshot_build_in_progress: true`、`data`、`meta` が含まれる
  - `data`: 進行中のスナップショットから読み取った部分的な最初のページ（構築開始直後の場合は空の場合あり）
  - `meta.count`: 部分的なエントリ数、`meta.partial: true`
  - 制限付きバックオフで同じリクエストを再試行してください

### `DELETE /quota/snapshot`

即時にスナップショットの再構築を強制します。再構築を非同期に開始した後、`{"status": "ok"}` を伴う `200` を返します。スナップショットはバックグラウンドで再構築されます。

### `GET /quota/usernames/:username`

単一ユーザー名の詳細を返します。レスポンスフィールドは `username`、`used`、`limit`、`clientids` です。

ユーザー名にアクティブなセッションがない場合は `404 NOT_FOUND` を返します。

### `GET /metrics`

プラグインのPrometheusテキスト形式メトリクスを返します。レプリカントノードではリクエストがスナップショット所有のコアノードに転送されます。

現在エクスポートされているメトリクス：

- `emqx_username_count`: アクティブスナップショット内のユーザー名総数

### `POST /kick/:username`

指定ユーザー名のすべてのセッションをキックします。キックしたセッション数を `{"kicked": N}` で返します。

ユーザー名にアクティブなセッションがない場合は `404 NOT_FOUND` を返します。

### `POST /quota/overrides`

ユーザー名ごとのクォータオーバーライドを設定します。ボディはJSON配列です：

```json
[
  {"username": "user1", "quota": 1000},
  {"username": "vip", "quota": "nolimit"},
  {"username": "blocked", "quota": 0}
]
```

オーバーライドの意味：

| `quota` の値    | 意味                                        |
|------------------|------------------------------------------------|
| 正の整数         | このユーザー名のカスタムセッション制限         |
| `"nolimit"`      | 無制限セッション（クォータ未適用）              |
| `0`              | 接続禁止：すべての新規接続を拒否                |

オーバーライドはディスクに永続化され、クラスター全体にレプリケートされます。ユーザー名にオーバーライドがない場合は、グローバル設定の `max_sessions_per_username` が使用されます。

### `DELETE /quota/overrides`

ユーザー名でオーバーライドを削除します。ボディはユーザー名文字列のJSON配列です：

```json
["user1", "blocked"]
```

### `GET /quota/overrides`

すべてのオーバーライドを一覧表示します。`{"data": [{"username": "...", "quota": ...}, ...]}` を返します。

## スナップショットベースの一覧処理の仕組み

このセクションでは、`GET /quota/usernames` や `GET /metrics` のようなリスト形式APIのためにプラグインがスナップショットをどのように構築し提供しているかを説明します。

### スナップショット所有ノードのルーティング

スナップショットはコアノード上で構築されます。`GET /quota/usernames` と `GET /metrics` は、ソートされた稼働中のコアノードリストの最初のノードとして選択されたスナップショット所有コアノードにルーティングされます。

### ブルー/グリーンスナップショット

2つのスナップショットバッファ（ブルーとグリーン）を維持します。1つが読み取りリクエストに応答している間、もう1つは次のスナップショット構築に使用されます。構築完了後に役割を入れ替えます。これにより、新しいスナップショットが準備できるまで古いスナップショットが利用可能なため、再構築中のデータギャップがなくなります。

### バックグラウンドスナップショット構築

スナップショットの再構築はバックグラウンドプロセスで行われ、サーバーのブロックを避けるためにイールドベースのスロットリングが適用されます。構築中もリストAPIは応答可能です。

## 運用上の考慮事項と制限

このセクションでは、本プラグインを本番運用する際に考慮すべきランタイム動作や制限について説明します。

### 接続バースト時のクォータ超過

クォータの判断は認証時に行われ、セッションカウンターの最終確定はセッションライフサイクルフックで行われます。高い同時接続バースト（特にクラスター環境）では、同期の短いウィンドウが生じ、1つのユーザー名の同時セッション数が一時的に `max_sessions_per_username` を超える可能性があります。

実務上の意味：

- 本プラグインはバースト負荷下で最終的整合性を持つクラスター全体のクォータ強制を提供します。
- 極端な接続集中時に、瞬間的な厳密な接続ごとのクォータ強制は保証しません。

### プラグイン起動時のブートストラップ動作

プラグインが稼働中のクラスターにインストールされた場合、既存のクライアントセッションはフック登録前に確立されています。起動時にプラグインはローカルのすべてのチャネルを巡回して各セッションを登録し、クォータ状態をブートストラップします。

コアノードのDB書き込み負荷を避けるため（特にレプリカントノードに多数の既存接続がある場合）、ブートストラップループはスロットリングされます：

- セッションは100件ずつバッチ登録されます。
- 各バッチ後、最後に書き込まれたレコードのローカルテーブルへのレプリケーション完了を待ちます。ポーリング間隔は10msです。
- 10秒以内にレプリケーションが完了しない場合、エラーログを出力してブートストラップを中止します。タイムアウト前に登録されたセッションは保持され、残りは再接続時のフック登録で自然に拾われます。

### リストAPIの `503` 応答の取り扱い

サーバーがビジー状態かスナップショット構築中の場合、リストAPIは `503` を返します。

`503` レスポンスボディには、進行中のスナップショットテーブルから読み取った部分的な最初のページの `data` 配列が含まれます。これにより呼び出し元は空レスポンスではなく即時にベストエフォートのデータを取得できます。`meta.partial: true` フラグはデータが不完全であることを示します。構築開始直後の場合は部分ページが空の場合もあります。

APIクライアントへのガイダンス：

- 可能な限り `data` を確認して部分的な結果を利用してください。
- 制限付きバックオフで再試行してください。

<!-- PLUGIN-DOWNLOADS:BEGIN (auto-generated, do not edit) -->

## ダウンロード

各EMQXリリースのtarball：

| EMQXバージョン | プラグインバージョン | パッケージ |
|---|---|---|
| 6.3.0 | 1.2.3 | [emqx_username_quota-1.2.3.tar.gz](https://www.emqx.com/downloads/emqx-plugins/6.3.0/emqx_username_quota-1.2.3.tar.gz) ([sha256](https://www.emqx.com/downloads/emqx-plugins/6.3.0/emqx_username_quota-1.2.3.sha256)) |

<!-- PLUGIN-DOWNLOADS:END -->
