# Hàng chờ và nghẽn

> **Tóm tắt** — Câu trả lời submit cho biết bao nhiêu run của bạn đang chạy, bao nhiêu đang chờ, và giới hạn của bạn. `GET /v2/queue` cho xem các số đó ngay lúc này. `GET /v2/queue/log` trả các số theo từng phút hoặc từng giờ, `GET /v2/queue/refusals` liệt kê các lần submit bị từ chối, và `GET /v2/queue/episodes` liệt kê các đợt nghẽn. Đăng ký `queue.congestion_started` và `queue.congestion_ended` để chuyển sang phương án dự phòng (fallback) sớm.

Bạn chỉ thấy số của chính product mình. Key test và key live tính riêng.

## Từ ngữ
| Từ | Nghĩa |
|---|---|
| Suất chạy (concurrency slots) | Số run của product chạy cùng lúc. Mặc định 20; Wandura nâng theo từng product khi có yêu cầu. |
| Hàng chờ (queue) | Các run priority đã được nhận và đang chờ suất chạy. Hàng chờ chứa tối đa bằng số suất chạy của bạn. |
| Bị từ chối (refusal) | Một lần submit bị trả `429` vì product đã chạm giới hạn. Lý do là `product_busy` (hết suất chạy và hàng chờ đầy) hoặc `rate_limited` (vượt số run mỗi phút, trả `gateway_rate_limited`). Không có run nào được tạo, không tính tiền. |
| Đợt nghẽn (congestion episode) | Khoảng thời gian product ở một loại nghẽn: `refusing`, `near_full` hoặc `slow`. Có lúc bắt đầu và lúc hết. |

Giới hạn hằng ngày theo end user (`end_user_cap_reached`) không phải là bị từ chối: nó nói về một người, không phải về product của bạn.

## Header trên mỗi lần submit
Câu trả lời của `POST /v2/styles/{style}`, `POST /v2/runs` và `POST /v2/jobs` có ba header: dù submit được nhận, được phát lại (replay) hay bị từ chối.

| Header | Nghĩa |
|---|---|
| `X-Concurrency-Limit` | Số suất chạy của bạn. Hàng chờ chứa thêm đúng bằng số đó. |
| `X-Concurrency-Running` | Số run của product đang dùng một suất chạy lúc này. |
| `X-Concurrency-Waiting` | Số run priority của product đang chờ suất chạy. |

```http
HTTP/1.1 202
X-Concurrency-Limit: 20
X-Concurrency-Running: 20
X-Concurrency-Waiting: 6
```

Khi Running và Waiting cùng chạm Limit, run priority mới bị từ chối: `429 product_busy` (Jobs API: `PRODUCT_BUSY`) kèm `Retry-After: 5`. Run Relax không bao giờ bị từ chối vì lý do này; chúng chờ.

- Submit bị trả lỗi trước khi xét tới giới hạn (body sai, style không có, key sai) thì không có các header này.
- Coi các header là không bắt buộc: nếu tạm thời không đọc được số, câu trả lời sẽ không kèm chúng.

## Hàng chờ lúc này
`GET /v2/queue` trả số của product ngay lúc này, theo product và chế độ (test/live) của key.

```bash
curl https://api.wandura.ai/v2/queue -H "Authorization: Bearer $WANDURA_KEY"
```

```json
{
  "object": "queue_state",
  "product": "prod_…",
  "livemode": true,
  "concurrency_limit": 20,
  "running": 20,
  "waiting": 6,
  "queue_limit": 20,
  "accepted_this_minute": 41,
  "per_minute_limit": 60,
  "refusals": { "last_15m": 0, "last_24h": 3 },
  "at": "2026-10-15T09:30:12.000Z"
}
```

| Field | Nghĩa |
|---|---|
| `concurrency_limit` | Số suất chạy của bạn. |
| `running` | Số run đang dùng suất chạy. |
| `waiting` | Số run priority đang chờ suất chạy. |
| `queue_limit` | Số run priority được phép chờ. Bằng số suất chạy. |
| `per_minute_limit` | Số run mỗi phút cho gateway key và Jobs API. Vượt mức: `429 gateway_rate_limited` (Jobs API: `RATE_LIMITED`). |
| `accepted_this_minute` | Số run đã tính vào giới hạn đó từ đầu phút UTC hiện tại. |
| `refusals` | Số lần submit bị từ chối trong 15 phút và 24 giờ vừa qua. |

## Nhật ký hàng chờ
`GET /v2/queue/log` trả các số theo thời gian, mỗi ô (bucket) là một phút hoặc một giờ, cũ nhất trước.

| Tham số | Nghĩa |
|---|---|
| `granularity` | `minute` (mặc định) hoặc `hour`. |
| `from` | Lúc bắt đầu, có tính. Một thời điểm nằm trong một ô thì tính cả ô đó. Mặc định: 24 giờ trước `to`. |
| `to` | Lúc kết thúc, không tính. Mặc định: bây giờ. |

- Một khoảng dài tối đa 7 ngày nếu theo phút, 90 ngày nếu theo giờ. Ô phút được giữ 7 ngày, ô giờ 90 ngày.
- Ô không có hoạt động thì bị bỏ ra.
- Một phút hiện ra khoảng một phút sau khi nó kết thúc. Giờ hiện tại được điền dần.
- Giá trị sai trả `400 invalid_request`, thông điệp nói rõ giá trị nào.

```bash
curl "https://api.wandura.ai/v2/queue/log?granularity=minute&from=2026-10-15T09:00:00Z" \
  -H "Authorization: Bearer $WANDURA_KEY"
```

```json
{
  "object": "list",
  "product": "prod_…",
  "livemode": true,
  "granularity": "minute",
  "from": "2026-10-15T09:00:00.000Z",
  "to": "2026-10-15T09:30:00.000Z",
  "data": [
    {
      "object": "queue_log_bucket",
      "bucket": "2026-10-15T09:29:00.000Z",
      "accepted": 38,
      "refusals": { "product_busy": 2, "rate_limited": 0 },
      "max_running": 20,
      "max_waiting": 20,
      "concurrency_limit": 20,
      "ended": 17,
      "median_wait_ms": 41000,
      "median_duration_ms": 96000
    }
  ]
}
```

| Field | Nghĩa |
|---|---|
| `bucket` | Đầu phút hoặc đầu giờ (UTC). |
| `accepted` | Số run được nhận. |
| `refusals` | Số lần submit bị từ chối, theo lý do. |
| `max_running` | Số run dùng suất chạy cùng lúc nhiều nhất. |
| `max_waiting` | Số run priority chờ suất chạy cùng lúc nhiều nhất. |
| `concurrency_limit` | Số suất chạy của bạn. |
| `ended` | Số run kết thúc sau khi đã chạy. Run bị huỷ khi còn queued không tính. |
| `median_wait_ms` | Trung vị thời gian chờ từ lúc submit tới lúc bắt đầu, của các run priority bắt đầu trong ô. |
| `median_duration_ms` | Trung vị thời gian chạy, từ lúc bắt đầu tới lúc kết thúc (không tính lúc chờ), của các run kết thúc trong ô. |

## Bị từ chối
`GET /v2/queue/refusals` liệt kê các lần submit bị từ chối, mới nhất trước. Được giữ 30 ngày.

| Tham số | Nghĩa |
|---|---|
| `limit` | Từ 1 tới 200. Mặc định 50. |
| `before` | `id` của mục cuối ở trang trước. |

```json
{
  "object": "list",
  "data": [
    {
      "object": "queue_refusal",
      "id": "5821",
      "at": "2026-10-15T09:29:41.204Z",
      "reason": "product_busy",
      "style": "sty_…",
      "end_user_id": "user_42",
      "retry_after_s": 5,
      "livemode": true
    }
  ],
  "has_more": false
}
```

`reason` là `product_busy` (hết suất chạy và hàng chờ đầy) hoặc `rate_limited` (vượt số run mỗi phút). `end_user_id` là `null` khi lệnh gọi không gửi. Dùng nó để biết người dùng nào của bạn bị ảnh hưởng.

## Đợt nghẽn
Wandura kiểm product của bạn mỗi phút. Mỗi loại nghẽn mở một đợt riêng, nên nhiều đợt có thể cùng mở.

| `kind` | Bắt đầu khi | Hết khi |
|---|---|---|
| `refusing` | Có một lần bị từ chối trở lên trong 5 phút vừa qua. | 10 phút không có lần bị từ chối nào. |
| `near_full` | Dùng từ 80% số suất chạy trở lên trong 1 phút. | Dưới 60% trong 5 phút. |
| `slow` | Ít nhất 3 run kết thúc trong 15 phút vừa qua, và trung vị thời gian chạy của chúng trên 10 phút. | Trung vị về lại dưới 8 phút, hoặc 15 phút không run nào kết thúc và không run nào đang chạy hay đang chờ. |

- Cùng một loại không bắt đầu lại trong 30 phút sau khi hết.
- Chỉ key live: key test không bao giờ mở đợt nghẽn.
- Thời gian chạy tính từ lúc bắt đầu tới lúc kết thúc. Không tính lúc chờ trước đó.

`GET /v2/queue/episodes` liệt kê các đợt nghẽn, mới nhất trước. Nhận cùng `limit` và `before` như danh sách bị từ chối.

```json
{
  "object": "list",
  "data": [
    {
      "object": "congestion_episode",
      "id": "184",
      "product": "prod_…",
      "livemode": true,
      "kind": "refusing",
      "status": "ended",
      "started_at": "2026-10-15T09:31:00.000Z",
      "ended_at": "2026-10-15T09:52:00.000Z",
      "duration_s": 1260,
      "start": { "running": 20, "waiting": 20, "concurrency_limit": 20, "refusals_5m": 3, "refusals_10m": 3, "runs_ended_15m": 24, "median_duration_ms": 95000 },
      "end": { "running": 9, "waiting": 0, "concurrency_limit": 20, "refusals_5m": 0, "refusals_10m": 0, "runs_ended_15m": 30, "median_duration_ms": 88000 },
      "affected": { "refusals": 7, "runs_accepted": 212, "runs_ended": 205 }
    }
  ],
  "has_more": false
}
```

| Field | Nghĩa |
|---|---|
| `status` | `open` hoặc `ended`. |
| `started_at`, `ended_at`, `duration_s` | Lúc bắt đầu và lúc hết. Hai field sau là `null` khi đợt còn mở. |
| `start`, `end` | Số của bạn lúc bắt đầu và lúc hết (`end` là `null` khi còn mở): `running`, `waiting`, `concurrency_limit`, số lần bị từ chối trong 5 và 10 phút vừa qua, số run kết thúc trong 15 phút vừa qua và trung vị thời gian chạy của chúng (`null` khi không run nào kết thúc). |
| `affected` | Khi đã hết: số lần bị từ chối (tính từ 5 phút trước lúc bắt đầu), số run được nhận và số run kết thúc trong đợt. `null` khi còn mở. |

## Event webhook
Mỗi đợt nghẽn được gửi hai lần tới webhook của product:

- `queue.congestion_started` khi đợt bắt đầu.
- `queue.congestion_ended` khi đợt hết.

Đây là event phải tự đăng ký (opt-in): webhook của bạn chỉ nhận khi danh sách event của nó có tên hai event này. Muốn bật, hãy nhờ Wandura thêm cả hai event vào webhook của product. Các event khác của bạn giữ nguyên.

`data` là đợt nghẽn đúng như lúc event được tạo. Không có `sequence`, vì event không thuộc run nào. Chữ ký, gửi lại, `Wandura-Event-Id` và `Wandura-Event-Type` giống event của run: xem [Webhook](https://docs.wandura.ai/vi/developers/api-v2/concepts#webhooks).

```json
{
  "id": "evt_…",
  "object": "event",
  "type": "queue.congestion_started",
  "created_at": "2026-10-15T09:31:00.412Z",
  "data": {
    "object": "congestion_episode",
    "id": "184",
    "product": "prod_…",
    "livemode": true,
    "kind": "refusing",
    "status": "open",
    "started_at": "2026-10-15T09:31:00.000Z",
    "ended_at": null,
    "duration_s": null,
    "start": { "running": 20, "waiting": 20, "concurrency_limit": 20, "refusals_5m": 3, "refusals_10m": 3, "runs_ended_15m": 24, "median_duration_ms": 95000 },
    "end": null,
    "affected": null
  }
}
```

- Ghép lúc bắt đầu với lúc hết theo `data.id`. Hiếm khi, event hết tới trước event bắt đầu: hãy tin `data.status`.
- Event là tín hiệu nhanh. Muốn có sổ đầy đủ, đọc `GET /v2/queue/episodes`.

## Nên làm gì
- **Theo dõi header.** Khi `X-Concurrency-Waiting` tăng dần tới `X-Concurrency-Limit`, hãy gửi chậm lại hoặc đưa việc mới sang fallback trước khi bị từ chối.
- **Làm theo event.** Khi nhận `queue.congestion_started`, chuyển sang fallback: ngay lập tức với `refusing`, sớm với `near_full`, còn với `slow` thì báo người dùng hoặc dùng đường nhanh hơn. Khi nhận `queue.congestion_ended` cùng loại, chuyển về lại.
- **Khi gặp `429`** (`product_busy`, `gateway_rate_limited`; Jobs API `PRODUCT_BUSY`, `RATE_LIMITED`): không có run nào được tạo, nên gửi request đó sang fallback không thể chạy hai lần. Muốn thử lại với Wandura, chờ đúng `Retry-After` giây (cộng thêm chút jitter) rồi gửi lại với **cùng** `Idempotency-Key`.
- **Khi gặp `503 service_unavailable`** (Jobs API `SERVICE_UNAVAILABLE`): không có gì được tạo hay tính tiền. Thử lại sau `Retry-After` với cùng `Idempotency-Key`.
- **Poll thong thả.** Khi product đang nghẽn, run chờ lâu hơn. Poll mỗi run 5–10 giây một lần, hoặc tốt hơn, dùng webhook. Poll nhanh hơn không làm run bắt đầu sớm hơn.
- **Cần thêm chỗ?** Nhật ký hàng chờ và danh sách bị từ chối cho thấy bạn chạm giới hạn khi nào và bao nhiêu lần. Gửi chúng cho Wandura để được nâng số suất chạy hoặc số run mỗi phút của product.

Khi chính Wandura gặp sự cố, thông báo nằm ở [wandura.instatus.com](https://wandura.instatus.com).

Xem thêm: [Lỗi](https://docs.wandura.ai/vi/developers/api-v2/errors) · [Khái niệm: giới hạn](https://docs.wandura.ai/vi/developers/api-v2/concepts#limits) · [Jobs API: giới hạn](https://docs.wandura.ai/vi/developers/api-v2/jobs#limits).
