改了設定要不要重啟?關鍵在「每次請求即時讀」還是「啟動時快取」

現在 code 都讓 AI 寫了,這種判斷它靠得住嗎? 你在資料庫改了一筆設定——權限、feature flag、某個閾值——接著要決定:改完到底要不要重啟服務才生效? 放在以前,你會憑經驗猜:保守派一律重啟(常常多此一舉、白白製造一次中斷),樂觀派賭它即時生效(結果改半天前端還吃舊行為,debug 一小時才發現要重啟)。但現在多了一個選項,也是大多數人實際在做的——直接問 AI。於是真正的問題變成:這種「要不要重啟」的判斷,AI 答得對嗎? AI 會怎麼判斷——而且為什麼常常自信地答錯 先說結論:這題剛好是 AI 最容易出錯的類型,而且會錯得很有自信。 AI 寫 code 的本質,是從訓練資料的 pattern 推測最可能的答案。而「改設定要重啟」在無數系統裡都成立,於是它傾向很篤定地叫你重新部署,卻沒去確認你眼前這份程式碼到底是 live 讀還是快取。版本差異更是重災區——同一個框架舊版快取、新版改成 live 讀,AI 常常混為一談,給你一個「看起來很對」的錯答案。 問題的本質其實沒變,只是換了位置:從前要自己判斷,現在要判斷「AI 的判斷對不對」。而要驗證它、或反過來把它導對,你得先有一把尺——一條不靠經驗、可以拿原始碼直接對答案的準則。 那把尺:即時讀,還是啟動時快取? 決定「改完要不要重啟」的,從來不是設定存在哪裡,而是程式什麼時候去讀它: 讀取時機 改完是否即時生效 典型例子 每個請求即時讀(per-request live read) ✅ 即時,不必重啟 每次查 DB 的權限、即時拉的 feature flag 啟動時載入記憶體後快取(boot / in-memory cache) ❌ 必須重啟或等過期 bootstrap 載入的設定、process 內的計數器 換句話說:改 DB 要不要重啟,等於問「這個值是 live 讀還是被快取了」。把這句話記住,後面所有案例都是它的推論。 麻煩的是,框架很少在文件裡明講某個值屬於哪一類,AI 也答不準。所以正確的態度不是查文件、更不是憑經驗猜,而是翻原始碼,看那個值是在「請求路徑」上被讀,還是在「啟動路徑」上被讀。下面用一正一反兩個真實案例,把這把尺磨利。 實例一:Strapi 權限「改 DB 即刻生效」 需求很單純:把兩個 content-type(就叫它 article 與 category)的 CUD(Create / Update / Delete,建立/更新/刪除)權限,授給幾個自訂角色。Strapi(一個 Node.js 的 headless CMS)的權限存在資料庫的 up_permissions 表,所以最直接的做法就是下 SQL 把對應的 grant 寫進去。 ...

June 19, 2026 · 3 分鐘 · Peter

EKS 維運的三個隱形陷阱:工具 Pod 消失、事件蒸發、審計空白

起因:工具 Pod 人間蒸發 某天要查資料庫,照慣例執行 kubectl exec -it psql-client,結果 Pod 不見了。 這個 psql-client 是用來連 RDS PostgreSQL 的工具容器,平常拿它跑 SQL 查詢、檢查資料表大小。問題是——沒有人記得刪過它,也沒有任何記錄顯示是誰或什麼原因讓它消失。 這件事本身影響不大,重建一個就好。但它暴露了三個更深層的問題:為什麼 Pod 會無聲消失?為什麼查不到是誰刪的?為什麼整個叢集沒有留下任何操作軌跡? 陷阱一:裸 Pod 沒有人管它的死活 問題本質 當初建立 psql-client 的指令大概是這樣: kubectl run psql-client --image=postgres:15-alpine --restart=Never -- sleep infinity 這行指令建立的是一個裸 Pod(Bare Pod)——直接建立 Pod 物件,不隸屬於任何 Deployment、ReplicaSet 或 StatefulSet。 裸 Pod 和 Deployment 管理的 Pod,差別在於有沒有 Controller 在背後看著它。Deployment 的 ReplicaSet Controller 會持續監控 Pod 數量,少一個就補一個。裸 Pod 沒有 Controller,它的生死完全取決於節點的命運。 flowchart TD subgraph 節點故障發生 A[Node 異常關機] end A --> B[裸 Pod] A --> C[Deployment Pod] B --> D[Pod 永久消失] D --> E[無人知曉] C --> F[ReplicaSet Controller 偵測] F --> G[在其他 Node 重建 Pod] G --> H[服務自動恢復] classDef default fill:#f5f5f5,stroke:#333,color:#333 classDef error fill:#f8d7da,stroke:#dc3545,color:#721c24 classDef success fill:#d4edda,stroke:#28a745,color:#155724 classDef info fill:#d1ecf1,stroke:#17a2b8,color:#0c5460 class A info class D,E error class G,H success 不處理會怎樣 裸 Pod 在以下情境會永久消失: ...

May 16, 2026 · 3 分鐘 · Peter

ORM 在騙你:當 populate / include 悄悄失效

症狀:資料明明在,前端就是拿不到 一個 CMS 系統的文件列印預覽,每份文件 header 都固定顯示「上傳者:—」。DB 裡明明有資料,API 回 HTTP 200 OK,回應裡的其他欄位(日期、文件類型、附件)都對——唯獨 uploadedBy 是 null。 沒錯誤、沒警告,伺服器 log 也乾淨。前端碰到 uploadedBy?.realName || '-' 就乖乖畫 dash。使用者以為這份文件沒指派擁有者,作者本人以為畫面壞了。大家都錯,但系統看起來好好的。 這是最難纏的 bug——靜默失敗。 先看 ORM 層在整個架構哪裡 要理解為什麼這類 bug 發生、也為什麼解法是「繞過 ORM」,先把架構分層看清楚: flowchart TD A[前端 / Client<br/>Vue, React, App] --> B[Web / API 層<br/>Controller, Route, 權限檢查] B --> C[業務邏輯層<br/>Service / Use Case] C --> D[ORM 層 ⭐<br/>Strapi Entity Service, Prisma, TypeORM,<br/>Mongoose, SQLAlchemy ORM] D --> E[Query Builder 層 可選<br/>knex, SQLAlchemy Core] E --> F[DB Driver<br/>pg, mysql2, mongodb] F --> G[資料庫<br/>PostgreSQL, MongoDB, ...] classDef default fill:#f5f5f5,stroke:#333,color:#333 classDef orm fill:#d1ecf1,stroke:#17a2b8,color:#0c5460 classDef db fill:#d4edda,stroke:#28a745,color:#155724 class D orm class G db 為什麼 bug 只會發生在 ORM 層? ...

April 24, 2026 · 2 分鐘 · Peter

頭貼切不回去?一個 Bug 揪出 Admin Panel 權限漂移的跨後端通病

症狀:選完手機照片,畫面卻還是貼圖 使用者回報的步驟很簡單:點頭像 → 選「從手機圖庫」→ 選張照片 → 儲存。跳回會員中心,頭像還是原本的系統貼圖。 再點進去編輯頁,看到的又是手機照片。明明存成功了,為什麼 UI 顯示不一致? 這個 bug 表面上是前端畫面問題,但用 log 逼出來的真相,藏在三層之外——包括一個會在幾乎所有 admin-panel 型後端都出現的經典陷阱。 偵錯:跟著 log 走,別信錯誤訊息 第一步是打開前端 log 重現流程。關鍵幾行: StickerService -> Deactivating all stickers StickerService -> Error: status code of 403 StickerService -> Error Response: {status: 403, message: Forbidden} StickerService -> Deactivate all failed: You do not own this sticker 最後這句「You do not own this sticker」幾乎讓我停在錯誤方向——去查使用者到底擁有哪幾個貼圖。但翻前端程式碼才發現:那不是後端回的訊息,是前端 _handleDioError 把「403 Forbidden」硬翻譯成這句。 **這是第一個教訓:HTTP 狀態碼與文字訊息之間的語意對應是會說謊的。**真正的錯誤只有一個字——Forbidden。跟 ownership 無關,純粹是權限問題。 第一層根因:Admin Panel 的權限設定漂移 翻 Strapi admin → Settings → Users & Permissions → Roles → Authenticated → Sticker section,發現 deactivateAll 這支 route 的權限沒勾。 ...

April 17, 2026 · 3 分鐘 · Peter

Fastlane CI 突然壞了:一個被遺忘的 Keychain 依賴如何讓 Build 連續失敗

Build 突然壞了,但沒有人改過 CI 設定 iOS 的 Jenkins pipeline 突然開始失敗。上一版(058)還好好的,下一版(059)就掛了。再跑一次(060),還是掛。 第一時間檢查 git diff —— 兩版之間只有 Dart 程式碼改動,沒有任何人碰過 Fastfile、Jenkinsfile、或 CI 相關設定。程式碼改動是 Flutter 層的 bug fix,跟 iOS build 流程完全無關。 這是最令人困惑的情境:什麼都沒改,但 CI 壞了。 先看一下各版的 build 結果: Tag Build 結果 耗時 prod-0.1.04+2026000058 #1 SUCCESS 9.5 分鐘 prod-0.1.04+2026000059 #1 FAILURE 2.3 分鐘 prod-0.1.04+2026000060 #1 FAILURE 2.3 分鐘 059 和 060 都只跑了 2 分多鐘就掛了 —— 連 Flutter build 都沒跑到,在 Fastlane 的前期設定階段就失敗了。 先理解 Fastlane 的 Keychain 機制 在深入除錯之前,先理解 iOS code signing 在 CI 環境的運作方式。 ...

April 11, 2026 · 5 分鐘 · Peter

Kubernetes 節點 DiskPressure 事故:部署失敗到緊急救援的完整記錄

事發:部署成功但服務掛了 一次例行的 Strapi 後端部署,Jenkins build 成功、Docker image 推上 ECR、kubectl set image 也順利執行。但 rollout 等了 300 秒後超時。 以下是整個部署流程,可以看到問題出在最後一步: flowchart LR A[Jenkins Build] -->|✅ 成功| B[推送 Image 到 ECR] B -->|✅ 成功| C[kubectl set image] C -->|✅ 成功| D[rollout status 等待] D -->|❌ 超時 300s| E[自動回滾] classDef success fill:#d4edda,stroke:#28a745,color:#155724 classDef error fill:#f8d7da,stroke:#dc3545,color:#721c24 class A,B,C success class D,E error 具體的錯誤訊息: error: timed out waiting for the condition Jenkins 回報「部署失敗,已自動回滾」。奇怪的是,build 每一步都成功了,問題出在 rollout 階段。 調查:Pod 起不來的真正原因 問題排查的過程如下,從 pod 狀態開始一路追到節點層級: ...

March 26, 2026 · 4 分鐘 · Peter

Flutter iOS 冷啟動閃退:Debug 模式的隱藏陷阱

問題現象:Xcode 正常,冷啟動閃退 最近在開發 Flutter iOS App 時遇到一個詭異的問題:透過 Xcode 按下 Run 按鈕啟動 App 完全正常,但當我停止 Debugger、從多工畫面滑掉 App、再從主畫面點擊圖示重新開啟時,App 只顯示 Launch Screen 就立刻閃退。 這個問題讓我走了不少彎路,最後發現答案簡單得令人意外。 誤導方向:UIScene 與 Plugin 註冊 由於 Xcode 26 開始顯示 UIScene lifecycle will soon be required 警告,我最初懷疑是 UIScene 遷移不完整導致的問題。接著又懷疑是 Plugin 註冊的 race condition,甚至在 GeneratedPluginRegistrant.m 中加入大量 debug log 來追蹤每個 Plugin 的註冊狀態。 嘗試過的「修復」包括: 完整實作 SceneDelegate 與 FlutterSceneDelegate 在 Flutter Engine 初始化前加入 0.5 秒延遲 逐一排除可能有問題的 Plugin 這些方向全部是錯的。 找到根本原因:iOS Console Log 關鍵轉折點是查看 iOS 的 Console log。使用 Xcode 的 Devices and Simulators > Open Console 或 macOS 的 Console.app 連接 iPhone,重現冷啟動閃退後,看到了這段關鍵訊息: ...

February 17, 2026 · 2 分鐘 · Peter

AWS S3 Upload Failed: The Bucket Does Not Allow ACLs

問題發生 在將 Production 環境複製到 Staging 環境後,發現 Strapi CMS 無法上傳圖片到媒體庫,畫面只顯示 Internal Server Error。 Strapi 是一個開源的 Headless CMS(無頭內容管理系統),可以讓開發者快速建立 API,並提供管理後台來管理內容。在這個專案中,我們使用 Strapi 搭配 AWS S3 來儲存上傳的圖片和檔案。 追查過程 第一步:查看 Kubernetes Logs 由於 Strapi 部署在 EKS(Elastic Kubernetes Service,AWS 的託管 Kubernetes 服務)上,我透過 kubectl 指令查看 Pod 的日誌: kubectl logs -f deployment/strapi-stg --tail=100 kubectl 是 Kubernetes 的命令列工具,用來與 Kubernetes 叢集互動。logs 指令可以查看容器的輸出日誌。 第二步:找到錯誤訊息 在日誌中發現關鍵錯誤: error: The bucket does not allow ACLs AccessControlListNotSupported: The bucket does not allow ACLs 這個錯誤訊息指出 S3 Bucket(AWS 的物件儲存服務中的儲存桶)不允許使用 ACL。 ...

January 30, 2026 · 3 分鐘 · Peter

資料庫同步的隱藏陷阱:Link Table 的重要性

問題現象:登入成功卻被拒於門外 最近在 Staging 環境遇到一個詭異的問題:使用者登入成功,拿到了有效的 JWT Token,但存取任何需要認證的 API 都回傳 401 Unauthorized。 # 登入成功,拿到 token POST /api/auth/local → 200 OK { "jwt": "eyJhbGc...xxxxx...your-jwt-token", "user": { "id": 1001, "email": "[email protected]" } } # 但存取個人資料失敗 GET /api/users/me → 401 Unauthorized Token 驗證通過、使用者存在、帳號未被封鎖。問題到底在哪? 根本原因:遺失的 Link Table 經過一番追查,發現問題出在資料庫同步時漏掉了關聯表(Link Table)。 什麼是 Link Table? 在關聯式資料庫中,多對多關係需要透過中間表來建立。這個中間表就是 Link Table(也稱為 Junction Table、Join Table、或 Pivot Table)。 erDiagram users ||--o{ users_roles_lnk : has roles ||--o{ users_roles_lnk : assigned_to users { int id PK string email string password boolean confirmed } roles { int id PK string name string type } users_roles_lnk { int user_id FK int role_id FK int user_ord } 使用者與角色的關係: ...

January 20, 2026 · 4 分鐘 · Peter

Flutter CI/CD Debugging: Three Build Failures in One Day

前言:當建置一直紅燈 CI/CD pipeline 亮紅燈是開發日常,但連續遇到三個不同層面的問題,從 iOS codesigning、Android Gradle、到 Google Play API,這就值得記錄下來了。 這篇文章記錄我在同一天內遇到的三個建置失敗,以及逐步排除的過程。每個問題都有其獨特的根因,但也反映出 CI/CD 環境的複雜性。 問題一:iOS Keychain 解鎖失敗 症狀 Jenkins 建置在 iOS 階段失敗,錯誤訊息: [!] Error unlocking keychain at path: fastlane_keychain Command failed with exit status 51 macOS Keychain 運作機制 在深入問題之前,先了解 macOS Keychain 的運作方式: flowchart TD subgraph Keychain["macOS Keychain 系統"] KC1[login.keychain<br/>使用者預設] KC2[fastlane_keychain<br/>CI 專用] KC3[fastlane_tmp_keychain<br/>臨時] end subgraph Certs["憑證與密鑰"] CERT[Apple Distribution<br/>Certificate] KEY[Private Key] end KC2 --> CERT KC2 --> KEY subgraph Access["存取控制"] LOCK[🔒 Locked<br/>密碼保護] UNLOCK[🔓 Unlocked<br/>可存取憑證] end LOCK -->|正確密碼| UNLOCK LOCK -->|錯誤密碼| ERROR[❌ Exit 51] classDef default fill:#f5f5f5,stroke:#333,color:#333 classDef error fill:#f8d7da,stroke:#dc3545,color:#721c24 classDef success fill:#d4edda,stroke:#28a745,color:#155724 class ERROR error class UNLOCK success 關鍵概念: ...

January 17, 2026 · 4 分鐘 · Peter