Files
ewoooc/docs/adr/ADR-016-daily-sales-cache-fingerprint.md
OoO 8be332728e docs: ADR-016 daily_sales cache fingerprint + 4/28-29 history log + CLAUDE.md ADR 索引補 015/016
- 新增 docs/adr/ADR-016:daily_sales cache TTL → TTL+DB fingerprint,棄用
  N-POST broadcast hack(命中率 9.4%),引述 debugger/web-researcher/migration-engineer
  三方共識;殘留 HIGH-2(app.py 43 處 _SALES_PROCESSED_CACHE)列管
- docs/memory/history_logs.md 新增 4/28-29 條目:app.py -10.8%、4 個改進版死碼揭發、
  cache 機制重構、hook 9 PoC 強化、12 Agent 並行作戰實踐
- CLAUDE.md 補上 ADR-015/016 索引項

Memory(個人 auto-memory,未入 repo)同步:
- feedback_flask_blueprint_shadow.md(first-registered wins)
- feedback_cache_invalidation_pattern.md(DB fingerprint vs N-POST)
- feedback_agent_dispatch_order.md(critic 必須先於 fullstack)
- feedback_cloud_vs_local_automation.md(LAN 專案禁雲端 routine)
- project_phase3e_refactor_progress.md(11 commits 戰果)
2026-04-29 19:56:48 +08:00

3.6 KiB
Raw Permalink Blame History

ADR-016: daily_sales cache 失效策略改用 DB Fingerprint

  • 狀態: 已實作
  • 日期: 2026-04-29
  • 相關 commit: 8fefea0, 3971fd4
  • 相關 ADR: ADR-008188 多容器拓撲、ADR-011跨專案隔離

決策

daily_sales_snapshot 相關 cache 從「TTL-only」改為「TTL + DB fingerprint」雙閘失效機制

def _get_data_fingerprint(engine, table_name='daily_sales_snapshot'):
    SELECT MAX(snapshot_date)::text, COUNT(*) FROM "{table_name}"
    return (max_date, count)  # 任一變動 fingerprint 跳號

def _is_cache_valid(cache_key, engine=None, table_name=...):
    1. cache 不存在  False
    2. TTL 過期>300s)→ False
    3. DB fingerprint 不一致  False
    4. fingerprint 查詢失敗  保守維持return True
    5. 全通過  True

cache 寫入時同步記錄 fingerprint 欄位。

背景:為什麼不能用 N-POST broadcast hack

舊方案import 完成後從 services/import_service.py 連續 POST /api/daily_sales/clear_cache N 次N = GUNICORN_WORKERS=4),希望命中所有 worker 各自清掉 module-level dict。

實測不可靠debugger + web-researcher + migration-engineer 三方共識):

  • gunicorn sync worker 用 OS kernel accept() queue 派送,不是 round-robin
  • 4 次 POST 命中全部 4 worker 機率 = 4!/4^4 ≈ 9.4%coupon collector
  • 需 N=20 才能拉到 ~94% 覆蓋率N=50 才到 99.4%
  • 仍永遠不到 100%,且每次 import 多 50 個 internal HTTP 完全是浪費

DB fingerprint 把問題從「跨 worker 廣播」翻轉成「各 worker 自查 source of truth」

  • DB 是唯一一致狀態
  • 每次 cache 命中前查一次 MAX(snapshot_date), COUNT(*)< 5msPG 索引)
  • 資料寫入 → fingerprint 跳號 → 4 worker 下一次 request 各自偵測失效並重載
  • 強一致,無 N-POST 浪費

影響

項目 變動
cache 一致性窗口 5 min TTL → 1 個 request 週期(強一致)
每次頁面讀取額外成本 +1 次 SELECT MAX/COUNT< 5ms
跨 worker 同步機制 廣播(不可靠)→ DB pull強一致
/api/daily_sales/clear_cache 角色 必要清除路徑 → ops escape hatch
端點安全 無 auth → @login_required + CSRF雙重攔截POST 無 token 回 400

殘留技術債(未在本 ADR 範圍內)

  • app.py 仍有 43 處 _SALES_PROCESSED_CACHE 引用sales_analysis 等獨立 cache與 daily_sales_routes 中的同名 dict 是兩份不同 module-local dict
  • 後續應抽 services/cache_manager.py 統一管理,套用同樣的 fingerprint 模式
  • 列入 P10 Q1 後段任務

備選方案與否決理由

方案 否決理由
Redis pub/sub 違反「先查官方文件 → 一次只動一個依賴」鐵律;新增 SPOFKM 用 pgvector 不該被當訊息 broker職責汙染
File mtime sentinel 容器化下 volume mount 設定繁瑣NFS/iCloud 環境時序不穩
PostgreSQL LISTEN/NOTIFY 需 background threadsync worker 不友善;待 QPS 提升或升級 gthread 後再評估
維持 N-POST hack 9.4% 命中率本質不可靠

紀念踩坑

  • 8fefea0 在 critic 審查完成前已被 fullstack-engineer pushAgent dispatch 順序失誤)→ 引入 4 個 critic HIGH finding
  • 3971fd4 補丁修復 HIGH-3auth+ HIGH-4fingerprintHIGH-2app.py 43 處同名 cache作為已知殘留技術債列管
  • HIGH-1Flask 雙註冊衝突)為 critic 過慮web-researcher PoC 證實 first-registered wins 不會 raisebp 版本 shadow 為死碼