Skip to Content
概念退役

退役

當擁有部門 Worker 的部門被刪除,或其公司被根層級硬刪除而移除了相關資料列時,該部門 Worker 即被退役。退役是永久的:沒有還原路由,也沒有解除鎖定的指令。在雲端,Worker 在執行期持有的一切都會被銷毀;描述它的 TeamSync 資料列(edge_workers、版本、outbox、稽核)則保留,因為被刪除的部門只是軟刪除。雲端退役最早隨後端 v5.13.0 發行;本頁描述後端 5.22.0。

觸發條件

刪除部門(公司 API 的 DELETE /private/company/departments/{department_id},公司管理者):

  1. 在刪除交易中,部門被軟刪除。它的服務帳號被軟刪除,而不是被搬移(每個帳號各寫一筆稽核紀錄,見下方稽核紀錄);只有真人成員會搬到 fallback 部門,之後他們的 Edge Workers 呼叫會指向那個部門的 Worker。
  2. 提交之後,一個盡力而為的 hook 會透過目前的 provider 讓該部門 Worker 退役;它在刪除請求內(於工作執行緒中)執行,所以回應會等它,光是鎖定就以 8 秒為界。無論 hook 結果如何,刪除都回 200 Department '<name>' deleted successfully:hook 失敗只會記錄日誌(Edge Worker department retirement hook failed),並由 15 秒調解迴圈(見下)重試。

硬刪除公司會直接移除它的部門與 Worker 資料列。這些 Worker 由調解迴圈當作孤兒 script(見下)退役。

雲端退役

雲端 provider 對其環境獲准操作的每一個位置採取動作 — 共用位置與該環境自己的舊有位置,由 EDGE_WORKERS_CLOUDFLARE_DISPATCH_NAMESPACE 的設定與 API 環境決定;沒有可用的設定時,不會退役任何東西,也不會寫入任何確認。Worker 會在其部署所記錄的位置退役(以及搬移曾經碰過的任何位置),每個位置各跑一輪。沒有 Worker 資料列的部門會被略過,沒有該 Worker 任何部署的位置(且沒有任何搬移碰過它)也一樣:不做任何 Cloudflare 呼叫,也不寫入任何確認。刪除部門時,若它的某個 Worker 有尚未完成的搬移,刪除會被拒絕:400 worker_migration_state_retained。其餘情況,每個部門、每個位置依序:

  1. 鎖定 — 只在 script 仍帶有即將被覆寫的租戶程式碼時執行,針對 Worker 在該位置目前 deployed 的 release(否則是它在該位置完成的最新 release),每個變更指令都以 control.receipt.ack 確認:

    • 以 builder 政策 department-worker-project-v2.8 或更新版本建置的 release:retirement.lockdown 在 Worker 的 Durable Object 內設下旗標;此後兩條通道上的每個請求,都在路由比對之前回 403 {"code":"worker_retired","message":"Forbidden"},不論 Worker 範圍或逐操作的存取設定為何。沒有任何指令能清除這個旗標。
    • Worker 範圍的存取政策被換成 teamsync_internal 與空的 IP 允許清單(若本來就是則不動),所以沒有旗標的 v2.8 之前 release,其公開通道會以 403 access_denied 拒絕。
    • Worker 機密被清除(v2.6 及更新的 release),所有已送達的服務帳號憑證與快取的 token 也被清除(v2.9 及更新)。能力對照表見提交管線。

    這些控制呼叫共用 8 秒預算(在調解輪內,取該值與該輪剩餘時間中較小者)。被拒絕的呼叫在還有時間時每 0.5 秒重試一次。預算用完或某一步失敗時,provider 記錄 edge_worker_cloud_retirement_lockdown_skipped: <ExceptionClass>,仍然上傳墓碑:鎖定是縱深防禦,真正讓 script 退役的是墓碑。

  2. 墓碑 — script 被覆寫成一個平台模組:沒有租戶程式碼、沒有 Durable Object binding,tags 恰為 ["teamsync-tombstone-v2"],compatibility date 為 2026-08-14,且 Durable Object class TeamSyncDepartmentStorage 被標記為 deleted。因此 Cloudflare 會在 script 仍部署的情況下,刪除該 Worker 的 Durable Object 及其全部儲存與 alarm。上傳之後會讀回;沒有列成墓碑的 script 會重試(cloudflare_tombstone_unconfirmed)。script 在退役後保留,之後自動移除:Cloudflare 可能在持有它的邊緣機器上繼續執行已刪除的 dispatch script,且沒有文件化的界限,所以退役改用覆寫。namespace 沒有列出的 script 不會被建立,唯一例外是該 Worker 與 namespace 已有 department.worker_script_deleted 資料列:此時不存在的 script 也會得到墓碑。

  3. 確認 — 稽核資料列記錄這次退役(見下方稽核紀錄)。只有在沒有任何部署還能把租戶程式碼落到墓碑之上時,該部門才算在此 namespace 完成:此 Worker 在此 namespace 沒有 pending 或 leased 的部署列、沒有未完成的 uncertain 列,也沒有在最近 15 分鐘內結束的列。在那之前,後續各輪會重新讀取 script,並覆寫任何不是墓碑的內容。

墓碑對每個請求 — 任何路徑、任何 method、公開或簽章通道 — 都回 HTTP 404、cache-control: no-store、一個 x-teamsync-request-id header(轉送進來的請求 id 若是 UUID 就沿用,否則產生新的)以及 body {"code":"worker_not_found","message":"Worker not found","requestId":"<uuid>"}。這與 dispatcher 對不存在 script 的回應是同一組 code 與 message,所以已退役的 Worker 與從未存在的 Worker 無法區分。

被銷毀的與保留的

雲端
Durable Object:執行期儲存、存取政策與 API 金鑰、操作設定、runtime config、Worker 機密、服務帳號憑證、控制回執由 Cloudflare 刪除(墓碑把該 class 標記為已刪除)
Script退役後以墓碑的形式保持部署,之後自動移除
部門與其服務帳號軟刪除
edge_workers、edge_worker_versions、outbox 與稽核資料列保留
Script 名稱永不重用:它由公司與部門 id 推導,而部門 id 永遠不會被重用

呼叫端看到什麼

時機回應
hook 尚未觸及 Worker 之前正常回應
鎖定之後(release v2.8 或更新)兩條通道:403 worker_retired Forbidden
政策關閉之後(release 早於 v2.8)公開通道:403 access_denied Forbidden
仍持有舊 release 的邊緣機器,在 Cloudflare 移除已刪除的 Durable Object class 之後503 worker_unavailable Worker unavailable
墓碑404 worker_not_found Worker not found,此後一直如此

邊緣機器是逐台切換到墓碑的,TeamSync 的程式碼並未限定某台機器還會繼續服務舊 release 多久。因此呼叫端可能先看到 403 worker_retired,接著在仍執行舊 release 的機器上、Durable Object 消失之後看到 503 worker_unavailable,最後所有地方都是 404 worker_not_found。TeamSync 自己掌控的界限是上傳前的 8 秒鎖定預算與 15 秒重試間隔。用戶端應把 403 worker_retired 與 404 worker_not_found 視為最終結果並停止重試。

透過 Edge Workers API,被刪除的部門就是不存在了:針對它的公司管理 Edge Workers 路由回 404 Department not found,與不存在或屬於其他公司的 id 相同;它原本的成員則改用新部門的 Worker。

調解迴圈

每個 API replica 在 60 秒回收迴圈旁(見背景任務)還會跑第二條背景迴圈:它執行一輪 provider 調解,然後睡 15 秒。下列數值全是程式碼常數,沒有任何環境變數能改。

界限值
迴圈間隔每輪結束後 15 秒
單輪逾時20 秒;前一輪仍在執行的 tick 會被略過(Edge Worker host reconcile skipped: the previous pass is still running)
每輪的新退役10 秒之後不再開始任何一個
每個 API replica 每輪的 Cloudflare 退役數3(失敗也算),其餘等後續輪次
Cloudflare 回 429整個 namespace 跨 replica 暫停退役 Retry-After 秒(夾在 1–300,缺省 60),並結束該輪;暫停記在 Redis — 沒有 Redis 時只剩每輪上限限制呼叫數
鎖定預算8 秒,重試延遲 0.5 秒
卡住門檻單一 script 持續失敗 300 秒(每個 API replica 各自計)
上傳寬限15 分鐘
Script 管理 HTTP連線 5 秒、讀取 30 秒、不跟隨轉址;失敗或無法解讀的回應由後續輪次重試

雲端這一輪處理的是:Worker 在此 namespace 有部署(或搬移),但沒有針對該 namespace 的 department.worker_script_neutralized 確認的已刪除部門,加上孤兒 script,順序隨機。每個 namespace 各自保有自己的確認。墓碑在退役後保留,之後自動移除。

維運人員要盯的日誌:

日誌等級意義
edge_worker_cloud_retirement_tombstonedINFO一個 script 被覆寫
edge_worker_cloud_retirement_deferred: Cloudflare rate-limit backoffINFO429 暫停生效;稍後重試
edge_worker_cloud_retirement_retry: <code>WARNING可重試的 provider 錯誤
edge_worker_cloud_retirement_rejected: <code>ERROR其他 provider 錯誤
edge_worker_cloud_retirement_failedexception非預期錯誤
edge_worker_cloud_retirement_stuck: <code> for <N>s (<script_name>)WARNING該 script 已持續失敗 300 秒以上;需要處理
edge_worker_cloud_retirement_lockdown_skipped: <ExceptionClass>WARNING鎖定沒有完成;墓碑仍然上傳了
edge_worker_cloud_retirement_backoff_unavailableWARNING429 暫停所需的 Redis 無法使用
Edge Worker host reconcile failed: <exc>WARNING該輪拋出例外或逾時

退役之後的部署與控制變更

  • 刪除前已排入佇列的部署(雲端): 部署任務在取得租約之後、讀取產物之前檢查部門;部門已刪除(或不存在)時,票證結算為 failed、error_code: "department_deleted",且不上傳任何東西 — 現在上傳會讓已刪除部門的 Worker 復活。已在 GET-only 調解中的部署只讀取 provider,會略過此檢查。見背景任務。
  • 為已硬刪除公司排入佇列的部署: 回收掃描把 Worker 資料列已不存在的部署列結算為 failed、worker_deleted,而不是重新發佈。
  • 進行中的控制變更: Worker 已退役的存取政策、runtime config、操作設定、機密或服務帳號意圖,會在不呼叫 provider 的情況下結算:尚未讀取回執的意圖被取消(worker_retired,不寫稽核列),尚未確認的回執則停止重試。見稽核與控制面稽核。

被硬刪除的公司:孤兒 script

公司的根層級硬刪除會移除部門資料列;edge_workers 與 edge_worker_versions 資料列隨之消失(外鍵級聯)。Outbox 與稽核資料列留存,是這個資料庫部署過哪些 script 的唯一證據。當某 Worker 的 deploy outbox 列屬於此位置(依它所記錄的服務位置,否則依它入列時帶的 namespace;沒有 namespace 的舊有列只屬於那兩個舊有位置)、沒有任何部門資料列擁有它的部門 id(任何公司皆然)、沒有 edge_workers 資料列擁有它的 id,且此 namespace 尚未確認時,雲端調解輪就把它視為孤兒。這個資料庫從未部署過的 script 絕不會成為候選。

對每個孤兒(與該輪共用 10 秒預算與 3 次呼叫上限):script 名稱是該 Worker 的部署所存下的名稱(只有在沒有任何部署記錄過名稱時,才由公司與部門 id 推導);若有任何 edge_workers 資料列存著該名稱,該輪記錄 ERROR edge_worker_cloud_orphan_owned: <script_name>,且絕不碰那個 script。否則 script 被放上墓碑(不做鎖定:沒有任何資料列指向它的 release),記錄 INFO edge_worker_cloud_orphan_tombstoned: <script_name>,並在 15 分鐘上傳寬限過後以 department.worker_script_neutralized 確認。孤兒掃描失敗時記錄 edge_worker_cloud_orphan_scan_failed,該輪仍會處理已刪除的部門。

運作規則: 每個 dispatch namespace 必須恰由一個資料庫寫入。匯入公司的第二套環境會保留部門 id,因此也保留 script 名稱,必須使用自己的 namespace。

孤兒報告

唯讀的計數,需要該環境的 dispatch-namespace 憑證。它讀取資料庫並列出 namespace 的 script(分頁 GET,最多 100 頁),然後印出一個 JSON 物件(鍵排序)並以 0 結束:

鍵計數
namespace此環境的 dispatch namespace(字串)
worker_rowsedge_workers 資料列
stored_name_differs儲存的 script_name 與現行推導不同的資料列(部署與退役使用儲存的名稱)
orphans_pending此 namespace 尚未確認的、已硬刪除公司的 Worker
orphans_unsettled其中仍可能被部署觸及的(在 15 分鐘寬限內)
listednamespace 內名為 dw- 加 48 個 hex 的 script
unowned列出的 script 中,沒有任何 edge_workers 資料列儲存的
unowned_serving_code無主 script 中,tags 不恰為 ["teamsync-tombstone-v2"] 的

硬刪除公司之後,orphans_pending 應逐步歸零,unowned_serving_code 應為 0(但有一點例外:在共用位置,已搬移 Worker 的線上 script 會被回報為無主)。列表失敗時會拋出 Cloudflare script listing answered <status>,超過 100 頁則拋出 Cloudflare script listing did not end。

地端

在地端 kit(EDGE_WORKERS_PROVIDER=onprem)上,同樣的鎖定與清除會先執行(release 支援時,機密與憑證的資料表是被刪除,而不只是清空),而且必須成功:失敗時不會通知 host,由 15 秒迴圈重試(日誌 edge_worker_onprem_retirement_failed)。接著 edge host 讓該 script 退役,此後它的 fallback 對該 script 回 404 worker_not_found。host 保留 Worker 的資料。同一個迴圈也會讓任何在資料庫中沒有存活 Worker 的已服務 script 退役(公司硬刪除),但只在資料庫與 host 已配對時;未配對時記錄 edge_host_unpaired 並且什麼都不退役。公開 URL 會在刪除後約一分鐘內回 404。部署若觸及已退役的 script,會以 edge_host_script_retired 失敗。維運程序:地端。

稽核紀錄

退役會寫入 edge_worker_audit_logs 資料列(從資料表讀取;沒有任何 API 公開它們)。四個確認都是系統資料列(actor_type="system"、actor_id=""、auth_method="system"、resource_type="edge_worker"、resource_id = Worker id、result="success"),使用確定性 id,並立即提交。

actionresource_type寫入時機reason_code
service_account.deletedservice_account每個服務帳號一筆,在部門刪除交易內;auth_method="department_delete",actor 為執行刪除的使用者(或 system);digest = 正規化 {"deleted":true,"reason":"department_deleted","service_account_id":"<id>"} 的 SHA-256—
department.worker_script_deletededge_worker每次嘗試處理該 script 之前(不一定接著上傳),作為「此 namespace 已被動作」的標記namespace
department.worker_retirededge_worker該 Worker 有部署或搬移的每個位置,都已有自己的 department.worker_script_neutralized 確認(各自在 15 分鐘上傳寬限過後);在雲端,從未有部署觸及的 Worker 不會寫入此稽核列;地端則是在 host 讓它退役之後—
department.worker_script_neutralizededge_worker此 namespace 的雲端退役已結清(上傳寬限已過);孤兒也一樣namespace
department.worker_tombstone_deletededge_worker已退役 Worker 的墓碑 script 已從此 namespace 移除namespace

在控制面稽核通道中,當 Worker 的部門已軟刪除且存在 department.worker_retired,或其公司已被硬刪除(沒有任何公司有該 id 的部門資料列,也沒有 Worker 資料列)時,該 Worker 即視為已退役。

Last updated on