PIF AI Whitepaper

第 19 章:營運可觀測性與邊緣拓撲

本章從一個具體問題開始:一個當天註冊的租戶建完產品就離開,我們想知道他卡在哪——結果發現唯一的線索是容器的 docker logs,而那是易失的。追查過程意外揭開一個更嚴重的問題:平台對「請求從哪裡來」的判定,在三個不同的地方都是錯的,而三處的後果完全不同。

📌 本章重點

19.1 問題:租戶為什麼走掉

一位租戶當天註冊、上傳標籤三次、建立產品、嘗試從產品產生毒理報告、查看方案價格,然後離開。我們想知道他卡在哪一步。

當時能查到的只有 docker logs——138 行,涵蓋兩天,容器一重建就消失。這次查得到純屬運氣:他當天早上操作,而容器剛好幾天沒重建過。同一批更早流失的租戶,已經無從查起。

追出來的原因值得記錄,因為它不是 bug:台灣化粧品標籤依法必列全成分,AI 每次都讀到了,但當時的系統提示詞明寫「全成分不擷取」。他上傳了三次標籤,系統三次讀到成分又丟掉,最後請他對著空白表格手動重打整張配方。他沒打,去看了價格,離開了。

這個結論只能事後從程式碼推出來,無法從資料推出來——因為資料沒有被留下。

19.2 足跡:用既有的稽核表

修法不是新開一張分析表,而是沿用既有的 audit_logs。理由有三:

  1. 它已經是 append-only(DB trigger 擋 UPDATE/DELETE)——行為紀錄本來就不該被改。
  2. 它已有租戶隔離與索引(第 8 章)。
  3. 新開一張表會製造第二個真相來源:同一件事可能在兩張表裡狀態不一致。

行為事件以 funnel. 前綴與既有的文件 CRUD 稽核區分,涵蓋註冊、登入、標籤解析、成分分析、報告預覽、報告產出、方案瀏覽、進入結帳,以及兩類最有價值的受阻事件:撞到付費牆、額度用盡。

撞牆的人是「想用但用不到」的人——這是最接近成交、卻在修補前完全不可見的一群。

19.2.1 遙測不得沿用呼叫端的交易

一個容易寫錯的細節:既有的稽核寫入契約是「只 db.add(),由呼叫端的 commit 一併落地」——這對業務稽核是對的,稽核紀錄應與業務變更同生共死。

但遙測不能照做。若遙測在 POST 端點裡自行 commit,會把當下尚未完成的業務變更一併提交;若不 commit,GET 端點的事件則永遠不落地。解法是遙測開自己的 session,完全不碰呼叫端的交易,並整段包 try/except——記不到 log 不是業務錯誤

19.2.2 保留期:守門人是資料庫

行為紀錄含 email、IP 與完整行為軌跡,構成個人行為檔案,因此保留期為三年、且僅平台管理員可讀。

實作上的關鍵是邊界寫在哪audit_logs 原本無條件擋 DELETE;改為只放行 created_at 已過保留期的 DELETE,UPDATE 則永遠擋(不可竄改與保留期是兩件事)。清除工作只是執行者——它寫錯、被誤呼叫、或參數算錯時,未到期的紀錄在資料庫層就刪不掉。

清除採分批(單次上限數千列):這張表在請求路徑上持續有寫入,一次刪光會長時間持有鎖。

19.3 三種傷害,同一個根因

建立足跡後第一次驗證,發現記下的 IP 全部是 127.0.0.1。追查根因時才發現,同一個錯誤在平台的三個地方各造成一種不同的傷害。

19.3.1 邊緣拓撲

正式環境的請求鏈是:

Cloudflare → nginx:443(L4 stream,依 SNI 分流)→ 127.0.0.1:8443(nginx http)→ 應用容器

L4 層不改動 HTTP 標頭,但連到 8443 的來源就是本機,因此在 8443 這層 $remote_addr 恆為 127.0.0.1。而該層的設定為了防偽造,把 CF-Connecting-IP 覆寫成 $remote_addr——於是每個人都變成 127.0.0.1

真實來源仍在 X-Forwarded-For 中。存取記錄可以直接對照:$remote_addr = 127.0.0.1,而 $http_x_forwarded_for 是真實的外部位址。

19.3.2 兩種常見寫法都是錯的

寫法 在本拓撲下的結果
優先讀 CF-Connecting-IP 每個人都是 127.0.0.1(該標頭被刻意覆寫)
X-Forwarded-For 第一段 取到客戶端自己塞入的偽造值

第二點的成因是:CDN 是把真實 IP 附加在 XFF 尾端,客戶端自帶的值留在前面。因此正解是取 XFF 中最後一個非私有位址——偽造值(在前)被跳過,自家 hop(私有位址)被跳過,剩下的就是 CDN 認定的來源。

這個取法在本拓撲下不可偽造,前提是 L4 層對平台網域只接受 CDN 來源(非 CDN 的外部連線導向黑洞)。繞過 CDN 直連來源會在連線層被重置,因此能到達應用層的請求必然經過 CDN。

19.3.3 三處後果完全不同

使用處 錯誤後果 嚴重度
足跡遙測 每筆都記同一個值,IP 欄位失去意義 資料無用
per-IP 登入節流 所有人共用一個計數桶 可用性:任何人累積上限次登入失敗,全平台使用者一起被鎖
簽署來源 IP 簽署人可自行指定紀錄上的 IP 證據性:欄位用途反轉

第二項最嚴重。per-IP 防爆破的上限刻意設得寬鬆(考量共用 NAT 與企業出口),但在所有人塌成同一個桶之後,那個寬鬆上限變成全平台共用的鎖——不需要任何權限,累積上限次失敗即可讓所有租戶登不進來。而症狀(沒有錯誤訊息、重試更糟)與一次真實的登入事故完全相同,極易誤判方向。

第三項的性質不同:簽署來源 IP 與內容雜湊、版本綁定、簽署人資格快照同屬構成不可否認性的欄位組。若簽署人能決定紀錄上寫哪個 IP,這個欄位的用途就被反轉了——它本來要證明「你在這裡簽的」,變成「你說你在哪裡簽的」。

修補時機值得記錄:當時尚未發生任何實際簽署,因此不會產生「同一欄位兩種語意」的既有紀錄混雜。證據性欄位的語意改動,越早越便宜。

19.3.4 收斂為單一來源

三處原本各有一份實作、語意互不相同。修補後全平台只有一份 IP 解析,並以測試鎖住兩件事:任何模組不得再自行解析、以及行為驗證——直接送入含偽造前綴的 XFF,斷言取到的是 CDN 附加的真實來源。

行為驗證比字串比對可靠:即使日後有人改寫成別的寫法,只要行為錯了就會失敗。

19.4 觀察與限制

本章的教訓可以壓縮成一句:沒有錯誤訊息的失效最貴。IP 全記成 127.0.0.1 不會拋例外、節流塌成全域桶不會拋例外、簽署 IP 被偽造也不會拋例外。它們只能靠對機制本身的懷疑、以及行為層的驗證找出來。

📚 參考資料

📝 修訂記錄

版本 日期 摘要
v0.4 2026-08-24 首次撰寫。涵蓋易失證據問題、沿用 audit_logs 的足跡設計、遙測獨立交易、保留期邊界寫在 DB trigger、邊緣拓撲下 IP 解析的兩種常見錯誤寫法、同一根因造成的三種不同傷害(遙測/防爆破/簽署證據)、收斂為單一來源與行為驗證。

© 2026 Baiyuan Tech. Licensed under CC BY-NC 4.0.

導覽 ← 第 18 章:計費模型與服務履約 · 附錄 A:縮寫與術語 →