# ADR-007: 資料保留策略

> **狀態**: 已接受
> **日期**: 2026-03-20
> **決策者**: CEO, CTO, CIO

---

## 背景

需要定義各類型資料的保留時間 (TTL)，確保：
1. 系統效能不因資料累積而下降
2. 重要資料有足夠的回溯時間
3. 儲存成本可控

### CEO 指示 #7

> 熱資料 (Redis/即時查詢) TTL 7 天 => 初期是否也保留 6 個月？要確認數據量有多大？

---

## 決策

### 資料分層策略

```
┌─────────────────────────────────────────────────────────┐
│ Layer 1: 熱資料 (Redis)                                 │
│ TTL: 7-30 天                                            │
│ 用途: 即時查詢、快取、Session                            │
│ 預估容量: ~500MB                                        │
└─────────────────────────────────────────────────────────┘
                        │ 過期後
                        ▼
┌─────────────────────────────────────────────────────────┐
│ Layer 2: 溫資料 (PostgreSQL)                            │
│ TTL: 6 個月 (CEO 指示)                                  │
│ 用途: 歷史查詢、報表、分析                               │
│ 預估容量: ~5GB/月                                       │
└─────────────────────────────────────────────────────────┘
                        │ 過期後
                        ▼
┌─────────────────────────────────────────────────────────┐
│ Layer 3: 冷資料 (歸檔)                                  │
│ TTL: 永久 (審計日誌) / 1 年 (一般)                       │
│ 用途: 合規、稽核、法律要求                               │
│ 預估容量: ~10GB/年                                      │
└─────────────────────────────────────────────────────────┘
```

### 各資料類型 TTL 定義

#### Redis 熱資料 (Layer 1)

| 資料類型 | TTL | 說明 | 預估大小 |
|---------|-----|------|---------|
| Session Token | 7 天 | 用戶登入狀態 | ~1KB/session |
| Dashboard 快取 | 5 分鐘 | 即時指標聚合 | ~10KB |
| 主機狀態快取 | 30 秒 | 即時健康狀態 | ~2KB/host |
| AI 回應快取 | 1 小時 | 相同問題快取 | ~5KB/entry |
| 限流計數器 | 1 分鐘 | Rate Limiting | ~100B/user |

**Redis 容量評估**:
- 4 台主機 × 2KB = 8KB (即時狀態)
- 100 用戶 × 1KB = 100KB (Session)
- Dashboard 快取 = 50KB
- AI 快取 (1000 條) = 5MB
- **總計: ~10MB (遠低於 Redis 16GB 容量)**

> ✅ **結論**: Redis 熱資料保持短 TTL (7-30 天) 是合理的，不需要延長至 6 個月。
> Redis 用於快取和即時查詢，歷史資料應存放在 PostgreSQL。

#### PostgreSQL 溫資料 (Layer 2)

| 資料類型 | TTL | 說明 | 預估大小 |
|---------|-----|------|---------|
| 監控指標 | 6 個月 | CPU/Memory/Disk 歷史 | ~1GB/月 |
| 告警記錄 | 6 個月 | 歷史告警 | ~100MB/月 |
| 部署記錄 | 6 個月 | Pipeline 執行歷史 | ~50MB/月 |
| 工單記錄 | 6 個月 | 處理歷史 | ~20MB/月 |
| AI 對話記錄 | 6 個月 | Copilot 歷史 | ~500MB/月 |
| 用戶操作記錄 | 6 個月 | 行為追蹤 | ~200MB/月 |

**PostgreSQL 容量評估**:
- 每月增量: ~2GB
- 6 個月累計: ~12GB
- **總計 (含索引): ~20GB**

> ✅ **結論**: PostgreSQL 溫資料保留 6 個月，符合 CEO 指示，容量可控。

#### 冷資料歸檔 (Layer 3)

| 資料類型 | TTL | 說明 |
|---------|-----|------|
| 審計日誌 | 永久 | 合規要求，不可刪除 |
| 財務記錄 | 7 年 | 法律要求 |
| 安全事件 | 3 年 | 資安稽核 |
| 系統設定變更 | 1 年 | 變更追蹤 |

---

### 資料清理機制

#### 自動清理 Job

```python
# apps/api/app/jobs/data_cleanup.py

from datetime import datetime, timedelta
from app.database import get_db
from app.models import Metric, Alert, Deployment, AIConversation

async def cleanup_expired_data():
    """每日凌晨 3:00 執行"""
    six_months_ago = datetime.utcnow() - timedelta(days=180)

    async with get_db() as db:
        # 清理過期監控指標
        deleted_metrics = await db.execute(
            delete(Metric).where(Metric.created_at < six_months_ago)
        )
        logger.info(f"Deleted {deleted_metrics.rowcount} expired metrics")

        # 清理過期告警 (保留 acknowledged 狀態)
        deleted_alerts = await db.execute(
            delete(Alert).where(
                Alert.created_at < six_months_ago,
                Alert.status != 'archived'  # 保留歸檔的告警
            )
        )
        logger.info(f"Deleted {deleted_alerts.rowcount} expired alerts")

        # ... 其他資料類型

        await db.commit()

    # 更新 Prometheus 指標
    cleanup_records_deleted.labels(type="metrics").inc(deleted_metrics.rowcount)
    cleanup_records_deleted.labels(type="alerts").inc(deleted_alerts.rowcount)
```

#### K8s CronJob

```yaml
# k8s/jobs/data-cleanup-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: awoooi-data-cleanup
  namespace: awoooi-prod
spec:
  schedule: "0 3 * * *"  # 每日凌晨 3:00
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: cleanup
              image: awoooi-api:latest
              command: ["python", "-m", "app.jobs.data_cleanup"]
          restartPolicy: OnFailure
```

---

### 資料量監控

#### Prometheus 指標

```yaml
# k8s/monitoring/prometheus/data-alerts.yaml
groups:
  - name: data-storage-alerts
    rules:
      # PostgreSQL 容量警告
      - alert: PostgreSQLHighUsage
        expr: |
          pg_database_size_bytes{datname="awoooi"} > 15 * 1024 * 1024 * 1024
        labels:
          severity: warning
        annotations:
          summary: "PostgreSQL 容量已達 15GB"
          description: "目前使用 {{ $value | humanize1024 }}，建議檢查資料清理 Job"

      # Redis 容量警告
      - alert: RedisHighMemory
        expr: |
          redis_memory_used_bytes{db="10"} > 1 * 1024 * 1024 * 1024
        labels:
          severity: warning
        annotations:
          summary: "Redis DB 10 記憶體使用超過 1GB"
```

---

### 儲存成本評估

| 層級 | 6 個月容量 | 儲存類型 | 成本 |
|------|-----------|---------|------|
| Redis (熱) | ~10MB | 內存 | 包含在伺服器 |
| PostgreSQL (溫) | ~20GB | SSD | 包含在伺服器 |
| 歸檔 (冷) | ~10GB/年 | HDD/S3 | ~$0.5/月 |

**結論**: 採用自建伺服器，儲存成本基本為 $0 (已攤提)。

---

## 影響

### 正面

- 資料保留策略明確，符合 CEO 6 個月要求
- Redis 維持高效能 (短 TTL)
- 歷史資料可追溯
- 儲存成本可控

### 需要注意

- 清理 Job 需要監控，確保正常執行
- 歸檔資料需要定期備份
- 審計日誌不可刪除，需永久保留

---

## 變更記錄

| 日期 | 版本 | 變更 | 作者 |
|------|------|------|------|
| 2026-03-20 | v1.0 | 初版建立 | CTO |

---

*此 ADR 記錄資料保留策略的決策過程與實作規範。*
