題幹與適用場景
你維護檔案、組織或知識圖譜 API,資源可以透過別名或繫結指向其他資源。用戶端要求遞迴展開,類似 WebDAV 的 Depth: infinity。請設計遍歷演算法、循環回應、深度與節點預算,並說明何時不該使用 508。題目適合後端、儲存與平台職位。
RFC 5842 將 508 定義為服務端在處理無限深度操作時發現循環並終止整個操作;它不是一般重新導向循環或 CPU 逾時的通用錯誤碼。假設資源圖可能跨租戶,邊與節點都要經過權限檢查。
面試官考察點
- 能否把樹遍歷問題辨識成有環圖,而不是只遞迴到堆疊溢位。
- 能否區分「再次遇到同一資源」與「遇到目前路徑上的循環」,選擇路徑集合與 visited 集合的不同職責。
- 能否為深度、節點數、回應大小與查詢時間設定預算,並給用戶端可採取行動的診斷資訊。
普通回答只說「加一個遞迴深度」。強回答會說明資源身分正規化、路徑循環與共享子圖、預算耗盡與 508 的邊界,並涵蓋多實例快取與權限洩漏風險。
回答前需要澄清的問題
- 資源關係是樹、DAG,還是允許任意有向圖?DAG 仍需 visited 來避免重複展開,任意圖還要偵測目前路徑的循環。
- 用戶端要完整展開、分頁結果,還是只驗證可達性?輸出目標會決定是否可用 208、部分結果或非同步任務。
- 資源 ID 是否全域唯一?若繫結跨租戶或允許別名,必須先正規化身分,否則同一節點可能被誤判成不同節點。
- 預算由誰設定?服務端必須有硬上限,用戶端深度參數不能直接決定資料庫與記憶體消耗。
30 秒回答框架
「我先把關係建模成有向圖,正規化每個資源的穩定 ID。遍歷時維護目前路徑集合來發現真正的循環,同時維護全域 visited 避免重複展開共享子圖。服務端對深度、節點、邊、回應位元組與耗時設定硬預算;發現循環可以回傳 508,預算耗盡則回傳明確的限制錯誤或非同步任務狀態。結果帶有循環邊、截斷原因與請求 ID,但不洩漏無權存取的節點。測試要涵蓋自我循環、跨別名循環、共享子圖、權限拒絕與惡意超深圖。」
分步驟深入解答
1. 先確定 508 的邊界
RFC 5842 的 508 針對遞迴資源操作發現無限循環。若是一般 URL 重新導向,應使用重新導向鏈保護;若只是逾時或預算耗盡,不應偽裝成 508。狀態碼表達原因,回應本體再提供診斷欄位。
2. 正規化資源身分
把別名解析成租戶、資源類型與不可變 ID 的三元組。路徑字串、大小寫或不同 URL 不能直接作為 visited 鍵。解析別名本身也要有 hop 上限,避免在進入圖遍歷前就循環。
3. 同時維護路徑集合與 visited 集合
path 表示目前 DFS 分支;邊指向 path 中的節點才是循環。visited 表示整個請求已完成或排隊的節點,用於共享子圖去重。把兩者混成一個集合,會把合法的菱形圖誤報成循環,或漏掉另一條分支上的循環。
4. 設計預算與截斷
至少限制最大深度、節點數、邊數、回應位元組與牆鐘時間。預算按租戶與請求級別計算,資料庫查詢使用分頁。預算耗盡時回傳截斷原因、已處理計數與繼續方式;若協定要求完整結果,建立非同步遍歷任務,而不是回傳看似成功的部分樹。
5. 處理權限與快取
先做資源級授權,再把節點放入可見結果。快取鍵必須包含租戶、權限版本與遍歷參數;不能因一個租戶曾看過節點,就讓另一個租戶透過循環診斷推測其存在。高風險圖可快取解析後的鄰接邊,但每次請求仍重新檢查授權。
6. 選擇回應形式
發現循環且用戶端支援診斷時,回傳 508,提供循環邊的脫敏 ID、截斷位置與請求 ID。若用戶端只需要盡力結果,可回傳成功的分頁集合並標示 truncated;這與 508 的「整個操作失敗」語意不同。不要把資料庫堆疊溢位或反向代理自循環都統稱為 508。
7. 驗證攻擊與失敗路徑
測試自我循環、A 指向 B 指向 A、同一節點多別名、共享子圖、深度剛好達上限、超大扇出、跨租戶無權邊與逾時。斷言每個節點最多展開一次,權限拒絕不改變可見計數,錯誤回應也不洩漏隱藏資源 ID。
高品質示範回答
「我會把遞迴展開當成有向圖問題。先把別名解析為租戶加穩定資源 ID,再用目前路徑集合偵測真正循環,用全域 visited 去重共享子圖。深度、節點、邊、回應大小與耗時都設硬預算,並把預算傳到分頁查詢。發現循環時,只有在這是遞迴資源操作且用戶端能理解時才回傳 508;一般重新導向循環或單純逾時使用相應錯誤。回應只返回經授權的脫敏循環資訊與請求 ID。快取鍵要包含租戶、權限版本與參數。測試涵蓋自我循環、別名循環、菱形圖、跨租戶邊與惡意超深圖,確保單次請求不會無限消耗資源。」
常見錯誤
- 把最大深度等同於循環偵測 → 合法深層樹也被拒絕,真正淺層循環可能仍隱藏 → 用 path 集合偵測循環,深度只作預算。
- 只維護全域 visited → 共享子圖被誤報為循環 → 區分目前路徑與全域存取狀態。
- 所有逾時都回傳 508 → 用戶端無法區分圖循環與資源過載 → 讓狀態碼反映真正失敗原因。
- 先展開再授權 → 錯誤內容可能洩漏隱藏節點 → 在資源級別執行授權與結果計數。
- 快取不含權限版本 → 舊權限下的結果繼續可見 → 綁定租戶、權限版本與遍歷參數。
追問及應對
如果圖是 DAG,為什麼仍需要 path 集合?
資料模型宣稱是 DAG 不代表輸入永遠可信;遷移、別名或並行寫入可能暫時形成循環。path 集合是低成本的執行期保險,若發現循環還應記錄寫入來源並阻止新的繫結。
客戶要求「盡量回傳已找到的節點」,還能回傳 508 嗎?
不能把部分結果偽裝成 508 的完整失敗。可以定義明確的分頁或非同步協定,回傳已完成頁、截斷原因與繼續游標;只有用戶端要求原子完整展開時才用 508 終止操作。
如何防止惡意租戶用高扇出圖耗盡資料庫?
為租戶設定並行、節點、邊、查詢時間與回應位元組配額,批量預取受限並採用背壓。超限請求進入非同步佇列或被拒絕,監控每租戶消耗與失敗原因,不讓用戶端任意提高深度繞過預算。