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

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

## 決策

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

```python
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(*)`（< 5ms，PG 索引）
- 資料寫入 → 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 | 違反「先查官方文件 → 一次只動一個依賴」鐵律；新增 SPOF；KM 用 pgvector 不該被當訊息 broker（職責汙染）|
| File mtime sentinel | 容器化下 volume mount 設定繁瑣；NFS/iCloud 環境時序不穩 |
| PostgreSQL `LISTEN/NOTIFY` | 需 background thread，sync worker 不友善；待 QPS 提升或升級 gthread 後再評估 |
| 維持 N-POST hack | 9.4% 命中率本質不可靠 |

## 紀念踩坑

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