# Prometheus TSDB 第一次接觸

## 1\. 架構總覽

### 🏗️ 核心架構流程

```mermaid
graph LR
    A[📥 Scrape Target] -->|寫入| B[📝 WAL]
    B -->|同步| C[💾 Head Block<br/>記憶體]
    C -->|定期| D[✅ Checkpoint]
    C -->|每2小時| E[💿 Persistent Block<br/>磁碟]
    E -->|包含| F[📦 Chunks]
    
    style A fill:#e74c3c,stroke:#c0392b,color:#fff
    style B fill:#e74c3c,stroke:#c0392b,color:#fff
    style C fill:#3498db,stroke:#2980b9,color:#fff
    style D fill:#f39c12,stroke:#e67e22,color:#fff
    style E fill:#2ecc71,stroke:#27ae60,color:#fff
    style F fill:#9b59b6,stroke:#8e44ad,color:#fff
```

> **🔑 核心概念**：Prometheus 使用多層級的儲存架構，用來確保數據的**持久性**，**高效查詢**跟**儲存空間優化**的保證。

## 2\. WAL (Write-Ahead Log)

### 📝 功能定位

**WAL**是所有新數據的第一站，類似資料庫的 transaction log，確保數據不會因系統崩潰而遺失。

### 📁 檔案結構

```c
/data/wal/
         ├── 00000000  # 128MB segment
         ├── 00000001  # 128MB segment
         ├── 00000002  # 活躍寫入
        └── checkpoint.00000001/
```

### ⚙️ 特性

| 特性 | 說明 |
| --- | --- |
| **大小** | 每個 segment 128MB |
| **順序** | 必須連續編號 |
| **保留** | 2-3 小時數據 |
| **格式** | 二進制 |

### 🔄 WAL 工作流程

```mermaid
sequenceDiagram
    participant S as Scrape
    participant W as WAL (磁碟)
    participant H as Head Block (記憶體)
    
    S->>W: 1. 先寫入 WAL
    Note over W: 確保持久化
    W->>H: 2. 再寫入 Head
    Note over H: 快速查詢
    
    alt Prometheus 崩潰
        W->>H: 3. 重啟時從 WAL 恢復
        Note over H: 重建記憶體狀態
    end
```

### ✅ WAL 的作用

1. **持久化保證**：先寫入磁碟，確保數據安全
    
2. **崩潰恢復**：重啟時重建 Head Block
    
3. **順序寫入**：高效的磁碟 I/O
    

---

## 3\. Head Block (記憶體區塊)

### 💾 功能定位

存放在**記憶體**中的活躍數據，提供最快的查詢速度。

### 🏗️ 結構組成

```mermaid
graph TD
    A[Head Block 記憶體] --> B[mmap chunks<br/>記憶體映射檔案]
    A --> C[Inverted Index<br/>倒排索引]
    A --> D[Series Metadata<br/>時間序列元數據]
    
    style A fill:#3498db,stroke:#2980b9,color:#fff
    style B fill:#5dade2,stroke:#3498db,color:#fff
    style C fill:#5dade2,stroke:#3498db,color:#fff
    style D fill:#5dade2,stroke:#3498db,color:#fff
```

### 📊 關鍵特性

* **數據範圍**：通常 2-3 小時
    
* **查詢速度**：極快（直接從記憶體）
    
* **壓縮算法**：XOR + Delta-of-Delta
    
* **更新頻率**：即時
    

---

## 4\. Checkpoint (檢查點)

### ✅ 功能定位

WAL 的**快照**，加速 Prometheus 重啟，減少需要重放的 WAL 數量。

### 📸 Checkpoint 機制

```mermaid
graph LR
    A[WAL 00000000] --> D[Checkpoint.00000002]
    B[WAL 00000001] --> D
    C[WAL 00000002] --> D
    E[WAL 00000003] -->|活躍| F[繼續寫入]
    G[WAL 00000004] -->|活躍| F
    
    style D fill:#f39c12,stroke:#e67e22,color:#fff
    style E fill:#2ecc71,stroke:#27ae60,color:#fff
    style G fill:#2ecc71,stroke:#27ae60,color:#fff
```

### ✨ 優點

* ✅ 重啟更快（不需要重放所有 WAL）
    
* ✅ 減少磁碟使用（可刪除舊 WAL）
    
* ✅ 每 2 小時自動創建
    

### 🔧 工作原理

```c
1. 每 2 小時創建一次 checkpoint
2. Checkpoint 包含 00000000-00000002 的數據
3. 舊的 WAL segments 可以安全刪除
4. 重啟時：
   → 讀取最新 checkpoint
   → 重放之後的 WAL segments
```

---

## 5\. Persistent Block (持久化區塊)

### 💿 功能定位

**壓縮後的歷史數據**，不可變（immutable），存儲在磁碟上。

### 📦 Block 結構

```c
/data/01K9TJBMDM5BC097TAG3HETHDZ/  ← Block ULID
├── chunks/
│   └── 000001              # 壓縮的時間序列數據
├── index                   # 倒排索引（快速查詢）
├── meta.json               # 元數據（時間範圍、統計）
└── tombstones              # 刪除標記
```

### 🔄 Block 壓縮層級

```mermaid
graph TD
    A[2小時 Block] --> B[Level 0]
    B --> C[4小時 Block]
    C --> D[Level 1]
    D --> E[12小時 Block]
    E --> F[Level 2]
    F --> G[24小時+ Block]
    G --> H[Level 3+]
    
    style A fill:#e8f5e9,stroke:#4caf50
    style C fill:#fff9c4,stroke:#fbc02d
    style E fill:#ffe0b2,stroke:#fb8c00
    style G fill:#ffccbc,stroke:#f4511e
```

### 📊 壓縮層級說明

| 層級 | 時間範圍 | 來源 |
| --- | --- | --- |
| Level 0 | 2 小時 | 從 Head Block 轉換 |
| Level 1 | 4-6 小時 | 合併 2 個 Level 0 |
| Level 2 | 12-24 小時 | 合併多個 Level 1 |
| Level 3+ | 24+ 小時 | 長期歷史數據 |

### 🎯 Block 特性

* **不可變**：一旦創建就不會修改
    
* **時間範圍固定**：每個 Block 覆蓋固定時間
    
* **高壓縮率**：~84% 空間節省
    
* **快速查詢**：通過 index 加速
    

---

## 6\. Chunks (數據塊)

### 📦 功能定位

實際存儲**時間序列樣本**的地方，使用高效壓縮算法。

### 🗜️ 壓縮效率

| 類型 | 大小 | 說明 |
| --- | --- | --- |
| **原始數據** | 16 bytes/sample | timestamp (8) + value (8) |
| **壓縮後** | 1.3 bytes/sample | XOR + Delta-of-Delta |
| **壓縮率** | **84%** | 節省空間 |

### 🔧 壓縮技術

```mermaid
graph LR
    A[原始時間戳] -->|Delta編碼| B[時間差值]
    B -->|Delta-of-Delta| C[差值的差值]
    D[原始數值] -->|XOR編碼| E[XOR結果]
    C --> F[變長編碼]
    E --> F
    F --> G[壓縮 Chunk]
    
    style A fill:#ffebee,stroke:#f44336
    style D fill:#ffebee,stroke:#f44336
    style G fill:#e8f5e9,stroke:#4caf50,color:#1b5e20
```

### 📊 Chunk 組織

```c
一個時間序列：metric{label="value"}

├── Chunk 1 (0-2小時)
│   ├── timestamp: 1763445600
│   ├── value: 42.5
│   ├── timestamp: 1763445615
│   └── value: 43.1
│   
├── Chunk 2 (2-4小時)
└── Chunk 3 (4-6小時)
```

---

## 7\. 數據生命週期

### 🔄 完整流程

#### ① 數據寫入階段

```c
Scrape Target
    ↓
寫入 WAL (磁碟) ← 崩潰恢復保證
    ↓
寫入 Head Block (記憶體) ← 快速查詢
    ↓
定期創建 Checkpoint ← 加速重啟
```

#### ② 數據壓縮階段（每 2 小時）

```c
Head Block (記憶體)
    ↓
持久化為 Block (磁碟)
    ↓
Chunks 壓縮存儲
    ↓
創建 Index (快速查詢)
    ↓
舊 WAL 可刪除
```

#### ③ 數據查詢階段

```c
Query
    ↓
├── 查詢 Head Block (最近 2 小時) ← 記憶體，最快
└── 查詢 Persistent Blocks ← 磁碟，較慢
    ├── 讀取 Index (找到相關 Chunks)
    └── 讀取 Chunks (解壓縮數據)
```

### 📊 時間軸示意

```c
時間: 0h    2h    4h    6h    8h    10h   12h
      ├─────┼─────┼─────┼─────┼─────┼─────┤
WAL:  [====活躍數據====]
      └────────┘
      轉換為 Block
      
Head: [====記憶體====]
      
Blocks:    [B1] [B2] [B3] [B4] [B5] ...
           └──┬──┘   └──┬──┘
              [B6]      [B7]  ← Level 1 壓縮
              └────┬────┘
                  [B8]       ← Level 2 壓縮
```

---

## 8\. 問題診斷與解決

### ❌ 常見錯誤

```c
opening storage failed: get segment range: segments are not sequential
```

### 🔍 錯誤原因

WAL segments 的編號不連續：

```diff
✗ 錯誤情況：
/data/wal/
├── 00000000
├── 00000001
- ├── 00000003  ← 缺少 00000002！
└── 00000004

✓ 正確情況：
/data/wal/
├── 00000000
├── 00000001
+ ├── 00000002  ← 連續！
└── 00000003
```

### 🎯 情況分析

```c
/data/wal/
├── 00000              ⚠️ 不該存在！
├── checkpoint.00587/  ✓ 正常
├── 00588              ✓ 正常
└── 00589              ✓ 正常
```

**診斷結果**：

* ⚠️ `00000` 是孤立的舊 segment
    
* ✅ Checkpoint 機制正常運作
    
* ✅ 新的 segments (00588, 00589) 正常
    

**原因**：

* Pod 異常重啟時創建了 `00000`
    
* 但舊的 checkpoint 和 segments 還保留著
    
* 導致 WAL 結構不一致
    

---

## 🛠️ 解決方案

### 方案對比

| 方案 | 影響 | 適用場景 | 操作複雜度 |
| --- | --- | --- | --- |
| **方案 1：刪除異常 segment** | 🟢 最小 | 單一 segment 損壞 | 簡單 |
| **方案 2：刪除整個 WAL** | 🟡 中等 | 多個 segment 損壞 | 中等 |
| **方案 3：完全重置** | 🔴 最大 | 測試環境/嚴重損壞 | 複雜 |

---

### ✅ 推薦方案：刪除異常 00000

#### 步驟 1：驗證問題

```bash
kubectl exec -n kubecost kubecost-prometheus-server-7dd5f595d6-4k8h6 \
  -c prometheus-server -- ls -lh /data/wal/00000
```

#### 步驟 2：備份（可選）

```bash
kubectl exec -n kubecost kubecost-prometheus-server-7dd5f595d6-4k8h6 \
  -c prometheus-server -- cp /data/wal/00000 /tmp/wal_00000_backup
```

#### 步驟 3：刪除異常檔案

```bash
kubectl exec -n kubecost kubecost-prometheus-server-7dd5f595d6-4k8h6 \
  -c prometheus-server -- rm /data/wal/00000
```

#### 步驟 4：重啟 Pod

```bash
kubectl delete pod -n kubecost kubecost-prometheus-server-7dd5f595d6-4k8h6
```

#### 步驟 5：驗證恢復

```bash
# 監控新 Pod 啟動
kubectl get pods -n kubecost -w

# 查看日誌
kubectl logs -n kubecost <new-pod-name> -c prometheus-server -f

# 應該看到：
# ✅ "Server is ready to receive web requests"
```

#### 步驟 6：檢查 WAL 結構

```bash
kubectl exec -n kubecost <new-pod-name> -c prometheus-server -- \
  ls -lh /data/wal/

# 預期結果：
# checkpoint.00587/
# 00588
# 00589
# 00590 (新創建)
```

---

### 🔄 備選方案 2：刪除整個 WAL

如果方案 1 失敗，使用此方案：

```bash
# 進入 Pod
kubectl exec -it -n kubecost <pod-name> -c prometheus-server -- sh

# 刪除 WAL 和 chunks_head
rm -rf /data/wal/*
rm -rf /data/chunks_head/*

# 退出並重啟
exit
kubectl delete pod -n kubecost <pod-name>
```

**影響**：

* ❌ 丟失最近 2-3 小時數據
    
* ✅ 保留所有歷史 Blocks
    

---

### 🔄 備選方案 3：完全重置

**僅用於測試環境或嚴重損壞！**

```bash
# 1. 縮減副本
kubectl scale deployment kubecost-prometheus-server -n kubecost --replicas=0

# 2. 刪除 PVC（會刪除所有數據！）
kubectl delete pvc -n kubecost -l app=prometheus

# 3. 重新啟動
kubectl scale deployment kubecost-prometheus-server -n kubecost --replicas=1
```

**影響**：

* ❌ **丟失所有歷史數據**
    
* ✅ 全新乾淨的資料庫
    

---

## 📊 預防措施

### 1\. 監控指標

```c
# WAL segment 數量
prometheus_tsdb_wal_segment_current

# WAL 損壞次數
prometheus_tsdb_wal_corruptions_total

# Checkpoint 失敗次數
prometheus_tsdb_checkpoint_creations_failed_total

# TSDB Head 大小
prometheus_tsdb_head_series
```

### 2\. 資源配置建議

```yaml
# Prometheus 配置
resources:
  limits:
    memory: 4Gi        # 足夠的記憶體
    cpu: 2000m
  requests:
    memory: 2Gi
    cpu: 1000m

# 存儲配置
storage:
  tsdb:
    retention.time: 15d
    retention.size: 50GB
    wal-compression: true  # 啟用 WAL 壓縮
```

### 3\. 定期檢查清單

* \[ \] 每週檢查 WAL 結構
    
* \[ \] 監控磁碟使用率（&gt; 80% 告警）
    
* \[ \] 定期使用 promtool 驗證
    
* \[ \] 檢查 Prometheus 日誌中的錯誤
    
* \[ \] 監控 Pod 記憶體使用（避免 OOM）
    

### 4\. 使用 Promtool 驗證

```bash
# 檢查 TSDB 健康度
kubectl exec -n kubecost <pod> -c prometheus-server -- \
  promtool tsdb analyze /data

# 檢查 WAL 完整性
kubectl exec -n kubecost <pod> -c prometheus-server -- \
  promtool tsdb check wal /data/wal

# 列出所有 Blocks
kubectl exec -n kubecost <pod> -c prometheus-server -- \
  promtool tsdb list /data
```

---

## 📚 總結

### 核心概念回顧

```c
數據流向：
Scrape → WAL → Head Block → Checkpoint → Persistent Block → Chunks
         ↓      ↓            ↓            ↓                 ↓
      持久化  快速查詢    加速重啟     長期存儲         高壓縮
```

### 關鍵要點

1. **WAL**：預寫日誌，確保數據持久性
    
2. **Head Block**：記憶體中的活躍數據
    
3. **Checkpoint**：WAL 快照，加速重啟
    
4. **Persistent Block**：壓縮的歷史數據
    
5. **Chunks**：實際數據存儲，高效壓縮
    

### 故障影響範圍

| 組件 | 損壞影響 | 恢復難度 |
| --- | --- | --- |
| WAL | 丟失 2-3 小時數據 | 容易 |
| Head Block | 丟失 2-3 小時數據 | 容易 |
| Checkpoint | 重啟較慢 | 容易 |
| Persistent Block | 丟失該 Block 時間範圍數據 | 困難 |

---

## 🔗 參考資源

* [Prometheus Storage Documentation](https://prometheus.io/docs/prometheus/latest/storage/)
    
* [TSDB Format](https://github.com/prometheus/prometheus/blob/main/tsdb/docs/format/README.md)
    
* [Promtool Documentation](https://prometheus.io/docs/prometheus/latest/command-line/promtool/)
