# ユーザー名ごとのセッションクォータ

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

- セッションカウンターはユーザー名ごとに管理され、クラスター全体で同期されます。
- 設定されたクォータに達すると、認証は `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`。フィルターはカーソルに固定されています。
- `used_gte` も `cursor` もなし：**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`

指定ユーザー名のすべてのセッションをキックします。キックしたセッション数Nを含む `{"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秒以内にレプリケーションが完了しない場合、エラーをログに記録し、`error` レベルログとともにブートストラップを中止します。
  タイムアウト前に登録されたセッションは保持され、残りは再接続時のフック登録で自然に拾われます。

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

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

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

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

- 即時に利用可能な部分結果があれば `data` を確認してください。
- バウンデッドバックオフで再試行してください。

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

## ダウンロード

各EMQXリリース用のtarball：

| EMQXバージョン | プラグインバージョン | パッケージ |
|---|---|---|
| 6.1.1 | 1.0.0 | [emqx_username_quota-1.0.0.tar.gz](https://packages.emqx.io/emqx-plugins/6.1.1/emqx_username_quota-1.0.0.tar.gz) |
| 6.1.2 | 1.2.1 | [emqx_username_quota-1.2.1.tar.gz](https://packages.emqx.io/emqx-plugins/6.1.2/emqx_username_quota-1.2.1.tar.gz) |
| 6.1.3 | 1.2.2 | [emqx_username_quota-1.2.2.tar.gz](https://packages.emqx.io/emqx-plugins/6.1.3/emqx_username_quota-1.2.2.tar.gz) |

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