阿晴把一支十分鐘的料理影片拖進瀏覽器。進度到 83% 時,家裡的網路斷了。重新連線後,她希望繼續上傳;上傳完成後,她把連結傳給朋友,對方卻看到「影片處理中」。隔天有人分享,觀看人數突然增加;再隔天,她發現畫面拍到了不該公開的資訊,按下刪除。

對阿晴來說,她只是上傳、分享,再刪掉一支影片。平台卻得一路回答:檔案收到哪裡了?哪個版本可以播放?誰有權公開?內容進了快取以後,還能在多久內停止存取?我沿著這些問題設計一個隨選影音平台,從第一版的容量與資料模型,走到轉碼重試、熱門流量與下架。

我會先選擇基本畫質完成就能發布、高畫質後補的方案。接下來的設計都要守住這個承諾:觀眾拿到的版本必須完整可播,而較晚完成的工作不能蓋過創作者後來的決定。直播、廣告競價、版權辨識模型與個人化推薦模型留待後續;它們接入時,也要遵守同一套發布與授權規則。

1. 上傳完成,只代表創作者交出了檔案

阿晴看到進度條 100%,系統就顯示「發布成功」,是最容易做出的第一版。直到第一位觀眾打開影片,播放器才發現還沒有能用的清單,或者只有一個瀏覽器不支援的原始檔。

這裡要把完成條件分開:

  • 上傳完成:平台已收到預期的原始物件,大小與完整性驗證符合本次工作階段的約定。創作者可以關掉分頁,後續處理由平台負責。
  • 可播放:至少一組支援的影音產物與清單已準備好,且經過檢查。
  • 公開:創作者選擇公開、內容政策允許,而且目前可播放的版本通過發布交易。

接收完成、可播放與公開,是三份不同的承諾。 UI 可以顯示「檔案已收到,正在準備播放」,不能為了消除等待感,把三份承諾壓成一個成功訊息。

從創作者上傳到觀眾播放與影片下架的雙角色旅程 創作者與觀眾沿著收到原檔、允許發布、播放開始、下架生效四個里程碑,取得不同的承諾。上傳完成不代表可播放,停止授權也不能收回已下載內容。同一支影片,兩種不同的承諾創作者在等「處理完成」;觀眾只在意「現在能不能看」。創作者觀眾01 · 收到原檔上傳與續傳斷線只補缺失部分看不到成品原檔收完 ≠ 可播放02 · 允許發布確認發布最低可播組合已驗證依可見性進入公開/未公開/私密03 · 播放開始觀察處理結果高畫質可稍後補齊取清單與分段授權後由 CDN 傳輸04 · 下架生效提出下架立即停止新的授權後續請求受限制已下載內容無法收回每一個「完成」,都要有可驗證的邊界。收檔成功、資產就緒、可公開、可播放與已刪除,不能共用同一個模糊狀態。
  1. 01 · 收到原檔創作者:斷線後接著上傳

    觀眾:仍看不到成品。檔案收完不等於可播放。

  2. 02 · 允許發布創作者:最低可播組合已就緒

    觀眾:依公開、未公開或私密的規則取得存取資格。

  3. 03 · 播放開始觀眾:授權後取得清單與分段

    影音位元組由 CDN 傳輸;創作者可以繼續等待高畫質版本。

  4. 04 · 下架生效立即停止發放新的播放授權

    搜尋、推薦與快取繼續收斂;已下載的內容無法收回。

收檔成功、資產就緒、可公開與可播放,是不同的承諾。

圖 1|用同一支影片串起產品契約;本圖是設計推演,不是 YouTube 內部流程。

這項區分會影響 API。建立影片回傳 video ID,不代表觀眾能播放。完成上傳回傳 receipt,不代表轉碼成功。創作者按發布,可以得到「已記錄公開意圖,等待基本畫質完成」。瀏覽器不必一直等到所有畫質處理完。

我選擇「先完成基本播放組合,再允許公開;較高畫質後補」的產品政策。基本組合包含一個可用的影片 rendition、需要的音訊、海報與經驗證的播放清單。實際產品可以要求所有畫質完成才公開,代價是增加等待時間。也可以開放原檔下載,但那是另一種交付契約,不能把下載連結當成串流播放完成。

影片如果被政策審查擋住,原檔依然可能已收到、轉碼也可能成功。創作者後台要能解釋「目前不能公開的原因」。只顯示 failed,會讓她以為又要上傳一次。

2. 分開估算容量,讓影片傳輸離開業務 API

假設產品第一階段有每日 1,000 支上傳,每支平均 500 MB、長度十分鐘;觀眾每日觀看 100,000 次,每次平均觀看五分鐘,平均實際傳輸位元率為 3 Mbps。以下採十進位 MB、GB、TB,忽略協定與容器額外開銷。估算先看哪些參數會左右儲存、頻寬與處理需求。

上傳原始資料是 1,000 × 500 MB = 500 GB/日。若原檔保留三十天,尚未加副本、備份與失敗殘留前,就是 15 TB。假設各種播放產物合計為原檔的 1.5 倍,三十天再增加 22.5 TB。這個倍率取決於內容、編碼設定與畫質組合,不能拿來當廠商保證;原檔保留策略改變,帳單也會跟著改變。

觀看資料約為 100,000 × 300 秒 × 3 Mb/s ÷ 8 = 11.25 TB/日,平均下行約 1.04 Gbps。若尖峰是平均的十倍,規劃情境約為 10.4 Gbps。熱門影片可能讓十分鐘內的負載比這個倍數更高,所以平均值只能用來估成本,不能直接當准入上限。

處理工作量又是另一回事。每天 10,000 分鐘的來源影片,如果「產出整組基本畫質」的實測成本是每分鐘來源需要四個 CPU 分鐘,便是每天 40,000 CPU 分鐘,約 27.8 個持續忙碌的 CPU 核心。四倍只是待驗證參數;codec、preset、解析度、硬體與內容運動量都會改變結果。排程還要加上尖峰、失敗重跑、機器維護與等待時間預算。

觀看傳輸量遠高於上傳量,轉碼又需要獨立的 CPU 預算。因此,應用伺服器不代理整段影片位元組,讓客戶端直接向受限的物件儲存入口上傳,播放由 CDN 分發。API 負責許可、狀態與收據。若讓每個影片片段都穿過 PHP 或 Node 業務程序,原本用來處理帳號與 metadata 的資源會被大量傳輸占住。

可驗收目標必須說明測量起點

示例目標可以寫成:受支援、十分鐘內且 1080p 以下的合格來源,從平台確認上傳完成到基本畫質可播放,95% 在十分鐘內;超過規格的來源不混進同一個分母。播放啟動則從使用者按下播放,到第一幀實際呈現,按裝置與網路類型分組觀測。把伺服器回傳 manifest 的 200 當成第一幀,就量錯了使用者感受。

卡頓比例也要說清楚:我建議記錄播放期間因緩衝停住的秒數與有效觀看秒數,另外記錄啟動失敗率。暫停、切換背景與使用者主動拖曳不能全算成系統卡頓。完整的 SLO 定義方式 與 容量預算 可以另讀;在這個產品裡,我們要用量測結果決定加轉碼機,還是先修播放器與快取。

3. 最小架構可以是模組化單體,加上獨立的轉碼工作者

第一版的 API 應用可以用 Laravel 或 Node,將帳號、影片目錄、上傳工作階段、發布與播放許可分成模組,共用一個 MySQL 寫入來源。轉碼工作者因為資源需求與處理時間不同,獨立執行。物件儲存收原始檔與版本化產物,佇列傳遞工作,CDN 服務觀看流量。

模組化單體、MySQL 與獨立影音傳輸的最小架構 控制路徑由模組化單體 API 與 MySQL 負責。已提交的 outbox 事件經 relay 和持久佇列交給 worker。創作者直接上傳物件儲存,轉碼產物經 CDN 到播放器,影音不穿過 API。第一版:模組化單體管理規則,影音走獨立資料路徑控制路徑 · 小資料、交易與權限創作者/播放器建立上傳、改可見性請求播放授權模組化單體 APIUpload / Video / Playback 模組先共用部署,保留明確責任MySQL影片、資產與工作狀態同一交易寫入 outbox背景路徑 · 允許重試,不能重複發布Outbox relay讀取已提交事件,再送出持久工作佇列攜帶 job ID;允許重送轉碼 worker版本化產物;完成後條件提交影音位元組路徑 · API 不代理大檔傳輸創作者直接分塊上傳物件儲存原檔 / 不可變版本產物CDN授權+快取播放器分段下載
  1. 控制路徑客戶端 → 模組化單體 API → MySQL

    Upload、Video、Playback 模組先共用部署。影片狀態與 outbox 事件在同一交易提交。

  2. 背景路徑Outbox relay → 持久佇列 → worker

    事件可重送,工作可重試;以 job ID、產物版本與條件提交守住發布邊界。

  3. 影音位元組路徑創作者 → 物件儲存 → CDN → 播放器

    創作者直接分塊上傳;worker 讀原檔、寫版本化產物。API 不代理大檔。

線路分開是為了分開擴充與故障影響,不代表第一版就要拆成微服務。

圖 2|實線是主要請求或資料方向,虛線跨接非同步工作;控制、處理與位元組傳輸各有責任。

影片目錄與發布規則先留在同一個應用裡。若它們仍由同一團隊維護,拆成服務會多出跨服務一致性問題,暫時沒有必要。轉碼則涉及 CPU 密集、長時間運算與不可信輸入,值得獨立部署。這樣可以單獨擴充工作者,也能限制解碼程序的權限。

MySQL 交易無法直接包住物件儲存、佇列與 CDN。我採用 outbox:接受上傳完成的交易,同時記錄原檔資訊與待處理事件;背景 dispatcher 把事件送進佇列,成功後標記傳遞進度。AWS 的 transactional outbox 指引 說明了資料庫寫入與訊息傳送之間的雙寫風險,也提醒消費者仍須面對重複訊息。

若 dispatcher 已送出事件,但還沒標記就當機,事件會再送一次。因此消費端以 event ID 去重、以工作識別限制可接受的結果。用 outbox 能保留「應該做什麼」的證據,並沒有把所有外部副作用變成一次完成。

用四個 API 把責任接起來

以下節選主要介面;公開意圖的變更與刪除另接第 6、10 節,兩者都需核對擁有者授權與目前版本條件。

先用四個 API 表達建立影片、交付原檔與取得播放許可的責任:

POST /videos
  輸入:title、intended_visibility、idempotency_key
  回傳:video_id、generation、visibility=draft

POST /videos/{id}/uploads
  輸入:generation、expected_bytes、checksum_policy
  回傳:upload_session_id、expires_at、受限的 part 上傳資訊

POST /uploads/{session}/complete
  輸入:有序 part_number + etag 清單、原檔驗證資訊
  回傳:同一 session 的 receipt、received 或 reconciling

POST /videos/{id}/playback-sessions
  輸入:觀眾身分與播放能力;不接受客戶端自選任意 object key
  回傳:asset_version、manifest_url、短期存取資格、expires_at

第一個 API 的冪等鍵綁定帳號與請求內容;同鍵但不同標題/可見性意圖應回報衝突,避免兩次意外點擊變成兩支影片。完成上傳的冪等範圍是 UploadSession,播放 session 則可以每次重新核發。把所有 POST 都用同一種去重方式,會錯過不同操作的目的。

錯誤回應也要能支援 UI:版本已過期代表需要重新讀取影片狀態;來源格式不支援代表需要換檔;暫時處理失敗可以稍後查詢 receipt。回應只暴露安全的原因碼,不把內部 bucket 路徑、雲端憑證或完整例外堆疊送到創作者頁面。

哪些東西可以先不放進來

Redis 可以快取目錄或短期流量計數,但不需要承擔影片是否公開的唯一真相。搜尋初期可從有限範圍的資料庫查詢開始;只有文字搜尋品質或查詢負載需要時,再接搜尋引擎。推薦先用最新公開影片與有限的熱門清單,讓第一版能完成發現內容的旅程。

另一個方案是使用託管影音服務,讓服務商處理上傳、編碼與分發。它降低媒體基礎設施維護量,但仍要確認回呼重送、素材識別、可見性、授權與刪除的契約,並估算遷移原檔與產物的成本。我選擇分開的元件,是為了看清這些責任;小團隊若沒有影音運維能力,託管方案可能更合理。

4. 把影片身分、檔案版本與公開權限分開保存

一個 status 欄位很快會長成 uploading、transcoding、public、private、blocked、deleted。這些值不在同一個維度:private 的影片仍然可以完成轉碼,public 的影片也可能正在準備替換版本。硬塞在一起,會讓每次增加需求都要修改一大串不完整的轉移規則。

把這些狀態拆成四種記錄,公開權限就不必跟著上傳或轉碼進度一起變動:

  • Video 保存穩定的影片身分、owner、標題、visibility、policy 狀態、目前的 source generation、單調遞增的 metadata revision,以及 active playback asset ID。
  • UploadSession 保存一次傳輸的 provider upload ID、預期大小、到期時間、原始物件 key 與狀態。
  • MediaAsset 保存某一 generation 的原檔與播放產物、驗證結果、manifest 位置。
  • TranscodeJob 保存工作鍵、attempt、租約與處理結果。
傳輸、資產與影片可見性的三個獨立生命週期 UploadSession 管理原檔傳輸,MediaAsset 管理指定版本的產物處理,Video 管理公開、未公開或私密的可見性。只有目前 generation 的最低資產通過驗證、產品規則允許且影片未下架,才能在交易中啟用播放資產;私密影片亦適用。不要把三種生命週期塞進一個 statusUploadSession傳輸進度createduploadingverified確認原檔完整;可另走 aborted / expiredMediaAsset特定版本的產物pendingprocessingready資產可失敗與重試;ready 必須代表驗證通過Video產品可見性privateunlistedpublic可見性不是進度;每次變更推進 metadata_revision啟用播放資產是一個條件交易,與公開可見性分開。目前 generation + 最低可播資產 ready + 產品審核允許 + 尚未 blockedvisibility 決定誰能看;播放資產指標 決定看哪個版本;下架旗標獨立覆蓋兩者。
  1. UploadSession · 傳輸created → uploading → verified

    只有確認物件與完整性後,才能標為 verified。中止與過期走各自的終止路徑。

  2. MediaAsset · 產物pending → processing → ready

    每個 generation、格式與解析度各有資產。失敗能重試;ready 代表已驗證。

  3. Video · 可見性private / unlisted / public

    三者是可選規則。每次變更推進 metadata_revision;下架旗標覆蓋可見性與資產啟用狀態。

  4. 播放資產閘門 · 一個交易私密影片也能啟用可播產物

    檢查目前 generation、最低資產 ready、審核允許與尚未 blocked,再更新 播放資產指標。

資產 ready 不會自動變 public;visibility 也不能證明資產可播放。

圖 3|分開追蹤傳輸、處理與可見性,再由條件交易啟用播放資產。

其中 generation 是應用層的遞增版本識別,不等同於某個雲端供應商的 object generation。創作者換原檔,或下架後要使舊處理結果失去資格時,系統推進這個版本。另有 metadata revision,任何影響對外狀態的修改都遞增,包含同一原檔的公開/私密切換;不能拿 source generation 當作所有事件的排序鍵。每個工作都帶上它處理的 generation;完成後必須再比對。

資料表的最小約束可以是:UploadSession 的 provider upload ID 唯一;TranscodeJob 的 (video_id, generation, profile_version) 唯一;每個產物指向一個明確的 Video 與 generation;播放資產指標只能指到經驗證、且仍被允許發布的資產。工作可能有多個 attempt,不能因為工作鍵唯一,就假設只有一個程序還在執行。

這些記錄共同決定影片能否公開:

可以公開 = 原檔已確認
        且基本播放組合已驗證
        且產物 generation = Video 目前 generation
        且創作者公開意圖有效
        且 policy 允許
        且 Video 未被刪除

資產啟用與公開的交易可以鎖定 Video 記錄,讀取相關資產驗證狀態,再以目前版本更新播放資產指標;或採帶版本條件的更新,並檢查影響筆數。不能先查一次狀態、離開交易做一段工作,最後不帶條件地寫 public。這會讓「剛被刪除」與「剛轉碼完成」互相覆蓋。

這也延續了訂單系統的 寫入責任邊界:worker 有權提出產物完成的證據,發布模組有權決定那份證據現在是否仍可生效。

5. 續傳的答案在儲存端,不在瀏覽器的進度條

阿晴重新連線時,瀏覽器記得自己曾送出第八個 part,但不確定伺服器收到了沒有。如果直接從第九個開始,可能少一塊;如果重新建立整個上傳,也可能白白留下半份物件。

以 S3 multipart 為例,API 建立 UploadSession,綁定 owner、video、generation 與受控的 object key,回傳每一 part 的短期上傳許可。

客戶端直接上傳,將 part number 與服務回傳的 ETag 回報平台保存成自己的 part ledger;重連時,以儲存端查詢結果核對 ledger,再補缺失的部分。

ListParts 要處理分頁,只用來核對,不直接拿它的結果組成完成請求;完成請求使用自己維護且確認過的有序 part/ETag 清單。瀏覽器的進度只是提示,續傳也不能悄悄換成另一份來源檔。

可續傳上傳:先確認物件,再提交影片狀態客戶端取得上傳識別碼後,直接將帶有校驗值的分片送到物件儲存。斷線後透過 ListParts 或查詢確認已收到的分片,只補傳缺片。完成請求可冪等重試;伺服器完成組裝並確認物件的識別碼、大小與校驗值,才將影片狀態及 outbox 事件放進同一筆資料庫交易。ETag 不被視為通用的內容校驗值。上傳成功的依據,是伺服器查證過的物件。創作者客戶端保留 upload_id 與分片進度上傳 API掌握完成狀態與冪等鍵物件儲存保存分片、組裝原始影片① 建立上傳工作與 video_id② 分片直傳:part_number + 明確指定的 checksum③ 斷線後 ListParts/查詢:哪些分片真的已收到?以服務端回覆為準,只補缺片;本機進度不能證明上傳完成。④ complete(冪等鍵)⑤ 完成組裝,再查證物件 metadata確認 object key、bytes 與 checksum⑥ 同一筆 DB transaction持久化「原檔已確認」狀態 + 寫入 outbox重送 complete 回傳既有結果,不重複建立處理工作。校驗值依儲存服務與演算法契約驗證;multipart ETag 不能直接當成檔案 checksum。

先查證原檔,再提交影片狀態

  1. 建立上傳工作API 配發 video_id 與 upload_id,客戶端保存續傳進度。
  2. 分片直接送物件儲存每片有 part_number 與明確指定的 checksum;影片 bytes 不繞經 API。
  3. 斷線後,先問再補傳用 ListParts/查詢確認已收到的分片,只補缺片。本機顯示成功不算服務端確認。
  4. 冪等完成請求complete 帶冪等鍵。回覆遺失時可重送,取得既有結果。
  5. 伺服器確認實際物件完成組裝後,查證 object key、bytes 與 checksum,不能只信任客戶端回報。
  6. DB 狀態與 outbox 一起提交同一交易持久化「原檔已確認」與處理事件,避免重複建立工作。

校驗演算法須明確約定。multipart ETag 不能直接當成檔案 checksum。

續傳解決斷線,冪等解決重試;物件查證與 DB 交易共同決定何時能進入後續處理。

S3 的 multipart 概觀 說明建立、上傳 parts、完成三個階段;完成時需要提供各 part 的識別資訊。ETag 不能一律當成整份原檔的 MD5,完整性驗證要依實際 checksum 機制設計。這裡也不應把 S3 的 part 協定與另一家服務的 byte-offset 協定混用,續傳客戶端要知道自己遵守哪一份契約。

平台的完成 API 使用同一個 UploadSession ID 重試。若已完成,回傳同一份 receipt;若儲存端完成、資料庫尚未確認,就進入 reconciliation,查證原物件後補上狀態與 outbox。可以保留一個短期 completing 狀態,但一定要有到期掃描,不能讓中斷的請求永久卡住。

還有一個會藏在正常回應裡的錯誤:CompleteMultipartUpload 可能先送出 HTTP 200,最後在內容中回報錯誤。用官方 SDK 時仍需處理 SDK 解析後的失敗;自寫 HTTP 客戶端不能只檢查 status code,就宣告原檔可用。

上傳許可的範圍要比登入許可更小

客戶端不能自行決定要覆寫哪個正式 object key。上傳工作階段必須綁定帳號與影片,限制預期大小、期限、可用操作與物件位置;完成 API 重新檢查擁有權。後端驗證實際檔案大小與格式,不相信副檔名或 Content-Type。解碼工作者處理的是外部輸入,應隔離執行、限制 CPU/記憶體/磁碟時間,不給它任意讀取其他使用者物件或發布公開狀態的權限。

過期 UploadSession 要清除未完成的 parts。S3 multipart 不會因為使用者關掉分頁就自動完成清理,可用明確 abort 與生命週期規則處理;但正在傳送的 part 與 abort 有競爭,清理程序要依 AbortMultipartUpload 文件 確認殘留。這筆費用不該等帳單出現才發現。

6. 轉碼工作可以重跑,發布只能接受仍有效的產物

假設 worker A 已開始處理 generation 7,心跳中斷後,排程器把工作交給 worker B。A 其實沒死,只是網路暫時不通;幾分鐘後兩個 worker 都回報成功。

把佇列 visibility timeout 拉長,只能改變重送時機。以 SQS standard queue 為例,官方直接要求應用面對至少一次投遞。所以我把「可以重複執行」和「可以採用哪個結果」分開。

每次 attempt 寫到獨立、不可變的產物前綴,例如影片 v42、generation 7、profile p3、attempt b。worker 不覆寫正在服務觀眾的 manifest,也不直接修改播放資產指標。它只提出候選資產,附上工作鍵、attempt token、檔案清單與驗證結果。

工作可重跑,播放資產啟用須通過條件交易同一個轉檔 job 可以有多次 attempt,每次使用不覆寫的 source generation 與 attempt 路徑。該 job 的租約與 fencing token 防止過期 worker 提交,租約期限以 DB 時間判斷。啟用交易同時檢查目前原檔世代、啟用政策與最低可播資產, 並以 expected bundle revision 做 CAS,才原子切換播放資產指標、儲存完成紀錄與寫入 outbox。CAS 衝突時重讀目前指標並重組候選 bundle,不能讓跨 job 的舊候選覆蓋 已啟用的較完整版本。同一 job、attempt 與 candidate 的重複完成回傳既有 receipt, 終態 job 與唯一完成紀錄確保不新增 outbox 事件。資產啟用不改影片可見性,私人與不公開影片也可啟用。一個 job,多次 attempt;完成通知不等於資產啟用許可video = v42 | job = j8 | 來源 generation = g7 | 重試沿用 job_id,遞增 attemptattempt 1:租約已過期該 job 的舊 fence = 41即使恢復運作,也不再有提交權。/v42/g7/a1/…attempt 2:當前持有者該 job 的新租約 + fence = 42產物另存新路徑,不覆寫舊 attempt。/v42/g7/a2/…遲到/重複完成提交完成結果啟用交易:所有條件與去重檢查一起成立source generation = g7仍指向目前原檔世代blocked = false目前政策允許啟用資產最低可播資產已驗證manifest + 必要畫質檔該 job 的租約與 fence須有效;期限以 DB 時間判斷播放資產指標 CAS:bundle revision 必須等於讀取時的 expected revisionsource generation 保證原檔有效;bundle revision 防止跨 job 的舊候選蓋回較完整的新版本。條件失敗/已處理全部通過重複回既有 receipt;失效拒絕同 job/attempt/candidate 去重終態 job/唯一完成紀錄:不新增 outboxCAS 衝突 → 重讀指標、重組 bundle,再試原子切換播放資產指標CAS 成功:bundle revision r12 → r13資產指標 + 完成紀錄 + outbox 同一交易不改影片可見性;觀看仍須通過權限 API。
一個 job,多次 attempt

video v42/job j8/source generation g7。重試沿用 job_id,產物寫入不同的不可變路徑。

attempt 1:已失去租約

該 job 的 fence 41 已過期。即使舊 worker 恢復,也不能提交。

/v42/g7/a1/…
attempt 2:當前持有者

該 job 的新租約與 fence 42。另存新路徑,不覆寫舊產物。

/v42/g7/a2/…
↓ 完成事件進入啟用交易
  • 仍是目前的 source generation
  • 未被封鎖,且目前政策允許啟用資產
  • manifest 與最低可播資產已驗證
  • 該 job 的租約與 fencing token 仍有效,期限以 DB 時間判斷
  • 播放資產指標的 bundle revision 仍等於讀取時的 expected revision,才能 CAS 更新
全部通過 → 原子啟用資產

同一交易以 CAS 切換播放資產指標、儲存完成紀錄與寫入 outbox。bundle revision 由 r12 更新為 r13,保留已啟用的較完整資產。這不會改變影片可見性。

失效拒絕,重複回既有 receipt

同一 job/attempt/candidate 的重複完成回傳已儲存的結果。終態 job 與唯一完成紀錄確保不新增 outbox 事件。 過期 worker 或舊 source generation 不能再次啟用資產。CAS 衝突時重讀目前指標、重組候選 bundle,再重試;不能以最後完成者直接覆蓋。

兩種版本,各管一件事

source generation 保證原檔仍有效;bundle revision 防止跨 job 的較舊候選覆蓋較完整版本。單一 job 的 fence 無法處理跨 job 競爭。 私人/不公開影片也可啟用資產,實際觀看仍須通過權限 API。

租約與 fencing 限制同一 job 的舊 worker;播放資產指標 CAS 處理跨 job 競爭。資產啟用不代表公開,私人/不公開影片仍可在授權後播放。

想看舊 token 的產物怎麼被擋下?到機制實驗室操作驗印閘門。

先決定哪個 attempt 還有資格完成

相同工作、attempt 與候選資產的完成通知若已被接受,回傳儲存的完成收據即可。收據證明當時接受過這份結果;影片此刻是否仍可播放,須另查目前狀態。唯一完成約束與終止狀態,則避免重送再次產生啟用事件。

第一次接受結果時,完成交易要一起核對 Video generation、目前有效的 job attempt token,以及以資料庫時間判斷的租約期限。claim 新 token 與續約,也都透過帶條件的原子更新。

這是應用的 fencing 規則。B 在租約過期後取得新 token,A 手上的舊 token 就不能再贏得完成狀態。只檢查 generation 不夠:A、B 處理的是同一代原檔,兩者都會通過。generation 擋住過期的來源版本,attempt token 與租約擋住已失去工作資格的程序。

再決定完整的播放組合能否切換

基本畫質完成後,啟用交易一次切換 active playback asset ID。私密與未公開連結影片也可以有可播放資產;是否對大眾公開,由 visibility 與 policy 另外決定。只有要把 visibility 改成 public,才要求創作者的公開意圖有效。

後補高畫質時,還有另一種競爭:兩個 rendition job 都有效,卻各自拿著較舊的清單加入自己的畫質。若讓最後完成者直接覆寫,前一個工作加入的畫質可能消失。因此由同一個 bundle assembler 組裝新的完整清單版本,切換時以 compare-and-swap(CAS)核對預期的 active playback asset ID/bundle revision。指標已被其他工作更新,就重新組裝。

每個 rendition job 的 token 只管理自己的工作,不能替不同工作之間的組合版本排序。bundle 的版本檢查通過後,才原子切換指標;觀眾取得的每份 manifest 都必須引用完整存在的 segments。主清單一旦可見,所列的檔案就要能讀取。

把結果接收、資產啟用與需要的公開操作放進同一筆交易,可寫成以下概念程序。正式實作仍需補 schema、索引、隔離級別與客戶端影響筆數語義:

BEGIN
  鎖定 Video,再依固定順序鎖定 Job
  若此 (job_id, attempt_token, candidate_asset_id) 已接受,結束交易後回傳原收據
  比對 current_generation、active_attempt_token、lease_expires_at
  確認 Job 尚未完成、Video 未下架、政策允許啟用此資產
  確認候選 Asset 已通過最低播放組合驗證
  比對預期的 active playback asset ID/bundle revision
  將候選 Asset 設為接受,將 Job 設為完成,保存唯一完成收據
  更新 Video 的播放資產指標與 metadata revision
  若需轉成 public,再核對公開意圖,於同一交易更新 visibility
  INSERT outbox:video.asset_activated,包含交易最後的 revision、visibility 與 asset_id
COMMIT

鎖定順序一致,可以減少不同流程互相等待的機會。需要做遠端物件檢查時,先在交易外完成驗證並保存不可變的結果,交易只檢查其身分與有效性,不在持鎖期間下載影片。若資產可能被清理程序刪除,清理程序也必須遵守引用與候選狀態規則。MySQL 的 UPDATE 行為 與 InnoDB 鎖定說明 支持這些資料庫原語;跨資料庫與物件儲存的整體協定仍是我的設計選擇。

產物驗證至少包含存在性、播放清單能解析、引用的檔案存在、音訊與影片組合符合支援範圍,以及必要的實際解碼抽查。只確認 ffmpeg process exit 0 不足以證明整條播放路徑。GOP 與 ABR 背景可參考站內 影音上傳與轉碼文章;這裡的容量估算仍以前述案例參數為準。

切換成功後,舊產物還不能立刻刪

未被採用的物件交由延後清理處理,清理前須確認沒有目前播放或回復用的引用。已被替換的 manifest/segments 也不能立即刪除:有效的播放 session 可能仍在使用舊版本。這些檔案須保留到允許的 session/許可期限與清理緩衝結束,或依明確的強制撤銷政策處理。新觀眾拿到新版本,不代表舊觀眾已經播完。

什麼值得重試,什麼應該停止

暫時無法讀取原檔、工作者被回收,可以用有上限的指數退避與抖動重試。來源損壞、格式不支援、超過產品規格,反覆重跑只會占滿佇列。系統應保存失敗原因分類、嘗試次數與最後進度,讓創作者看到可採取的動作。死信佇列是待處理清單,必須有負責人與回復程序,不能當成「問題已處理」的終點。

7. 按下播放之後,授權與影片位元組走不同的路

阿晴的朋友打開連結,頁面先取得影片 metadata。按播放後,應用檢查這個觀眾是否能看目前版本,回傳播放 session、manifest 位置與短期存取資格。播放器隨後向 CDN 取得清單和 segments,依網路與緩衝狀態選擇可用畫質。

HLS RFC 8216 定義清單與媒體分段的協定形式,包括 Master Playlist 指向不同 Variant Streams。協定提供描述與選取媒體的工具;這裡的「哪些畫質先公開」是產品政策,不能從 HLS 規格直接推出。

權限先行,影片直接從 CDN 播放播放器先呼叫權限 API,檢查觀看者與影片狀態;只有通過後才取得短效、範圍受限的 manifest 及 segment 存取憑證。播放器帶憑證直接向 CDN 取影片。CDN 每次請求都先驗證授權,即使快取命中也不能略過。權限無法確認時不發憑證,無效憑證在 CDN 被拒絕;撤銷有短效憑證的存活窗口,已下載的資料無法收回。權限 API:先判斷能不能看身分/觀看資格/影片可見性/下架狀態不能確認權限 → 不發憑證(fail closed)通過才發放:短效、限定影片與路徑的憑證同時保護 manifest 與 segment;到期須重新授權。可用簽名 URL 或 cookie,不能只保護播放清單。① 查權限② 短效憑證播放器影片 bytes 不經 API後續分片持續攜帶有效憑證③ 請求 + 憑證④ 影片 bytesCDN:快取命中也要先驗授權每次請求先驗證簽名、範圍、期限無效 → 拒絕授權通過才取資料命中 → 快取回傳 bytes未命中 → 私有 originorigin 僅接受受信任 CDN 存取,避免繞過邊緣授權。撤銷有邊界:停止新授權,並讓既有短效憑證失效或到期。短效憑證仍有存活窗口;已下載到裝置的 bytes 無法收回。
1.播放器先呼叫權限 API

檢查身分、觀看資格、影片可見性與下架狀態。不能確認權限,就不發憑證。

2.取得短效、限範圍憑證

manifest 和每個 segment 都必須受保護;可使用簽名 URL 或 cookie。憑證到期須重新授權。

3.CDN 每次都先驗證授權

驗證簽名、範圍與到期時間。快取命中也不能略過;無效憑證直接拒絕。

4.CDN 直接回傳影片 bytes

命中就從快取取資料,未命中才回源。origin 只接受受信任 CDN,影片 bytes 不繞經權限 API。

撤銷的邊界停止新授權後,既有短效憑證仍須失效或到期。已下載的 bytes 無法收回。

權限 API 決定誰能取得播放資格,CDN 負責每次存取驗證與影音傳輸;快取不能成為授權旁路。

想看資格檢查和影片位元組怎麼分開走?到機制實驗室操作售票亭與輸送管。

對私密影片,只保護 manifest 不夠。如果 segments 的 URL 可以被任何人持續讀取,知道路徑的人就可能繞過播放 API。因此我們要求 CDN 在媒體請求到達時驗證資格,包含快取命中的情況;origin 同時限制只能由預期的 CDN 路徑存取。

以 CloudFront 為例,signed cookies 適合授權一組受限檔案,signed URL/cookie 選擇指引 舉的情境就包含 HLS。signed cookies 文件說明,CloudFront 驗證簽章與政策後,才決定從 edge cache 或 origin 回應。這可以避免每個 segment 都回到應用查資料庫,但需要設計 cookie domain、路徑、跨來源請求與播放器相容性。

另一個方案是 signed URL,適合明確的單一資源許可;如果播放清單包含大量獨立網址,生成、傳遞與更新許可會更複雜。也可使用邊緣驗證的 bearer token,但 token 的驗證與快取鍵要分開設計。每位觀眾一個隨機 cache key,可能把共享媒體快取打散;完全忽略 token 驗證,又會洩漏內容。

短期資格到期,不等於立即收回每個位元組

假設播放資格有效五分鐘,播放器在到期前向應用更新;這是示例政策,並非固定推薦值。太短會增加授權依賴與播放中斷風險,太長會拉長撤銷後舊資格仍有效的時間。CloudFront 在每次 HTTP 請求時檢查期限;已開始的傳輸與之後發出的請求要分開理解,不能宣稱時鐘一到就撤回已傳出的片段。

如果產品要求下架後十秒內阻止所有新片段請求,五分鐘的離線可驗證資格就不符合要求。你需要更短的期限、邊緣可查的撤銷狀態,或其他能達到要求的方案,並量測傳播延遲與故障行為。因此,選定許可期限前,要先確認產品容許多長的撤銷延遲。

8. 爆紅影片的成本,先看快取未命中的位元組

第二天,阿晴的影片出現在熱門社群。應用 API 的 QPS 增加,但每個觀眾持續讀取的影片片段才是大量頻寬來源。只看 API CPU 很平穩,可能忽略 origin 已經接近傳輸或請求上限。

沿用前面的尖峰 10.4 Gbps 假設,如果媒體位元組快取命中率為 95%,簡化後的回源約是 0.52 Gbps;命中率降到 70%,回源約為 3.12 Gbps,相差六倍。這裡算的是位元組比例,不是 request hit ratio,尚未包含不同 edge 重複抓取、Range 行為與清單請求等因素。

分層快取如何降低回源流量這是假想影音平台的教學模型,不是 YouTube 內部架構或實測數據。假設觀眾端輸出為 10.4 Gbps,邊緣快取以位元組加權的命中率為百分之九十五,未命中的 0.52 Gbps 交給來源屏障。來源屏障集中回源並快取內容,只有再次未命中才讀取來源物件儲存;此圖不假設來源屏障的命中率。相同輸出若邊緣命中率降為百分之七十,交給來源屏障的流量將增加至 3.12 Gbps,是原本的六倍。這不是來源物件儲存的最終流量。回源不是承接全部觀看流量教學假設:觀眾端輸出 10.4 Gbps;命中率按位元組加權,非請求次數。01邊緣快取 · Edge假設命中 95%:就近提供快取內容未命中:向共用的 Origin Shield 取回觀眾端總輸出(假設)10.4 Gbps不等於來源端流量剩下 5%:0.52 Gbps 交給 Shield02來源屏障 · Origin Shield集中承接多個邊緣節點的回源請求命中則回傳;未命中才讀取物件儲存不另假設 Shield 命中率集中回源與快取減少對來源物件的重複讀取Shield 未命中,才向來源物件儲存取回03來源物件儲存 · Origin保存可播放的轉碼產物,承接兩層皆未命中的讀取來源流量取決於Shield 命中率假設 95% 命中 → 0.52 Gbps假設 70% 命中 → 3.12 Gbps回源增加 6 倍比較進入 Shield 的邊緣未命中流量;不是物件儲存的最終流量。簡化估算忽略重試、預熱、協定開銷與流量放大;快取命中仍須符合有效的播放授權。

教學假設:觀眾端輸出 10.4 Gbps。以下命中率皆按位元組加權。

  1. 01 邊緣快取 · Edge

    假設命中 95%,命中部分就近提供。

    未命中的 5%:10.4 × 5% = 0.52 Gbps,向 Shield 取回 ↓

  2. 02 來源屏障 · Shield

    集中承接多個邊緣節點的回源並快取內容,減少對來源物件的重複讀取。此圖不另假設 Shield 命中率。

    Shield 未命中,才讀取來源 ↓

  3. 03 來源物件儲存

    保存可播放的轉碼產物,承接兩層皆未命中的讀取。最終流量取決於 Shield 命中率。

教學假設:命中率降低的代價

相同 10.4 Gbps 輸出:

95% 命中 → 0.52 Gbps 回源

70% 命中 → 3.12 Gbps 回源

增加 6 倍。比較的是進入 Shield 的邊緣未命中流量,不是物件儲存的最終流量。

簡化估算忽略重試、預熱、協定開銷與流量放大。快取命中仍須符合有效的播放授權。

假想影音平台的分層快取教學模型;所有流量與命中率皆為假設,不代表 YouTube 實測值。

想看快取命中率掉一點,origin 要多扛多少?到機制實驗室操作水塔與抽水站。

版本化、不變的 segment 路徑有利於長期共享快取。相對地,帶狀態意義的 metadata、播放許可、或會變動的清單,應有不同的 TTL 與快取策略。把所有東西設成一年快取,會讓後來的權限修改與內容替換難以說清楚;把所有東西設成不快取,則把成本推回 origin。

多個 edge 同時遇到冷門轉熱門的影片,可以再加一層共享回源快取。CloudFront Origin Shield 的官方定位,是提供額外的快取層來減少 origin 負載。它不是替代授權的安全層,也不保證每個部署與工作負載都值得增加費用;應比較回源請求、位元組、額外延遲與單位成本。

預熱也要有邊界。只對已確認即將大量觀看的首段與基本畫質做有限預取,可能有幫助;把所有影片、所有畫質預熱到所有區域,常常先製造了成本。快取填充期間可以合併相同物件的並發回源,並限制 origin 的最大同時請求。若超出負載,優先保住已存在的基本畫質,暫緩昂貴的後補格式與非必要背景掃描。

預算要按行為歸因

帳單至少分開觀察原檔保留、播放產物、轉碼 CPU 或服務分鐘、CDN 下行、回源與請求數。播放時間提高可能同時增加產品價值與成本;轉碼重試飆高則可能純粹是故障。用每一有效觀看分鐘與每一成功發布影片的成本,比單看總費用更容易判斷下一個改造是否有意義。

換用更省位元率的編碼不一定立刻省錢。需要比較裝置支援、額外編碼成本、預期被觀看次數與保留期。對幾乎沒人觀看的長尾影片,一開始就產生所有高成本格式,可能無法回收那筆處理費用。

9. 搜尋與觀看數可以晚到,播放許可不能靠它們決定

影片已經公開,搜尋卻還找不到;觀看了一百次,頁面只顯示八十次。這些現象不必然是同一類故障。產品需要明確區分「使用者能不能看」與「讀取模型更新得多快」。

Video 與發布記錄是可見性的權威來源;搜尋、推薦清單與觀看數是衍生資料。影片發布事件帶上 event ID、video ID、metadata revision 與事件類型,分別送到索引、推薦與統計 consumer。consumer 保存處理進度,以每支影片的 metadata revision 拒絕較舊事件覆蓋較新狀態,並支援重建。這個版本在公開、私密、下架等修改都遞增,與只描述來源檔的 generation 分開。

權威資料與最終一致的讀模型在假想影音平台中,影片中繼資料與 transactional outbox 在同一筆 MySQL 交易提交。Outbox relay 將事件送入可重播的事件流。Video.metadata_revision 在每次可見性或發布狀態變更時遞增,事件攜帶該修訂,讀模型拒絕較舊修訂覆蓋新狀態;source_generation 僅判斷來源與轉碼產物是否有效,不是權限更新的排序鍵。中繼資料事件以 event_id 去重;觀看事件由量測管線另外匯入並獨立去重,計數聚合允許延遲。讀模型過期或故障可以使探索體驗降級,但不能繞過權威播放權限。權威狀態先提交,查詢體驗再追上source_generation 僅判斷來源/轉碼有效性;權限更新使用 metadata_revision。MySQL 權威中繼資料Video.metadata_revision可見性/發布狀態異動,每次遞增同一筆交易原子提交,避免雙寫落差Transactional outbox在同一資料庫記下待發布事件提交後由 relay 投遞;失敗可重試可重播事件流至少一次投遞消費端須能重複執行event_id 去重冪等更新,安全重播metadata_revision拒絕舊修訂覆蓋新值來源代次不排序權限搜尋索引新影片可能稍後才找得到;更新拒絕較舊的 metadata_revision。推薦讀模型排序與候選集合可以降級;真正播放仍須檢查授權。觀看計數觀看事件另行匯入並獨立去重;計數可延遲,不作授權依據。讀模型過期或故障 → 降級探索體驗;播放仍依權威狀態與有效授權,不能繞過權限。

1. 權威資料與 outbox 同時提交

MySQL 保存影片狀態與可見性。每次可見性或發布狀態變更,都遞增 Video.metadata_revision;transactional outbox 在同一筆交易記下事件,避免漏發。

提交後 relay 投遞;失敗可重試 ↓

2. 可重播、至少一次的事件流

中繼資料事件以 event_id 去重,冪等更新;事件攜帶 metadata_revision,讀模型拒絕舊修訂覆蓋新狀態。source_generation 僅判斷來源/轉碼產物有效性,不用於排序權限變更。

三種讀模型各自追趕 ↓

  • 搜尋索引

    新發布影片可能稍後才找得到;metadata_revision 檢查防止資料倒退。

  • 推薦讀模型

    候選與排序可以降級;真正播放仍須檢查授權。

  • 觀看計數

    觀看事件另行匯入並獨立去重;計數可延遲,不作授權依據。

讀模型過期或故障,不能繞過權限。播放仍依權威狀態與有效授權。

假想影音平台的權威資料與衍生讀模型;這是設計示例,不是 YouTube 的內部系統配置。

影片即使仍殘留在搜尋結果,點擊播放也要通過目前的授權。這項設計可以縮小索引延遲的影響,但還不夠:私密影片的標題與縮圖也可能是敏感資訊。搜尋與推薦端需要刪除/隱藏事件、延遲告警與查詢側的可見性保護,不能只說「反正影片播不了」就接受資訊曝光。

初期可以先將只允許公開的 metadata 放進搜尋索引,私密內容走個人資料庫查詢。未公開連結是一種產品可見性選項,不等於登入授權;若任何取得網址的人都能看,就應明白告知創作者,不能把它包裝成機密資料的保護措施。

觀看數需要先定義什麼算一次有效觀看。頁面打開、開始播放、觀看超過某段時間,是不同事件;重試、重複上報與機器流量也會影響統計。我的最小方案保存去識別化的播放事件與去重鍵,採批次聚合顯示近似進度;用來結算廣告或支付分潤的數字,則需要另一份更嚴格的對帳契約,不能直接沿用頁面上的快取數字。

推薦服務暫時不可用時,可退回最新公開影片;觀看數服務暫停,不應讓現有影片全部無法播放。曝光與統計的故障就能留在各自範圍內。事件管線恢復後,要能從 durable log 或權威資料重建,並檢查刪除狀態沒有被舊事件重新放回去。

10. 下架要阻止舊工作復活,也要說明快取收斂的期限

阿晴按刪除的同時,一個先前失聯的 worker 回來了。它帶著完整的高畫質產物,如果完成 API 只檢查檔案存在,就可能把影片重新設為公開。這個競爭情境比「成功刪掉一列」更值得測試。

下架交易先修改 Video 的可見狀態、清除可發布資格並遞增 generation,再寫出下架事件。之後的播放許可一律拒絕,舊 worker 的完成交易也因版本或狀態條件不符而失敗。這一刻是控制面接受下架的時間,必須有可稽核的 receipt。

下架交易、舊工作防復活與各層收斂的時間線 下架先在權威資料庫標記 blocked 並增加 generation,立即拒絕新的授權。舊 worker 持有舊 generation,條件更新會失敗。搜尋推薦、CDN 與儲存依序收斂;已發出的憑證有到期窗口,已下載內容不可收回。下架的第一步:撤銷未來資格,而不是先等檔案刪光T0 · 權威資料庫交易blocked = true;generation 41 → 42;metadata_revision++同一交易寫入 outbox,新授權拒絕;授權快取需明列最長陳舊窗口。播放入口:停止新授權所有可見性一律拒絕,包括 direct URL。已發出的短效憑證仍有到期窗口;更快撤銷需要邊緣 denylist / epoch 檢查。舊 worker:拒絕過期提交worker 手上仍是 generation 41。條件更新要求 generation = 41 且未下架,資料庫已是 42 → CAS 失敗,不可復活。之後逐步收斂:每一層都要可重試、可觀測搜尋與推薦移除版本化 tombstone 擋住舊事件CDN 清除快取清除是加速手段,不是權限機制儲存清理依保留政策移除物件與殘留產物不能承諾收回觀眾已下載的位元組,也不能把「purge 已送出」當成下架完成。
  1. T0 · 權威交易blocked = true;generation 41 → 42

    同一交易推進 metadata_revision 並記錄下架事件。新的播放授權依權威狀態拒絕;授權快取必須有明確陳舊上限。

  2. 立即 · 防止復活舊 worker 的 CAS 提交失敗

    工作仍拿 generation 41,但目前版本已變成 42。不得啟用舊資產。

  3. 憑證窗口已發出的授權等待到期或被撤銷

    更快撤銷需要邊緣 denylist 或 epoch 檢查;只清快取不能取代授權。

  4. 逐步收斂移除曝光 → 清快取 → 儲存清理

    搜尋與推薦用版本化 tombstone 擋住舊事件。各層以可重試任務與完成指標確認。

已下載內容無法收回;purge 已送出,也不等於所有邊緣已完成清除。

圖 9|同步交易守住資格,非同步任務清理殘影;下架不等於瞬間刪除所有副本。

接下來還有不同速度的收斂工作:搜尋與推薦移除曝光;CDN 撤銷或失效既有存取路徑;物件儲存依保留政策刪除來源與產物;備份與稽核資料依另外的期限處理。UI 可以先顯示「已停止公開,正在清理檔案」,但不能把資料庫變更稱為全球資料已刪除。

CloudFront 失效文件 說明如何讓分發中的檔案在到期前失效。失效請求需要追蹤完成進度,也無法移除已下載到觀眾裝置的資料。若 origin 還會回傳舊物件,單純清 cache 甚至可能讓下一次請求重新抓回內容。因此要一起處理目前授權、origin 存取、舊資格期限與快取清除。

刪除流程應該是可重試的工作清單

每個清理步驟保存目標與結果,重試刪除已不存在的物件應視為達成目標。清理完成前,影片 tombstone 不宜立即消失;否則重送的「發布成功」事件或舊索引快照可能失去比較版本的依據。tombstone 保留多久,要覆蓋可重播事件、離線工作與回復程序的範圍,不能隨便填一個 TTL 就假設安全。

如果法規、申訴或營運需要保留資料,必須將「停止公開」與「實體刪除」分開描述,並由產品與相關專業人員確認政策。保存期限確認後,工程端要提供可查詢的狀態、可稽核的操作與不會偷偷復活的重試行為。

11. 驗收要故意打斷轉碼、回源與授權

阿晴遇到的斷線、爆紅與刪除,已經各有處理路徑。接下來要故意打斷它們,確認逾時和重送時仍會得到預期結果。以下三個演練分別檢查轉碼能否接手、回源能否承受負載,以及授權故障時會放行哪些請求。

三種故障,三種不同的影響範圍這是假想影音平台的故障隔離示例。工作佇列停滯會延後新影片的處理與發布,既有影片的獨立播放路徑可以不受影響。中繼資料或授權服務失效時,不能確認權限就拒絕新授權,不可將影片當作公開;既有授權依期限、撤銷機制與既定政策處理。來源屏障或物件儲存故障時,快取命中可能在授權仍有效的條件下繼續,但未命中的內容無法取回,快取也不能讓舊權限無限有效。故障範圍,取決於哪條依賴斷了假想平台的三個情境:可用性降級,不等於放寬存取權限。01 工作佇列停滯上傳完成,卻等不到處理結果卡在轉碼或發布前的工作受影響新影片的處理/發布延後仍可繼續既有已發布影片的獨立播放路徑前提:播放沒有同步依賴這條工作佇列02 中繼資料/授權失效無法確認可見性或播放權限不能以「應該公開」作為預設必要限制拒絕新授權;不可預設影片公開既有授權僅依期限、撤銷機制與既定政策使用無法滿足驗證條件時,也不能繼續放行03 Shield/Origin 失效回源層不可用,冷內容取回失敗影響依快取所在層與命中情況而異快取命中仍須驗證有效授權,才可能繼續送出未命中若須讀取失效層,則無法取得內容快取命中不能讓舊權限無限延長服務畫成不同方塊,不代表已完成隔離;還要檢查同步依賴、授權期限與降級策略。

01 工作佇列停滯

上傳完成,卻等不到處理或發布結果。

受影響
新影片的處理/發布延後。
仍可繼續
既有已發布影片的播放,前提是播放路徑沒有同步依賴這條佇列。

02 中繼資料/授權失效

無法確認可見性或播放權限。

必要限制
拒絕新授權;不可預設影片公開。
既有授權
依期限、撤銷機制與既定政策使用。無法滿足驗證條件,也不能繼續放行。

03 Shield/Origin 失效

回源層不可用;影響依快取所在層與命中情況而異。

快取命中
仍須驗證有效授權,才可能繼續送出。
未命中
若須讀取失效層,則無法取得內容。快取命中不能讓舊權限無限延長。

畫成不同服務,不代表已完成隔離;仍須檢查同步依賴、授權期限與降級策略。

假想影音平台的故障隔離示例;可繼續服務的範圍,必須同時滿足依賴隔離與存取控制。

演練 A:基本畫質工作者失聯

觸發方式是在讀取原檔後終止 worker,或讓它完成產物但無法提交結果。觀察 queue age、lease 到期、重試次數與從接收完成到可播放的延遲。預期結果是新的 attempt 能接手,舊 attempt 回來不能覆蓋;原本已公開的其他影片繼續播放。

停止規則可設為示例:基本畫質 queue age 超過產品承諾的一半,暫停高成本的後補格式;若持續上升,限制新上傳的處理准入並告知等待。實際閾值需要測試決定。恢復時檢查孤兒物件與完成事件,不要一口氣重放所有死信,把系統再次壓垮。

演練 B:熱門影片回源增加

在可控測試環境清除測試物件快取、改變熱門集中度,觀察位元組命中率、origin 並發、回源延遲與第一幀。不能為了測試直接清正式站所有快取。預期降級是限制背景工作、保住基本畫質與必要的播放清單,並確認授權檢查沒有因為追求命中率被跳過。

如果提升 Origin Shield 或合併回源後,第一幀沒有改善,可能瓶頸在播放器、權限服務或網路最後一哩。改造要以同一組負載與觀測分母比較,不能只拿一張 CDN 命中率圖宣稱整個觀看體驗變好。

演練 C:播放授權依賴暫時不可用

讓 metadata/授權查詢回傳逾時,觀察新的播放 session 與既有 session。我選擇對不能確認權限的新請求拒絕發出許可;現有短期許可能持續到什麼時點,依前述政策執行。這會犧牲一部分可用性,但可以避免服務故障時把私密內容全部開放。

測試至少包含公開、私密、已下架與剛改權限的影片。對公開內容是否允許使用有明確期限的舊 metadata,是另外一個可接受性決策;它不能直接套用到私密或撤銷路徑。把這些情境寫成測試案例,才知道系統在降級時究竟保住了什麼。

12. 下一次升級架構,要對著量測到的限制動手

當 metadata 的寫入爭用開始影響發布與授權,才評估拆分寫入責任或讀取副本;採用副本前,要確認剛改權限的讀取不能因複寫延遲被放行。當跨區觀眾的第一幀主要卡在授權往返,才評估區域化的許可服務與撤銷資料分發。當轉碼等待超標,先量測輸入特性、profile 成本與工作排程,再決定擴機器或換編碼。

這些改變都會增加需要恢復與驗證的狀態。多區寫入尤其不能只把資料庫換成全球產品就算完成;同一支影片的下架、替換與發布,仍需要清楚的排序與衝突規則。對初期產品,單一寫入區域加跨區媒體分發,通常更容易建立可驗收的基線。是否值得改變,要看已量測的延遲、可用性與營運能力。

回到阿晴的影片,第一版需要做到的事已經很明確:斷線後能續傳,收到原檔後由平台負責處理,基本畫質備妥才提供播放。高畫質可以晚到,卻不能覆蓋較新的播放組合;創作者刪除後,舊工作也不能把它重新公開。

我會先用模組化單體、單一寫入區域與獨立轉碼工作者完成這條路。CDN 承接媒體傳輸,發布交易與短期許可負責決定誰能看哪個版本。等量測指出是哪一段失守,再增加服務或區域;否則每一次拆分,都可能讓原本可在一筆交易裡核對的條件更難維護。

練習:把接手、刪除與兩次完成畫成四筆交易

影片 v42 的 generation 是 7。worker A 正在處理基本畫質,租約過期後 worker B 接手。創作者這時刪除影片,接著 A、B 依序回報成功。請寫出:刪除交易修改哪些欄位?兩次完成請求各自要查哪些條件?哪些物件留下待清理?播放 API 與搜尋結果各應呈現什麼?

再加一個限制:觀眾在刪除前一秒已取得五分鐘的播放許可。產品經理要求十秒內不能再取得任何新片段。原方案哪一份承諾不成立?你會縮短許可、加入邊緣撤銷,還是與產品重新協商期限?寫出代價與故障時的行為。

參考思路

刪除交易推進 generation、記錄 tombstone/撤銷狀態、取消公開資格並寫出 outbox。A 的 attempt token 已過期;B 雖持有目前工作 token,generation 也已不符,所以兩者都不能推進公開狀態。產物可以存在,但不再有有效發布引用,交由可重試清理。新播放許可拒絕,搜尋移除事件追蹤到完成;索引尚未收斂時也不能成為授權來源。

五分鐘許可若僅離線驗證簽章與期限,不能單靠下架資料庫狀態保證十秒撤銷。加入撤銷查詢會增加邊緣狀態同步與故障成本,縮短期限會增加許可更新量。兩種方案都要測出撤銷傳播時間與故障時的行為,才能承諾十秒的期限。

下一步就用兩個 worker stub 和一個資料庫,固定重現「租約過期、刪除、舊結果回來」的順序。檢查兩次完成請求都被拒絕、沒有新的資產啟用事件,而且播放 API 不再核發許可。這個測試通過後,再接入轉碼工具;先把遲到的結果擋住,才有可靠的發布流程可以擴充。