Bản lưu ngày / Snapshot of 2026-10-19 · Xem bản mới nhất / See the latest version
WanduraDocsBeta
BetaDocs đang ở bản beta và còn thay đổi. Thấy chỗ sai, khó hiểu hoặc còn thiếu? Hãy cho chúng tôi biết.
Hướng dẫn

Phát mượt khi tải cao

Markdown
Trên trang này

TL;DR — Khi lưu lượng lên đỉnh, người xem chỉ nên thấy vòng xoay chờ một chút, không bao giờ thấy lỗi. Mỗi lần mở app chỉ nên tốn một lượt gọi catalog (rồi dùng cache của bạn) và một lượt xin vé cho mỗi tập bắt đầu xem (tập đó cộng một hai tập kế, trong một request), tất cả qua một kết nối dùng lại. Gửi viewer_ref ổn định để request lặp dùng lại vé, retry 429 và 503 bằng Retry-After, full jitter và ngân sách retry, và giữ tập hiện tại tiếp tục phát dù tập kế có chuyện gì.

Wandura làm gì khi bận

Khi Wandura
Bạn chạm rate limit playback Trả 429 kèm Retry-After, thường 1–2 giây: hạn mức được nạp lại mỗi giây.
Còn dưới khoảng 20 % hạn mức Từ chối vé prefetch trước ("reason": "prefetch_shed"); vé play vẫn được cấp bình thường.
Request play đến ngay khi vừa vượt hạn mức Giữ request tối đa khoảng 0,5 giây rồi mới trả 429.
Cùng người xem xin lại cùng tập, trên cùng thiết bị Trả lại vé đã cấp (còn ≥ 5 phút), không đếm lần nữa. Cần viewer_ref.
Không cấp kịp một vé 503 service_unavailable kèm Retry-After; trong một lô, chỉ phần tử đó lỗi.
Database của Wandura chậm Phục vụ catalog và kiểm giấy phép từ câu trả lời tốt gần nhất, cũ tối đa 5 phút.

Hai hệ quả cho code của bạn: luôn đọc Retry-After thay vì tự giả định một con số, và cho request xin vé timeout phía client ít nhất 5 giây.

Gọi catalog một lần

GET /api/delivery/v1/catalog trả cùng một danh sách cho mọi người xem của tổ chức bạn, và Wandura phục vụ nó từ cache có thể cũ tới 30 giây: hỏi dày hơn không nhận thêm gì mới. API gửi Cache-Control: no-store và không có ETag, nên không có request có điều kiện và không có 304. Việc cache là của bạn.

  • Một lần khi mở app, sau đó dùng cache của bạn. Giữ catalog trong bộ nhớ (thiết bị hoặc backend), làm mới theo hẹn giờ (15–60 phút, xem Caching), không bao giờ làm mới theo từng màn hình, từng dòng hay từng tab.
  • Chỉ một lượt gọi đang chạy. Các màn hình cùng cần catalog lúc mở app phải chờ chung một request, không mỗi màn tự gửi một cái. Mười hai lượt gọi song song tính là mười hai request catalog vào hạn mức của bạn và trả cùng một câu mười hai lần.
  • Các trang đi từng trang một, không bao giờ song song (số request song song).
  • Làm mới hỏng thì giữ bản cũ. Không bao giờ làm trống màn hình vì một lần làm mới thất bại.
  • Rải hẹn giờ: cộng ngẫu nhiên ±20 % vào chu kỳ làm mới để các máy chủ của bạn không cùng gọi vào một giây.
let inflight: Promise<Catalog> | null = null;
// refreshIfStale(): return the cached catalog if it is younger than your TTL, else GET it once.
export const getCatalog = () => (inflight ??= refreshIfStale().finally(() => (inflight = null)));

Xin vé sớm

  1. Xin theo lô, không bao giờ mỗi tập một lượt gọi. Khi người xem bắt đầu tập N, gửi một request: N với "intent": "play", cộng N+1 và N+2 dưới dạng prefetch nếu người xem được phép xem chúng. Một lượt gọi, tối đa ba vé. Hai dạng cho cùng kết quả (không trộn): episodes với 1–3 phần tử (ví dụ bên dưới), hoặc một tập neo cộng next (0–2), trả tập neo và các tập kế của cùng phim (ví dụ thứ hai bên dưới).
  2. Dùng lại vé cho đúng. Vé gắn với một thiết bị. Chỉ dùng lại cho cùng người xem, cùng thiết bị, cùng tập và cùng intent, và chỉ tới 5 phút trước expires_at. Wandura làm việc này cho bạn khi bạn gửi viewer_ref ổn định; cache ở backend theo cùng khoá cũng được. Không bao giờ dùng chung vé giữa các thiết bị.
  3. Gửi viewer_ref ổn định: HMAC của user ID cộng hash của install ID. Không bao giờ theo phiên, không bao giờ chứa dữ liệu cá nhân.
  4. Prefetch tập kế bằng prefetch. Vé của N+1 và N+2 về cùng lô trên và sống 30 phút, nên cứ giữ trong lúc người xem xem tập N. Mọi vé mà người xem chưa bấm xem phải mang "intent": "prefetch": nó không tính là lượt xem, và khi gần trần Wandura từ chối prefetch trước và vẫn cấp vé play. Khi người xem tới N+1, gửi lô kế với N+1 là play (lượt xem được đếm từ play) và N+2, N+3 là prefetch. Đừng prefetch tập mà người xem chưa được xem, và đừng chạy vòng lặp qua cả phim.
{
  "episodes": [
    { "film_ref": "the-heiress-returns", "episode_no": 4, "intent": "play" },
    { "film_ref": "the-heiress-returns", "episode_no": 5, "intent": "prefetch" },
    { "film_ref": "the-heiress-returns", "episode_no": 6, "intent": "prefetch" }
  ],
  "union_info": "<device ID from the Player SDK>",
  "drm_kek": "<device public key from the Player SDK>",
  "viewer_ref": "k3Jd9sQm2vXa7LpT0bWc1e:Zr8uYq2nM4a"
}

Cùng lô đó với tập neo cộng next: tập 4 là play, tập 5 và 6 được ghi là prefetch. Gửi next (kể cả 0) là chuyển response sang dạng lô, mỗi tập một phần tử.

{
  "film_ref": "the-heiress-returns",
  "episode_no": 4,
  "next": 2,
  "intent": "play",
  "union_info": "<device ID from the Player SDK>",
  "drm_kek": "<device public key from the Player SDK>",
  "viewer_ref": "k3Jd9sQm2vXa7LpT0bWc1e:Zr8uYq2nM4a"
}

Mỗi phần tử trong response có status riêng: một phần tử có thể 200 trong khi phần tử khác 404 not_ready (quá tập cuối), 429 (một prefetch bị tạm dừng) hoặc 503 kèm retry_after. Chỉ retry những phần tử hỏng mà bạn cần.

Retry 429 và 503 cho đúng

Các luật (status nào, Retry-After, full jitter, mức trần, timeout, ngân sách, số request song song) nằm ở chính sách retry chuẩn. Trong code:

/** How hard to try. INTERACTIVE = a viewer is waiting; BACKGROUND = a sync job. */
export type RetryPolicy = {
  maxRetries: number; // retries after the first attempt
  baseMs: number; // first backoff step
  capMs: number; // largest backoff step
  timeoutMs: number; // per attempt
  deadlineMs: number; // the whole call, retries included
};
export const INTERACTIVE: RetryPolicy = { maxRetries: 3, baseMs: 500, capMs: 8_000, timeoutMs: 8_000, deadlineMs: 15_000 };
export const BACKGROUND: RetryPolicy = { maxRetries: 5, baseMs: 1_000, capMs: 30_000, timeoutMs: 15_000, deadlineMs: 120_000 };

/** Retry only what can succeed later: network errors, 429, 500, 502 and 503 (except 503 `not_ready`). */
export function isRetryable(status: number | null, errorCode?: string): boolean {
  if (status === null) return true;
  if (status === 503) return errorCode !== 'not_ready';
  return status === 429 || status === 500 || status === 502;
}

/** Wait before retry number `attempt` (0-based): Retry-After (+ up to 1 s jitter) wins, else full jitter under the cap. */
export function backoffMs(attempt: number, retryAfterS: number | null, p: RetryPolicy, random = Math.random): number {
  if (retryAfterS !== null && retryAfterS > 0) return retryAfterS * 1_000 + random() * 1_000;
  return random() * Math.min(p.capMs, p.baseMs * 2 ** attempt);
}

/** Retries may be at most 10 % of the requests of the last minute (never fewer than 10 a minute). */
export class RetryBudget {
  private sent = 0;
  private retries = 0;
  private since = Date.now();
  private roll() {
    if (Date.now() - this.since > 60_000) { this.sent = 0; this.retries = 0; this.since = Date.now(); }
  }
  noteRequest() { this.roll(); this.sent++; }
  tryRetry(): boolean {
    this.roll();
    if (this.retries >= Math.max(10, this.sent * 0.1)) return false;
    this.retries++;
    return true;
  }
}

Endpoint backend dùng nó, với request theo lô và cách xử lý từng phần tử: Luồng playback và paywall.

Không để retry thành bão lúc mở app

  • Một ngân sách retry cho cả process. Mọi lượt gọi dùng chung một RetryBudget. Một màn hình đầy lượt gọi hỏng không được mỗi cái tự retry ba lần.
  • Một 429 tạm dừng cả endpoint đó trong process của bạn. Khi một lượt gọi nhận 429 với Retry-After: 2, giữ mọi lượt gọi khác tới endpoint đó 2 giây cộng ngẫu nhiên 0–1 giây. Retry các lượt kia ngay chỉ nhận thêm 429.
  • Chỉ retry thứ có thể thành công. Không bao giờ retry 400, 401, 403, 404, 409, 422. Một lô chỉ retry các phần tử hỏng, mỗi phần tử sau retry_after của riêng nó.
  • Không vòng lặp vô hạn. Mỗi lần retry đều chờ (exponential backoff với full jitter khi không có Retry-After) và dừng ở mức trần ở trên.

Dùng lại một kết nối

Tạo một HTTP client cho cả process và dùng nó cho mọi lượt gọi Wandura. Tạo client mới cho mỗi lượt gọi là mở một kết nối HTTPS mới mỗi lần (bắt tay TCP và TLS trước byte đầu tiên), làm mọi lượt gọi chậm đi và ăn mất timeout 5 giây. fetch có sẵn của Node đã tự dùng lại kết nối; với https thì truyền new https.Agent({ keepAlive: true }); Python dùng một requests.Session(); Kotlin một OkHttpClient; Swift một URLSession.

Wandura đo gì phía mình

Wandura giữ cửa phát vé theo một mục tiêu nội bộ, không phải cam kết hợp đồng: API đang beta (Go-live · Hỗ trợ); hỏi trong kênh của bạn để biết số hiện tại. Cách đo:

Cách đo
Availability Tỷ lệ vé được trả lời không có 5xx, theo tháng UTC. Một lô 3 tập tính 3 vé, mỗi vé có status riêng.
Latency Percentile 95 (p95) của vé và của catalog, đo bên trong Wandura, từ lúc nhận request tới lúc có câu trả lời. Không gồm mạng và không gồm lúc mở kết nối: người xem chờ con số này cộng mạng và backend của bạn. Một lô là một mẫu latency.
4xx Không bao giờ là lỗi phía Wandura: 400, 401, 403, 404 và 429 là request hoặc hạn mức của bạn, nên không tiêu hao error budget. Một cơn bão 429 có thể để số của Wandura vẫn xanh trong khi người xem của bạn thấy lỗi.
Không đếm Các lượt gọi mang header Wandura-Simulate (bạn thử màn hình lỗi).

Đo phía bạn theo cách tương tự: Observability.

Giữ người xem tiếp tục xem

  • Vòng xoay chờ, không phải lỗi. Trong lúc backend đang retry, giữ trạng thái đang tải. Chỉ hiện Retry khi đã hết ngân sách (khoảng 15 giây).
  • Tập hiện tại tiếp tục phát. Request cho tập kế bị hỏng không bao giờ làm dừng hay tải lại tập đang trên màn hình.
  • Prefetch bị từ chối không phải lỗi. Không làm gì người xem thấy được; xin với "intent": "play" khi người xem tới tập đó.
  • Không bao giờ hiện mã status thô.

Code phía app, bằng Swift và Kotlin, làm đúng như vậy: iOS, Android.

Đọc response một cách dễ tính

Bỏ qua trường lạ, coi giá trị lạ là trường hợp mặc định, và xử mã lỗi lạ theo nhóm status. Xem Versioning và API tiến hoá.

Danh sách kiểm tra

  • Mở app gửi một lượt gọi catalog; mọi lần đọc khác lấy từ cache của bạn, làm mới theo hẹn giờ và không bao giờ theo từng màn hình.
  • Bắt đầu một tập gửi một request xin vé: play cho tập đó, prefetch cho các tập kế đã mở khoá. Không bao giờ mỗi tập một request.
  • Vé chỉ được dùng lại theo người xem, thiết bị, tập và intent, không bao giờ dùng chéo thiết bị, tới 5 phút trước expires_at.
  • viewer_ref ổn định theo người xem và thiết bị, và không chứa dữ liệu cá nhân.
  • 429 và 503 được retry sau Retry-After cộng jitter; nếu không có thì exponential backoff với full jitter.
  • Tối đa 3 lần retry cho mỗi request tương tác, và ngân sách retry 10 % mỗi phút.
  • Một 429 tạm dừng mọi lượt gọi tới endpoint đó trong process của bạn tới khi hết Retry-After cộng jitter.
  • Một HTTP client dùng chung có keep-alive cho cả process.
  • Request xin vé có timeout phía client ít nhất 5 giây.
  • Prefetch bị từ chối không có tác động nhìn thấy được, và không bao giờ làm dừng tập hiện tại.

Cập nhật 13/10/2026 · Lịch sử thay đổi (4)

↑↓ chọnEnter mởEsc đóng

Gửi góp ý

Góp ý được gửi thẳng tới đội Wandura, kèm địa chỉ trang này.

0 / 4000