---
# System prepended metadata

title: '11. Database & Storage Architect  '

---

# 11. Database & Storage Architect

## 11.1 目標與整體觀念

在任何 MLOps 或大型分散式系統中,資料庫與儲存架構要同時滿足三個目標:

1. **一致性與正確性(Correctness)**:確保交易與資料不產生矛盾
2. **可擴充性(Scalability)**:能隨著資料量與使用者數量成長而水平擴充
3. **可維運性(Operability)**:易於備份、監控、調整 schema 與擴充節點

Kleppmann 在 *Designing Data-Intensive Applications* 中,將這類系統稱為「資料密集型應用」,並強調要從資料模型、儲存引擎、分散式協定三個層面整體設計,而不是只看單一資料庫產品。

### 關鍵架構考量點

```
資料庫架構三角
    ┌─────────────────┐
    │  Consistency    │
    │   (一致性)       │
    └────────┬────────┘
             │
       ┌─────┴─────┐
       │           │
┌──────▼─────┐ ┌──▼────────┐
│ Availability│ │Performance│
│  (可用性)    │ │  (效能)    │
└─────────────┘ └───────────┘
```

---

## 11.2 資料庫 Schema 設計

### 11.2.1 Schema 的角色

Schema 決定:
- 資料如何被分割、儲存與索引
- 查詢路徑與未來可否容易擴充或分片
- MLOps 中特徵抽取、標註、訓練資料抽樣的難易度

在集中式資料庫中,多半以「正規化 → 避免重複」為主;而在分散式資料庫或雲端環境,還要考慮「資料局部性(locality)」與「存取模式(access pattern)」。

### 11.2.2 設計重點

#### 1. 明確定義主鍵(Primary Key)

- 採用穩定、不易變動的識別子(如 `user_id`、`device_id`)
- 避免以易變欄位(如 email)當主鍵
- 在分散式系統中,主鍵常同時是「分片 key」的一部分

```sql
-- ✅ 良好的主鍵設計
CREATE TABLE users (
    user_id BIGINT PRIMARY KEY,
    org_id INT NOT NULL,
    email VARCHAR(255),
    created_at TIMESTAMP
);

-- ❌ 避免使用易變欄位作為主鍵
CREATE TABLE users (
    email VARCHAR(255) PRIMARY KEY,  -- 不推薦
    ...
);
```

#### 2. 針對存取模式設計

- 先列出主要查詢,如:「依使用者 + 時間區間查詢」、「依任務狀態查詢」
- Schema 不是只為了「資料漂亮」,而是要讓主要查詢走到有效率的索引與分片

```sql
-- 設計範例:支援常見查詢模式
CREATE TABLE training_runs (
    org_id INT,
    user_id BIGINT,
    run_id UUID,
    status VARCHAR(50),
    created_at TIMESTAMP,
    PRIMARY KEY (org_id, user_id, created_at),
    INDEX idx_status (org_id, status, created_at)
);
```

#### 3. 適度反正規化(Denormalization)

- 避免跨多表 join,尤其在分片後跨節點 join 成本很高
- 對於常被一起查詢的欄位,可以接受適度重複,換取查詢簡化與效能

**正規化 vs. 反正規化比較**

| 面向 | 正規化 | 反正規化 |
|------|--------|----------|
| 資料重複 | 最小化 | 接受部分重複 |
| 寫入效能 | 較快(單點寫入) | 較慢(多處更新) |
| 讀取效能 | 需要 JOIN | 直接讀取 |
| 適用場景 | 寫多讀少 | 讀多寫少 |
| 分散式友善度 | 較低 | 較高 |

#### 4. 多租戶(Multi-tenant)設計

- 若同一系統服務多個機構,常見作法是在所有主表加上 `tenant_id` 或 `org_id`
- 這個欄位通常也是分片 key 的一部分,有助於隔離負載與資料

```sql
-- 多租戶設計模式
CREATE TABLE medical_records (
    tenant_id INT,
    patient_id BIGINT,
    record_id UUID,
    data JSONB,
    created_at TIMESTAMP,
    PRIMARY KEY (tenant_id, patient_id, created_at)
);

-- 資料隔離查詢
SELECT * FROM medical_records 
WHERE tenant_id = 123 
AND patient_id = 456 
AND created_at >= '2025-01-01';
```

Google Spanner 的 schema 設計文件特別強調「設計主鍵與層級結構,讓同一個分片上的資料能對應主要存取模式」,以避免熱點與跨節點查詢。

---

## 11.3 分片(Sharding)設計

### 11.3.1 為什麼要分片

當單一資料庫節點在 CPU、記憶體、磁碟或 I/O 已達瓶頸時,就必須「水平切分」資料:

- 每個節點只存「全部資料的一部分」
- 查詢時依據分片 key 將請求路由到正確節點
- 這是多數「雲原生分散式資料庫」的核心能力

### 11.3.2 常見分片方式

#### 1. Range Sharding(範圍分片)

**原理**:依 key 範圍拆分

```
User ID Range Sharding 示例:
┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ Shard A      │  │ Shard B      │  │ Shard C      │
│ ID: 1-1M     │  │ ID: 1M-2M    │  │ ID: 2M-3M    │
└──────────────┘  └──────────────┘  └──────────────┘
```

**優點**:
- 支援範圍查詢
- 排序友好
- 易於理解和實作

**風險**:
- 如果新資料只落在某段(如時間序列),容易產生「熱點分片」
- 資料分佈可能不均勻

**適用場景**:
- 時間序列資料(配合定期重平衡)
- 地理位置資料
- 字母順序資料

#### 2. Hash Sharding(雜湊分片)

**原理**:對 key 做 hash,再依 hash 結果分配 shard

```
Hash Sharding 流程:
User ID → Hash Function → Hash Value mod N → Shard Assignment

Example:
user_id: 12345
  ↓ hash()
hash: 8a7f2c...
  ↓ mod 3
shard: 2 → Shard C
```

**優點**:
- 負載分佈均勻
- 不易產生熱點
- 自動平衡

**缺點**:
- 範圍查詢需要打到多個分片,成本較高
- 重新分片時需要大量資料搬移

**適用場景**:
- 點查詢為主的應用
- 需要均勻負載分佈
- 高併發寫入

#### 3. Directory / Lookup-based Sharding(目錄式分片)

**原理**:維護一張「分片對照表」,紀錄 key 區間對應哪個 shard

```
Directory-Based Sharding:
┌─────────────────────┐
│  Lookup Table       │
├──────────┬──────────┤
│ Key Range│ Shard ID │
├──────────┼──────────┤
│ A-F      │ Shard 1  │
│ G-M      │ Shard 2  │
│ N-Z      │ Shard 3  │
└──────────┴──────────┘
        ↓
  Route to correct shard
```

**優點**:
- 彈性高,可以動態調整
- 支援複雜的分片策略
- 易於遷移和重新平衡

**缺點**:
- 需要額外維護 metadata
- Lookup table 可能成為瓶頸
- 增加了一層查詢延遲

#### 4. Geographical Sharding(地理位置分片)

**原理**:根據地理位置分配資料到就近的分片

```
Geographical Sharding:
┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ Asia Shard   │  │ Europe Shard │  │ US Shard     │
│ (Tokyo)      │  │ (Frankfurt)  │  │ (Virginia)   │
└──────────────┘  └──────────────┘  └──────────────┘
```

**優點**:
- 減少跨區域延遲
- 符合資料主權要求
- 更好的用戶體驗

**適用場景**:
- 全球化應用
- 需要符合 GDPR 等法規
- 對延遲敏感的應用

### 11.3.3 分片 key 設計原則

**應該包含**:
- 租戶/組織 ID(`org_id`)
- 主實體 ID(`user_id`、`device_id`等)

**應該避免**:
- 單調遞增純時間戳作為唯一 key(會導致最新寫入集中到同一分片)

**時間查詢的解決方案**:
- 以 `(org_id, user_id)` 分片,再在 shard 內用時間排序
- 或採用時間分區表(partitioned table)搭配 range sharding

```sql
-- ✅ 良好的複合分片鍵設計
CREATE TABLE events (
    org_id INT,
    user_id BIGINT,
    event_id UUID,
    timestamp TIMESTAMP,
    data JSONB,
    PRIMARY KEY (org_id, user_id, event_id)
) PARTITION BY HASH (org_id, user_id);

-- ❌ 避免單一時間戳作為分片鍵
CREATE TABLE events (
    timestamp TIMESTAMP PRIMARY KEY,  -- 會造成熱點
    ...
);
```

### 11.3.4 自動分片管理

實務上通常會搭配「自動 split / merge」機制:
- 當某個分片過熱或過大時自動拆分
- Spanner 以自動切分 tablet 的方式將資料分散到多個伺服器

```
自動分片分裂示例:
Initial:
┌────────────────────────┐
│ Shard A (0-1000)       │
│ Size: 100GB (過大!)    │
└────────────────────────┘
              ↓ Auto-split
After:
┌─────────────┐ ┌─────────────┐
│ Shard A1    │ │ Shard A2    │
│ (0-500)     │ │ (501-1000)  │
│ 50GB        │ │ 50GB        │
└─────────────┘ └─────────────┘
```

**參考架構圖資源**:
- [Azure Sharding Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/sharding)
- [GeeksforGeeks Database Sharding Guide](https://www.geeksforgeeks.org/system-design/database-sharding-a-system-design-concept/)
- [DigitalOcean Sharding Tutorial](https://www.digitalocean.com/community/tutorials/understanding-database-sharding)

---

## 11.4 複製(Replication)與一致性(Consistency)

雖然你章節標題沒有特別寫「複製」,但在分散式資料庫中,分片與複製是一起討論的。

### 11.4.1 複製目的

1. **容錯**:單一節點故障不會造成資料遺失
2. **讀取擴充**:讀取可以分散到多個副本
3. **跨區部署**:資料靠近使用者或系統所在區域

### 11.4.2 常見模式

#### 1. Master-Slave Replication(主從複製)

**架構圖**:

```
Master-Slave Replication:
                ┌──────────┐
                │  Master  │ ← All Writes
                │ Database │
                └─────┬────┘
                      │ Replication Log
        ┌─────────────┼─────────────┐
        ▼             ▼             ▼
   ┌────────┐    ┌────────┐    ┌────────┐
   │ Slave1 │    │ Slave2 │    │ Slave3 │
   │  (讀)  │    │  (讀)  │    │  (讀)  │
   └────────┘    └────────┘    └────────┘
```

**運作流程**:
1. 所有寫入走 master
2. Master 將變更記錄到 binary log/WAL
3. Slave 從 master 拉取並重放日誌
4. 讀取可以分散到任何 slave

**優點**:
- 實作簡單
- 讀取可擴充
- 備份不影響主庫

**缺點**:
- 寫入無法擴充(單一 master 瓶頸)
- 存在複製延遲(Replication Lag)
- Master 故障需要手動或自動提升 slave

**一致性等級**:
- **同步複製**:等待所有 slave 確認(強一致,但慢)
- **非同步複製**:不等待 slave(快,但最終一致)
- **半同步複製**:等待至少一個 slave(平衡)

```sql
-- MySQL 配置半同步複製
-- 在 Master 上
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = 1;

-- 在 Slave 上
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
SET GLOBAL rpl_semi_sync_slave_enabled = 1;
```

**Failover 處理**:

```
Master 故障處理流程:
1. 偵測 master 不可用
2. 選擇最新的 slave (檢查 replication lag)
3. 將選定的 slave 提升為新 master
4. 更新其他 slave 指向新 master
5. 更新應用程式連線
```

#### 2. Multi-Master Replication(多主複製)

**架構圖**:

```
Multi-Master Replication:
   ┌──────────┐ ←──────→ ┌──────────┐
   │ Master 1 │  雙向複製  │ Master 2 │
   │(讀 + 寫) │ ←──────→ │(讀 + 寫) │
   └──────────┘           └──────────┘
        ↕                      ↕
   可接受寫入              可接受寫入
```

**優點**:
- 寫入可擴充
- 無單點故障
- 支援跨區域寫入
- 高可用性

**缺點**:
- 寫入衝突處理複雜
- 需要衝突解決策略
- 一致性較弱(通常是最終一致)

**衝突解決策略**:
1. **Last Write Wins (LWW)**:使用時間戳,最後寫入者勝出
2. **Application-level resolution**:應用層處理衝突
3. **CRDT (Conflict-free Replicated Data Types)**:無衝突資料型別

```python
# 衝突解決示例 - Last Write Wins
def resolve_conflict(value1, value2):
    if value1.timestamp > value2.timestamp:
        return value1
    else:
        return value2
```

#### 3. Google Spanner: 全球分散式強一致

Google Spanner 則採用「同步複製 + TrueTime」實作強一致性分散式交易,被視為 global-scale 分散式資料庫的代表架構。

**Spanner 架構核心元件**:

```
Spanner Architecture:
┌──────────────────────────────────────────┐
│           Spanner Universe               │
├──────────────────────────────────────────┤
│                                          │
│  ┌────────────┐  ┌────────────┐         │
│  │   Zone 1   │  │   Zone 2   │  ...    │
│  ├────────────┤  ├────────────┤         │
│  │ Spanserver │  │ Spanserver │         │
│  │  - Tablet  │  │  - Tablet  │         │
│  │  - Paxos   │  │  - Paxos   │         │
│  └────────────┘  └────────────┘         │
│         ↓               ↓                │
│  ┌─────────────────────────────┐        │
│  │   Colossus (Storage)        │        │
│  │   Distributed File System   │        │
│  └─────────────────────────────┘        │
│                                          │
│  ┌─────────────────────────────┐        │
│  │   TrueTime API              │        │
│  │   GPS + Atomic Clocks       │        │
│  └─────────────────────────────┘        │
└──────────────────────────────────────────┘
```

**關鍵技術**:
1. **Paxos 共識算法**:確保副本間一致性
2. **TrueTime API**:提供全球時間戳,誤差界限在微秒級
3. **2PC (Two-Phase Commit)**:跨分片事務

**TrueTime 如何運作**:

```
TrueTime API:
┌────────────────────────────────┐
│ TrueTime.now()                 │
│ Returns: [earliest, latest]    │
│                                │
│ 例如: [t-ε, t+ε]               │
│ 其中 ε 是時鐘不確定性           │
└────────────────────────────────┘

Transaction Commit:
1. 取得 TrueTime: [t1, t2]
2. 等待直到 t2 確定已過去
3. 此時可以保證全球順序一致性
```

**Spanner 的資料層級**:

```
Spanner Data Hierarchy:
Universe
  └─ Database
      └─ Table
          └─ Tablet (Split)
              └─ Paxos Group
                  └─ Replica 1, 2, 3, ...
```

**Interleaving (父子表交錯)**:

```sql
-- Spanner 的 Interleaving 範例
CREATE TABLE Customers (
    customer_id INT64,
    name STRING(100),
) PRIMARY KEY (customer_id);

CREATE TABLE Invoices (
    customer_id INT64,
    invoice_id INT64,
    amount FLOAT64,
) PRIMARY KEY (customer_id, invoice_id),
  INTERLEAVE IN PARENT Customers ON DELETE CASCADE;
```

這樣 Customer 和其 Invoices 會儲存在同一個分片上,避免跨節點查詢。

**參考資源**:
- [Google Spanner 論文 (OSDI 2012)](https://research.google.com/archive/spanner-osdi2012.pdf)
- [Spanner: Google's Globally-Distributed Database](https://research.google/pubs/spanner-googles-globally-distributed-database-2/)
- [Life of Spanner Reads & Writes](https://cloud.google.com/spanner/docs/whitepapers/life-of-reads-and-writes)

### 11.4.3 一致性模型比較

| 一致性模型 | 說明 | 優點 | 缺點 | 適用場景 |
|-----------|------|------|------|----------|
| 強一致性 | 所有副本即時同步 | 資料一致 | 效能較差 | 金融交易 |
| 最終一致性 | 副本最終會同步 | 高效能 | 可能讀到舊資料 | 社交媒體 |
| 因果一致性 | 保證因果關係順序 | 平衡效能與一致性 | 實作複雜 | 協作應用 |
| 讀寫法定人數 | N 個副本中 R+W>N | 可調整一致性 | 需要協調 | 通用分散式系統 |

在教學或計畫書中,通常會先讓讀者理解:「分片解決規模問題,複製解決可靠性問題;一致性則是兩者之間的協調結果。」

---

## 11.5 索引(Index)設計

### 11.5.1 索引的作用

索引是為了加速查詢,類似「書本的目錄」。

設計不當會導致:
- 寫入成本增加(每次寫入需更新多個索引)
- 儲存空間浪費
- 查詢規劃器選錯索引,反而變慢

### 11.5.2 B-Tree vs LSM-Tree

資料庫索引的兩大主流結構:

#### B-Tree (B+ Tree)

**結構圖**:

```
B-Tree Structure:
           [50|100]
          /    |    \
    [10|30]  [60|80] [110|130]
    /  |  \   /  |  \   /  |  \
[5|8][15][40][55][70][95][105][120][140]
                 ↓
              Data Pages
```

**特性**:
- 平衡樹結構,深度一致
- 每個節點包含多個 key
- 葉節點包含實際資料或資料指標
- 支援原地更新(in-place update)

**讀寫特性**:

| 操作 | B-Tree | 說明 |
|------|--------|------|
| 讀取 | O(log N) | 優秀的讀取效能 |
| 寫入 | 較慢 | 需要隨機寫入,可能觸發頁分裂 |
| 範圍查詢 | 優秀 | 葉節點連結,順序掃描 |
| 空間效率 | 中等 | 可能有碎片化 |

**優點**:
- 讀取效能優秀
- 成熟穩定,廣泛使用
- 支援範圍查詢
- 點查詢快速

**缺點**:
- 寫入放大(Write Amplification)較高
- 隨機寫入效能較差(特別是 HDD)
- 可能需要碎片整理

**使用案例**:
- PostgreSQL、MySQL InnoDB
- Oracle Database
- SQLite
- 適合讀多寫少的場景

#### LSM-Tree (Log-Structured Merge-Tree)

**結構圖**:

```
LSM-Tree Architecture:
┌─────────────────────────────────────┐
│         Memtable (記憶體)            │
│  寫入首先進入這裡,快速寫入            │
└──────────────┬──────────────────────┘
               │ 達到閾值時 flush
               ▼
┌─────────────────────────────────────┐
│  Level 0: SSTable Files (2-4 個)    │
│  [SST1] [SST2] [SST3] [SST4]        │
└──────────────┬──────────────────────┘
               │ Compaction
               ▼
┌─────────────────────────────────────┐
│  Level 1: SSTable Files (~10 個)    │
│  [SST...] × N                       │
└──────────────┬──────────────────────┘
               │ Compaction
               ▼
┌─────────────────────────────────────┐
│  Level 2: SSTable Files (~100 個)   │
│  [SST...] × N                       │
└──────────────┬──────────────────────┘
               │
              ...
               │
┌─────────────────────────────────────┐
│  Level N: 最大最冷的資料             │
└─────────────────────────────────────┘
```

**寫入流程**:

```
LSM-Tree Write Path:
1. Write → WAL (Write-Ahead Log)
           ↓
2. Write → Memtable (in-memory)
           ↓ (when full)
3. Flush → SSTable (Level 0)
           ↓ (background)
4. Compaction → Merge to Level 1, 2, ...
```

**讀取流程**:

```
LSM-Tree Read Path:
1. Check Memtable
   ├─ Found? → Return
   └─ Not Found → Continue
2. Check Bloom Filter (每層)
   ├─ Negative? → Skip this SSTable
   └─ Possible? → Continue
3. Search Level 0 SSTables
   ├─ Found? → Return
   └─ Not Found → Continue
4. Search Level 1, 2, ... (由新到舊)
   └─ Found? → Return
```

**特性**:
- 分層儲存結構
- 寫入先進記憶體,再批次寫入磁碟
- 使用 Compaction 合併和清理資料
- 支援 Bloom Filter 加速查詢

**Compaction 策略**:

1. **Size-Tiered Compaction (STCS)**:
   - 相似大小的 SSTable 合併
   - 寫入放大較小
   - 空間放大較大

2. **Leveled Compaction (LCS)**:
   - 嚴格分層,每層大小固定
   - 空間放大較小
   - 寫入放大較大

```
Compaction 示例:
Level 0: [A][B][C][D]  (4個 SSTables, 每個 10MB)
              ↓ Compact
Level 1: [Merged_ABCD] (1個 SSTable, 40MB)
```

**讀寫特性**:

| 操作 | LSM-Tree | 說明 |
|------|----------|------|
| 寫入 | O(1) amortized | 優秀的寫入效能 |
| 讀取 | O(log N) - O(N) | 可能需要查多層 |
| 範圍查詢 | 較慢 | 需要合併多個 SSTable |
| 空間效率 | 較高 | 但有空間放大 |

**優點**:
- 寫入效能優秀(順序寫入)
- 適合 SSD
- 壓縮效率高
- 寫入放大可控

**缺點**:
- 讀取可能較慢(需查多層)
- Compaction 會消耗 CPU 和 I/O
- 讀放大(Read Amplification)較高
- 空間放大需要管理

**使用案例**:
- RocksDB、LevelDB
- Apache Cassandra
- ScyllaDB
- HBase
- 適合寫多讀少的場景

#### 效能放大比較

```
放大係數比較:
┌──────────────┬──────────┬──────────┐
│   放大類型    │  B-Tree  │ LSM-Tree │
├──────────────┼──────────┼──────────┤
│ 寫入放大     │   較高   │   較低   │
│ 讀取放大     │   較低   │   較高   │
│ 空間放大     │   中等   │   中等   │
└──────────────┴──────────┴──────────┘

寫入放大 = 實際寫入磁碟 / 應用寫入
讀取放大 = 實際讀取磁碟 / 應用讀取
空間放大 = 實際佔用空間 / 資料大小
```

**混合架構**:
某些現代資料庫(如 CockroachDB)採用混合方式:
- 底層使用 LSM-Tree 儲存引擎
- 上層提供 B-Tree 語意的 SQL 介面

### 11.5.3 實務設計原則

#### 1. 以查詢為中心設計索引

```sql
-- 分析常見查詢
-- 查詢 1: 按組織和時間範圍查詢
SELECT * FROM events 
WHERE org_id = ? 
AND created_at BETWEEN ? AND ?;

-- 查詢 2: 按狀態查詢
SELECT * FROM tasks 
WHERE org_id = ? 
AND status = 'pending';

-- 針對性建立索引
CREATE INDEX idx_events_org_time 
ON events(org_id, created_at);

CREATE INDEX idx_tasks_org_status 
ON tasks(org_id, status);
```

#### 2. 複合索引(Composite Index)

索引欄位順序很重要:

```sql
-- ✅ 正確:最左前綴原則
CREATE INDEX idx_composite 
ON table (org_id, user_id, created_at);

-- 支援以下查詢:
-- 1. WHERE org_id = ?
-- 2. WHERE org_id = ? AND user_id = ?
-- 3. WHERE org_id = ? AND user_id = ? AND created_at > ?

-- ❌ 無法有效支援:
-- WHERE user_id = ?  (沒有 org_id)
-- WHERE created_at > ?  (沒有前面的欄位)
```

**索引順序選擇策略**:
1. 等值查詢欄位在前
2. 範圍查詢欄位在後
3. 選擇性高的欄位在前

```sql
-- 範例:按查詢頻率和選擇性設計
-- 假設查詢模式:
-- - org_id: 高選擇性,總是使用
-- - status: 低選擇性 (只有幾個值)
-- - created_at: 範圍查詢

CREATE INDEX idx_optimal 
ON orders (org_id, created_at, status);
-- org_id 在最前(高選擇性 + 總是使用)
-- created_at 其次(範圍查詢)
-- status 最後(低選擇性)
```

#### 3. 避免過度索引

```sql
-- ❌ 過度索引示例
CREATE INDEX idx1 ON users(email);
CREATE INDEX idx2 ON users(email, name);  -- 可能多餘
CREATE INDEX idx3 ON users(email, name, age);  -- 更多餘
CREATE INDEX idx4 ON users(created_at);
CREATE INDEX idx5 ON users(updated_at);
CREATE INDEX idx6 ON users(is_active);  -- 低選擇性,效益低

-- ✅ 精簡索引
CREATE UNIQUE INDEX idx_email ON users(email);  -- 唯一索引
CREATE INDEX idx_active_created 
ON users(is_active, created_at) 
WHERE is_active = true;  -- 部分索引
```

**索引成本評估**:

```
每個索引的代價:
┌────────────────────┬─────────────────┐
│   操作             │   影響          │
├────────────────────┼─────────────────┤
│ INSERT             │ 每個索引 +1 寫入 │
│ UPDATE (索引欄位)  │ 每個索引重建     │
│ DELETE             │ 每個索引標記刪除 │
│ 儲存空間           │ 索引大小累加     │
│ 記憶體             │ 索引緩存空間     │
└────────────────────┴─────────────────┘
```

#### 4. 分散式資料庫中的二級索引

在 Spanner 這類系統中,二級索引本身也會被分片與複製:

```sql
-- Spanner 二級索引範例
CREATE INDEX idx_user_email 
ON users(email)
STORING (name, created_at);  -- 包含額外欄位避免回表

-- 此索引會:
-- 1. 自己被分片儲存
-- 2. 被複製以確保可用性
-- 3. 與主表可能不在同一個分片
```

**全局索引 vs 局部索引**:

```
Global Index (全局索引):
┌─────────────────────────────┐
│    索引表(獨立分片)          │
│  key → shard_location        │
└─────────────────────────────┘
優點: 查詢快,缺點: 寫入慢

Local Index (局部索引):
每個資料分片維護自己的索引
優點: 寫入快,缺點: 查詢需要掃描所有分片
```

#### 5. 索引維護策略

```sql
-- 監控索引使用情況 (PostgreSQL)
SELECT 
    schemaname,
    tablename,
    indexname,
    idx_scan,  -- 索引被使用次數
    idx_tup_read,
    idx_tup_fetch
FROM pg_stat_user_indexes
WHERE idx_scan = 0  -- 找出從未使用的索引
ORDER BY schemaname, tablename;

-- 重建碎片化的索引
REINDEX INDEX idx_name;

-- 分析索引效能
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = 'test@example.com';
```

### 11.5.4 進階索引技術

#### 1. 部分索引(Partial Index)

```sql
-- 只索引活躍用戶
CREATE INDEX idx_active_users 
ON users(email) 
WHERE is_active = true;

-- 節省空間且提高效能
```

#### 2. 表達式索引(Expression Index)

```sql
-- 對計算欄位建索引
CREATE INDEX idx_lower_email 
ON users(LOWER(email));

-- 支援不區分大小寫查詢
SELECT * FROM users 
WHERE LOWER(email) = 'test@example.com';
```

#### 3. 覆蓋索引(Covering Index)

```sql
-- 包含查詢所需的所有欄位,避免回表
CREATE INDEX idx_covering 
ON orders(user_id, created_at)
INCLUDE (amount, status);

-- 此查詢可以只從索引取得資料
SELECT amount, status 
FROM orders 
WHERE user_id = ? 
AND created_at > ?;
```

**參考資源**:
- [TiKV: B-Tree vs LSM-Tree 詳細分析](https://tikv.org/deep-dive/key-value-engine/b-tree-vs-lsm/)
- [USENIX: Revisiting B+-tree vs. LSM-tree](https://www.usenix.org/publications/loginonline/revisit-b-tree-vs-lsm-tree-upon-arrival-modern-storage-hardware-built)

---

## 11.6 快取(Caching)設計

### 11.6.1 為什麼需要快取

當相同的查詢或資料被頻繁讀取時,直接打資料庫會造成:
- 資料庫負載升高
- 延遲變大
- 成本增加

透過快取,可以把熱門資料放在較快的媒介(記憶體/分散式快取),降低主資料庫負擔。

**快取效益**:

```
無快取 vs 有快取:
┌────────────────────┬──────────┬──────────┐
│      指標          │  無快取  │  有快取  │
├────────────────────┼──────────┼──────────┤
│ 平均回應時間       │  100ms   │   5ms    │
│ 資料庫 QPS         │  1000    │   100    │
│ 快取命中率         │   N/A    │   90%    │
│ 成本(相對)         │   1.0x   │   0.3x   │
└────────────────────┴──────────┴──────────┘
```

### 11.6.2 快取層級

#### 1. 應用程式內快取(In-Process Cache)

```python
# 使用 LRU Cache
from functools import lru_cache

@lru_cache(maxsize=1000)
def get_user_profile(user_id):
    return db.query(f"SELECT * FROM users WHERE id = {user_id}")
```

**優點**:
- 延遲最低(無網路開銷)
- 實作簡單
- 無額外成本

**缺點**:
- 無法跨伺服器共享
- 記憶體受限於單機
- 擴展困難

**適用場景**:
- 單體應用
- 計算密集型快取
- 配置資料

#### 2. 分散式快取(Redis / Memcached)

**架構圖**:

```
分散式快取架構:
┌──────────┐  ┌──────────┐  ┌──────────┐
│  App 1   │  │  App 2   │  │  App 3   │
└────┬─────┘  └────┬─────┘  └────┬─────┘
     │             │             │
     └─────────────┼─────────────┘
                   ▼
          ┌─────────────────┐
          │  Redis Cluster  │
          │  ┌───┬───┬───┐  │
          │  │ M1│ M2│ M3│  │
          │  └───┴───┴───┘  │
          └─────────────────┘
                   ▼
          ┌─────────────────┐
          │    Database     │
          └─────────────────┘
```

**Redis 特性**:
- 支援多種資料結構(String, Hash, List, Set, Sorted Set)
- 持久化選項(RDB, AOF)
- 發布/訂閱功能
- Lua 腳本支援
- 叢集模式

**Memcached 特性**:
- 純記憶體,無持久化
- 簡單的 key-value 儲存
- 多執行緒
- 極致效能

**比較**:

| 特性 | Redis | Memcached |
|------|-------|-----------|
| 資料結構 | 豐富(5+種) | 簡單(key-value) |
| 持久化 | 支援 | 不支援 |
| 複製 | 支援 | 不支援 |
| 事務 | 支援 | 不支援 |
| Lua 腳本 | 支援 | 不支援 |
| 多執行緒 | 單執行緒* | 多執行緒 |
| 效能 | 優秀 | 極致 |

*Redis 6.0 後支援多執行緒 I/O

**適合存放的資料**:
- Session 資料
- 使用者設定
- 熱門查詢結果
- 排行榜(使用 Sorted Set)
- 計數器
- 即時資料

```python
# Redis 使用範例
import redis

r = redis.Redis(host='localhost', port=6379, db=0)

# 快取使用者資料
def get_user(user_id):
    cache_key = f"user:{user_id}"
    
    # 1. 先查快取
    cached = r.get(cache_key)
    if cached:
        return json.loads(cached)
    
    # 2. 查詢資料庫
    user = db.query(f"SELECT * FROM users WHERE id = {user_id}")
    
    # 3. 寫入快取(設定過期時間)
    r.setex(cache_key, 3600, json.dumps(user))
    
    return user
```

### 11.6.3 快取更新策略

#### 1. Cache-Aside (Lazy Loading)

**流程圖**:

```
Cache-Aside Pattern:
┌─────────┐
│  App    │
└────┬────┘
     │
     │ 1. Read
     ▼
┌──────────┐   2. Miss
│  Cache   │ ────────┐
└──────────┘         │
     ▲               │
     │               │ 3. Query
     │               ▼
     │         ┌──────────┐
     │ 4.Write │ Database │
     └─────────└──────────┘
     
     5. Return Data
```

**程式碼範例**:

```python
def get_data(key):
    # 1. 嘗試從快取讀取
    data = cache.get(key)
    
    # 2. 快取命中,直接返回
    if data is not None:
        return data
    
    # 3. 快取未命中,查詢資料庫
    data = database.query(key)
    
    # 4. 寫入快取
    cache.set(key, data, ttl=3600)
    
    # 5. 返回資料
    return data

def update_data(key, value):
    # 1. 更新資料庫
    database.update(key, value)
    
    # 2. 刪除快取(或更新快取)
    cache.delete(key)
    # 或: cache.set(key, value, ttl=3600)
```

**優點**:
- 實作簡單
- 只快取實際需要的資料
- 快取失效後自動重新載入

**缺點**:
- 首次讀取慢(cache miss)
- 可能出現快取不一致
- 驚群效應(Thundering Herd)

#### 2. Read-Through

**流程圖**:

```
Read-Through Pattern:
┌─────────┐
│  App    │
└────┬────┘
     │ 1. Read
     ▼
┌──────────┐
│  Cache   │
│  Layer   │
└────┬─────┘
     │ 2. Cache Miss
     │ (自動查詢)
     ▼
┌──────────┐
│ Database │
└──────────┘
```

**特點**:
- 快取層自己負責載入資料
- 應用不需要處理快取邏輯
- 類似 Cache-Aside,但邏輯在快取層

#### 3. Write-Through

**流程圖**:

```
Write-Through Pattern:
┌─────────┐
│  App    │
└────┬────┘
     │ 1. Write
     ▼
┌──────────┐  2. Sync Write
│  Cache   │ ────────────┐
└──────────┘             │
                         ▼
                    ┌──────────┐
                    │ Database │
                    └──────────┘
                         │
                         │ 3. Ack
                         ▼
                    Response
```

**程式碼範例**:

```python
def update_data(key, value):
    # 1. 同時寫入快取
    cache.set(key, value, ttl=3600)
    
    # 2. 寫入資料庫(同步)
    database.update(key, value)
    
    # 3. 兩者都成功才返回
    return True
```

**優點**:
- 資料一致性強
- 快取總是最新的
- 讀取效能好

**缺點**:
- 寫入延遲增加(需等待兩個操作)
- 可能寫入不常用的資料
- 系統耦合度高

#### 4. Write-Behind (Write-Back)

**流程圖**:

```
Write-Behind Pattern:
┌─────────┐
│  App    │
└────┬────┘
     │ 1. Write
     ▼
┌──────────┐  2. Async
│  Cache   │ ─────────┐
└──────────┘          │
     │                │ (Background)
     │ 3. Ack         │
     │                ▼
     │           ┌──────────┐
     └───────────│ Database │
                 └──────────┘
```

**特點**:
- 寫入先更新快取
- 背景程序非同步同步到資料庫
- 寫入效能最好,但風險最高

**優點**:
- 寫入效能極佳
- 減少資料庫負載
- 批次寫入效率高

**缺點**:
- 資料可能遺失(快取故障時)
- 實作複雜
- 不適合關鍵資料

```python
# Write-Behind 範例(簡化)
class WriteBehindCache:
    def __init__(self):
        self.cache = {}
        self.dirty_keys = set()
        self.start_background_sync()
    
    def write(self, key, value):
        # 1. 立即寫入快取
        self.cache[key] = value
        self.dirty_keys.add(key)
        # 2. 立即返回
        return True
    
    def background_sync(self):
        while True:
            time.sleep(5)  # 每 5 秒同步一次
            for key in list(self.dirty_keys):
                value = self.cache[key]
                database.update(key, value)
                self.dirty_keys.remove(key)
```

### 11.6.4 快取模式比較

| 模式 | 讀取 | 寫入 | 一致性 | 複雜度 | 適用場景 |
|------|------|------|--------|--------|----------|
| Cache-Aside | 應用控制 | 應用控制 | 最終一致 | 低 | 通用場景 |
| Read-Through | 快取自動 | 應用控制 | 最終一致 | 中 | 讀密集 |
| Write-Through | 任意 | 同步寫入 | 強一致 | 中 | 一致性重要 |
| Write-Behind | 任意 | 非同步寫入 | 弱一致 | 高 | 寫密集,可容忍資料遺失 |

### 11.6.5 快取失效策略

#### 1. TTL (Time To Live)

```python
# 設定過期時間
cache.setex("user:123", 3600, user_data)  # 1 小時後過期

# 階梯式 TTL
CACHE_TTL = {
    'user_profile': 3600,      # 1 小時
    'config': 86400,           # 1 天
    'hot_data': 300,           # 5 分鐘
    'analytics': 7200          # 2 小時
}
```

#### 2. 主動失效(Active Invalidation)

```python
def update_user(user_id, data):
    # 1. 更新資料庫
    db.update(user_id, data)
    
    # 2. 主動刪除相關快取
    cache.delete(f"user:{user_id}")
    cache.delete(f"user:profile:{user_id}")
    cache.delete(f"user:permissions:{user_id}")
```

#### 3. 標籤式失效(Tag-based Invalidation)

```python
# 使用標籤管理相關快取
def set_with_tags(key, value, tags):
    cache.set(key, value)
    for tag in tags:
        cache.sadd(f"tag:{tag}", key)

def invalidate_by_tag(tag):
    keys = cache.smembers(f"tag:{tag}")
    for key in keys:
        cache.delete(key)

# 使用範例
set_with_tags("user:123:profile", data, ["user:123", "profiles"])
invalidate_by_tag("user:123")  # 刪除該用戶所有快取
```

### 11.6.6 快取常見問題與解決方案

#### 1. Cache Stampede (快取雪崩)

**問題**:熱門 key 過期時,大量請求同時打到資料庫

**解決方案**:

```python
import threading

locks = {}

def get_with_lock(key):
    # 1. 檢查快取
    data = cache.get(key)
    if data:
        return data
    
    # 2. 使用鎖確保只有一個請求查詢資料庫
    if key not in locks:
        locks[key] = threading.Lock()
    
    with locks[key]:
        # Double-check
        data = cache.get(key)
        if data:
            return data
        
        # 3. 查詢資料庫
        data = database.query(key)
        
        # 4. 寫入快取
        cache.setex(key, 3600, data)
        
        return data
```

#### 2. Cache Penetration (快取穿透)

**問題**:查詢不存在的資料,繞過快取直接打資料庫

**解決方案**:

```python
# 方案 1: 快取空值
def get_user(user_id):
    cached = cache.get(f"user:{user_id}")
    if cached == "NULL":  # 空值標記
        return None
    if cached:
        return cached
    
    user = db.query(user_id)
    if user:
        cache.setex(f"user:{user_id}", 3600, user)
    else:
        cache.setex(f"user:{user_id}", 60, "NULL")  # 快取空值,短 TTL
    return user

# 方案 2: Bloom Filter
from pybloom_live import BloomFilter

user_bloom = BloomFilter(capacity=1000000, error_rate=0.001)

def get_user_with_bloom(user_id):
    # 先檢查 Bloom Filter
    if user_id not in user_bloom:
        return None  # 一定不存在
    
    # 可能存在,繼續正常流程
    return get_user(user_id)
```

#### 3. Cache Avalanche (快取崩潰)

**問題**:大量快取同時過期,資料庫瞬間壓力暴增

**解決方案**:

```python
import random

def set_with_jitter(key, value, base_ttl):
    # 在基礎 TTL 上加上隨機時間
    jitter = random.randint(0, base_ttl // 10)
    actual_ttl = base_ttl + jitter
    cache.setex(key, actual_ttl, value)

# 使用範例
set_with_jitter("user:123", user_data, 3600)  # 3600±360 秒
```

**參考資源**:
- [AWS: Database Caching Strategies Using Redis](https://docs.aws.amazon.com/whitepapers/latest/database-caching-strategies-using-redis/caching-patterns.html)
- [Redis.io: Caching Patterns](https://redis.io/solutions/caching/)
- [Azure: Cache-Aside Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/cache-aside)

---

## 11.7 儲存規劃(Hot / Warm / Cold Storage)

### 11.7.1 多層儲存的動機

不同資料有不同特性:

- **熱資料(Hot)**:近期頻繁存取,例如最近一個月交易
- **溫資料(Warm)**:偶爾查詢,例如過去一年紀錄
- **冷資料(Cold)**:主要為合規與稽核保存,幾乎不查

雲端與混合雲架構中,將資料分層儲存可以同時兼顧成本與效能。

**儲存層級金字塔**:

```
Storage Tier Pyramid:
        ┌──────────┐
        │   Hot    │  ← 最快、最貴、容量小
        │  (SSD)   │
        ├──────────┤
        │   Warm   │  ← 中速、中價、容量中
        │  (HDD)   │
        ├──────────┤
        │   Cold   │  ← 最慢、最便宜、容量大
        │(Object)  │
        ├──────────┤
        │  Frozen  │  ← 歸檔、極少存取
        │ (Glacier)│
        └──────────┘

成本比例(以 AWS 為例):
Hot (SSD):     $0.10/GB/月
Warm (HDD):    $0.045/GB/月
Cold (S3):     $0.023/GB/月
Frozen (Glacier): $0.004/GB/月
```

### 11.7.2 實務規劃範例

#### Hot Storage (熱儲存)

**技術選擇**:
- RDBMS(PostgreSQL / MySQL / 分散式 SQL)
- 高效能儲存、SSD
- 記憶體快取(Redis)

**保留範圍**:最近 3-6 個月業務必要資料

**用途**:
- 線上交易(OLTP)
- 即時查詢
- 儀表板
- 應用程式主資料

**效能指標**:
- 延遲: < 10ms
- IOPS: > 10,000
- 可用性: 99.99%

```sql
-- Hot Storage 資料庫設計
CREATE TABLE transactions (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT NOT NULL,
    amount DECIMAL(10,2),
    created_at TIMESTAMP DEFAULT NOW(),
    INDEX idx_created (created_at)
) 
-- 只保留最近 3 個月
PARTITION BY RANGE (created_at);

-- 建立分區
CREATE TABLE transactions_2025_01 
PARTITION OF transactions 
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
```

#### Warm Storage (溫儲存)

**技術選擇**:
- 資料倉儲(BigQuery、Snowflake、Redshift)
- 時序資料庫(InfluxDB、TimescaleDB)
- 部分物件儲存

**保留範圍**:1-3 年

**用途**:
- 報表分析(OLAP)
- 模型訓練
- 歷史分析
- 趨勢查詢

**效能指標**:
- 延遲: < 1s
- 批次處理優先
- 可用性: 99.9%

```sql
-- Warm Storage 範例(資料倉儲)
CREATE TABLE analytics_events (
    event_id STRING,
    user_id INT64,
    event_type STRING,
    properties JSON,
    event_time TIMESTAMP
)
PARTITION BY DATE(event_time)
CLUSTER BY user_id, event_type;

-- 查詢最佳化
SELECT 
    DATE(event_time) as date,
    event_type,
    COUNT(*) as count
FROM analytics_events
WHERE event_time BETWEEN '2024-01-01' AND '2024-12-31'
GROUP BY 1, 2;
```

#### Cold Storage (冷儲存)

**技術選擇**:
- 物件儲存(S3、GCS、Azure Blob)
- 冷儲存階層
- 備份系統
- 歸檔系統

**保留範圍**:依法規,例如 5-7 年以上

**用途**:
- 合規要求
- 稽核備查
- 災難恢復
- 罕見回溯

**效能指標**:
- 延遲: 分鐘到小時級別
- 檢索成本考量
- 可用性: 99%

```python
# 資料生命週期管理範例(AWS S3)
import boto3

s3 = boto3.client('s3')

# 設定生命週期規則
lifecycle_config = {
    'Rules': [
        {
            'Id': 'Move to Cold Storage',
            'Status': 'Enabled',
            'Filter': {'Prefix': 'logs/'},
            'Transitions': [
                # 30 天後移到 S3 IA
                {
                    'Days': 30,
                    'StorageClass': 'STANDARD_IA'
                },
                # 90 天後移到 Glacier
                {
                    'Days': 90,
                    'StorageClass': 'GLACIER'
                },
                # 365 天後移到 Deep Archive
                {
                    'Days': 365,
                    'StorageClass': 'DEEP_ARCHIVE'
                }
            ],
            # 7 年後刪除
            'Expiration': {
                'Days': 2555  # 7 * 365
            }
        }
    ]
}

s3.put_bucket_lifecycle_configuration(
    Bucket='my-bucket',
    LifecycleConfiguration=lifecycle_config
)
```

### 11.7.3 資料遷移策略

#### 自動分層(Auto-Tiering)

```
Data Lifecycle Flow:
┌──────────┐  3個月後  ┌──────────┐  1年後  ┌──────────┐
│   Hot    │ ────────→ │  Warm    │ ──────→ │  Cold    │
│   SSD    │           │   HDD    │         │  Object  │
└──────────┘           └──────────┘         └──────────┘
  即時存取              分析查詢             歸檔合規
  
  自動化策略:
  - 按時間自動遷移
  - 按存取頻率
  - 按業務規則
  - 按成本優化
```

#### ETL 流程範例

```python
# 資料分層 ETL 範例
from datetime import datetime, timedelta

class DataTieringETL:
    def __init__(self):
        self.hot_db = HotDatabase()
        self.warm_dw = WarmDataWarehouse()
        self.cold_storage = ColdObjectStorage()
    
    def daily_tiering_job(self):
        # 1. Hot → Warm: 遷移 90 天前的資料
        cutoff_warm = datetime.now() - timedelta(days=90)
        old_data = self.hot_db.query(
            f"SELECT * FROM events WHERE created_at < '{cutoff_warm}'"
        )
        
        if old_data:
            # 寫入 Warm
            self.warm_dw.batch_insert(old_data)
            # 從 Hot 刪除
            self.hot_db.delete(
                f"DELETE FROM events WHERE created_at < '{cutoff_warm}'"
            )
        
        # 2. Warm → Cold: 遷移 1 年前的資料
        cutoff_cold = datetime.now() - timedelta(days=365)
        archive_data = self.warm_dw.query(
            f"SELECT * FROM events WHERE created_at < '{cutoff_cold}'"
        )
        
        if archive_data:
            # 壓縮並寫入 Cold
            compressed = self.compress(archive_data)
            self.cold_storage.upload(
                f"archive/{cutoff_cold.year}/events.parquet.gz",
                compressed
            )
            # 從 Warm 刪除
            self.warm_dw.delete(
                f"DELETE FROM events WHERE created_at < '{cutoff_cold}'"
            )
```

### 11.7.4 混合查詢策略

當資料分散在多個儲存層級時,需要智能查詢路由:

```python
class UnifiedDataAccessLayer:
    def query_events(self, start_date, end_date):
        results = []
        now = datetime.now()
        
        # 1. 判斷資料範圍分佈
        hot_cutoff = now - timedelta(days=90)
        warm_cutoff = now - timedelta(days=365)
        
        # 2. 路由到對應的儲存層
        if end_date > hot_cutoff:
            # 查詢 Hot
            hot_results = self.hot_db.query(
                start=max(start_date, hot_cutoff),
                end=end_date
            )
            results.extend(hot_results)
        
        if start_date < hot_cutoff and end_date > warm_cutoff:
            # 查詢 Warm
            warm_results = self.warm_dw.query(
                start=max(start_date, warm_cutoff),
                end=min(end_date, hot_cutoff)
            )
            results.extend(warm_results)
        
        if start_date < warm_cutoff:
            # 查詢 Cold
            cold_results = self.cold_storage.query(
                start=start_date,
                end=min(end_date, warm_cutoff)
            )
            results.extend(cold_results)
        
        # 3. 合併結果
        return self.merge_and_sort(results)
```

### 11.7.5 Elasticsearch 多層架構案例

Elasticsearch 提供了完整的 Hot-Warm-Cold-Frozen 架構:

```
Elasticsearch Data Tiers:
┌─────────────────────────────────┐
│  Hot Tier (高效能 SSD)          │
│  - 最新資料                     │
│  - 高頻查詢                     │
│  - Indexing + Search           │
│  節點配置: data_hot             │
└──────────────┬──────────────────┘
               │ ILM Policy
               ▼
┌─────────────────────────────────┐
│  Warm Tier (低效能 SSD/HDD)     │
│  - 較舊資料                     │
│  - 低頻查詢                     │
│  - Read-only                   │
│  節點配置: data_warm            │
└──────────────┬──────────────────┘
               │
               ▼
┌─────────────────────────────────┐
│  Cold Tier (HDD/部分掛載)       │
│  - 歷史資料                     │
│  - 罕見查詢                     │
│  - Searchable Snapshots        │
│  節點配置: data_cold            │
└──────────────┬──────────────────┘
               │
               ▼
┌─────────────────────────────────┐
│  Frozen Tier (快照)             │
│  - 歸檔資料                     │
│  - 極少查詢                     │
│  - Partial Mount from Snapshot │
│  節點配置: data_frozen          │
└─────────────────────────────────┘
```

**ILM Policy 範例**:

```json
{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": {
            "max_age": "7d",
            "max_size": "50gb"
          }
        }
      },
      "warm": {
        "min_age": "30d",
        "actions": {
          "shrink": {
            "number_of_shards": 1
          },
          "forcemerge": {
            "max_num_segments": 1
          }
        }
      },
      "cold": {
        "min_age": "90d",
        "actions": {
          "searchable_snapshot": {
            "snapshot_repository": "my_repository"
          }
        }
      },
      "frozen": {
        "min_age": "365d",
        "actions": {
          "searchable_snapshot": {
            "snapshot_repository": "my_repository",
            "storage": "shared_cache"
          }
        }
      },
      "delete": {
        "min_age": "2555d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}
```

### 11.7.6 成本優化計算

**範例計算**:假設有 100TB 資料

```
全部使用 Hot Storage (SSD):
100TB × $0.10/GB/月 = 100,000GB × $0.10 = $10,000/月

分層儲存策略:
- Hot (10TB):   10,000GB × $0.10 = $1,000/月
- Warm (30TB):  30,000GB × $0.045 = $1,350/月
- Cold (60TB):  60,000GB × $0.023 = $1,380/月
總計: $3,730/月

節省: ($10,000 - $3,730) / $10,000 = 62.7%
```

**參考資源**:
- [Elastic: Data Tiers Documentation](https://www.elastic.co/docs/manage-data/lifecycle/data-tiers)
- [Azure: Hot, Warm, Cold Data Path Design](https://www.futurelearn.com/info/courses/introduction-azure-architect-technologies/0/steps/325799)
- [CTERA: Hot vs Warm vs Cold Storage](https://www.ctera.com/company/blog/differences-hot-warm-cold-file-storage/)

---

## 11.8 完整架構整合範例

### 11.8.1 MLOps 場景的完整架構

```
完整 Database & Storage 架構圖:
┌──────────────────────────────────────────────────────────────┐
│                      應用層                                   │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                   │
│  │ ML 訓練  │  │ 特徵提取 │  │ 推論服務 │                   │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘                   │
└───────┼─────────────┼─────────────┼─────────────────────────┘
        │             │             │
        │        ┌────▼────────────▼────┐
        │        │   快取層 (Redis)     │
        │        │   - Session          │
        │        │   - 熱門查詢         │
        │        └────────┬─────────────┘
        │                 │
        │        ┌────────▼─────────────┐
        │        │   主資料庫叢集       │
        │        │   ┌───┬───┬───┐      │
        │        │   │M1 │M2 │M3 │      │
        │        │   └───┴───┴───┘      │
        │        │   Sharding + Replica │
        │        └────────┬─────────────┘
        │                 │
        ▼                 ▼
┌─────────────┐  ┌─────────────────┐
│ Hot Storage │  │  Warm Storage   │
│   (3 個月)  │  │   (1-3 年)      │
│             │  │                 │
│  訓練資料   │  │  資料倉儲       │
│  特徵庫     │  │  - BigQuery     │
│  即時推論   │  │  - Snowflake    │
└──────┬──────┘  └────────┬────────┘
       │                  │
       │         ┌────────▼────────┐
       │         │  Cold Storage   │
       │         │  (3+ 年)        │
       │         │                 │
       └─────────│  - S3 Glacier   │
                 │  - 合規歸檔     │
                 │  - 模型版本     │
                 └─────────────────┘
```

### 11.8.2 關鍵決策矩陣

**選擇資料庫時的考量**:

| 需求 | 推薦方案 | 原因 |
|------|----------|------|
| 強一致性交易 | PostgreSQL / Spanner | ACID 保證 |
| 寫入密集 | Cassandra / ScyllaDB | LSM-Tree 優化寫入 |
| 讀取密集 | MySQL / PostgreSQL | B-Tree 優化讀取 |
| 時序資料 | InfluxDB / TimescaleDB | 時間優化 |
| 文件儲存 | MongoDB / CouchDB | 靈活 Schema |
| 全文搜尋 | Elasticsearch | 倒排索引 |
| 圖資料 | Neo4j / JanusGraph | 關係查詢 |
| 快取 | Redis / Memcached | 記憶體速度 |

**分片策略選擇**:

| 場景 | 推薦策略 | 理由 |
|------|----------|------|
| 用戶資料 | Hash Sharding | 均勻分佈 |
| 時序日誌 | Range Sharding + 定期重平衡 | 支援範圍查詢 |
| 多租戶 SaaS | Directory Sharding (按 org_id) | 資料隔離 |
| 全球化應用 | Geographical Sharding | 減少延遲 |

**複製策略選擇**:

| 需求 | 推薦策略 | 權衡 |
|------|----------|------|
| 強一致性 | 同步複製 | 延遲較高 |
| 高吞吐量 | 非同步複製 | 可能資料延遲 |
| 跨區域 | 非同步多主 | 需要衝突解決 |
| 讀取擴展 | 主從複製 | 簡單實作 |

---

## 11.9 最佳實踐總結

### 11.9.1 設計原則

1. **以查詢模式驅動設計**
   - 先了解 80% 的查詢模式
   - Schema 和索引配合查詢優化
   - 避免過早優化

2. **適度反正規化**
   - 分散式環境中接受部分重複
   - 減少跨節點 JOIN
   - 權衡一致性與效能

3. **漸進式擴展**
   - 從單機開始,根據需求擴展
   - 避免過早分片
   - 保留擴展空間

4. **資料生命週期管理**
   - 自動化冷熱資料分層
   - 定期清理過期資料
   - 備份與歸檔策略

5. **監控與調優**
   - 持續監控關鍵指標
   - 定期檢視慢查詢
   - 根據實際負載調整

### 11.9.2 效能優化檢查清單

**資料庫層面**:
- [ ] 主鍵設計合理
- [ ] 索引覆蓋主要查詢
- [ ] 避免過度索引
- [ ] 查詢使用正確的索引
- [ ] 批次操作替代逐筆操作
- [ ] 連線池配置適當

**分片層面**:
- [ ] 分片鍵選擇合理
- [ ] 避免熱點分片
- [ ] 跨分片查詢最小化
- [ ] 自動平衡機制啟用

**快取層面**:
- [ ] 快取命中率 > 80%
- [ ] TTL 設定合理
- [ ] 快取更新策略明確
- [ ] 避免快取穿透/雪崩

**儲存層面**:
- [ ] 冷熱資料分離
- [ ] 生命週期策略自動化
- [ ] 壓縮與歸檔啟用
- [ ] 成本定期審核

### 11.9.3 故障處理與災難恢復

**備份策略**:

```
3-2-1 備份原則:
- 3 份資料副本
- 2 種不同儲存媒介
- 1 份異地備份

備份頻率建議:
┌──────────────┬──────────┬──────────┐
│   資料類型    │   頻率   │ 保留期   │
├──────────────┼──────────┼──────────┤
│ 關鍵交易資料 │  即時    │ 永久     │
│ 業務資料     │  每日    │ 90 天    │
│ 分析資料     │  每週    │ 30 天    │
│ 日誌資料     │  每日    │ 7 天     │
└──────────────┴──────────┴──────────┘
```

**災難恢復(DR)流程**:

```
RTO (Recovery Time Objective) 目標:
┌────────────────┬──────────┐
│   系統等級     │   RTO    │
├────────────────┼──────────┤
│ 關鍵系統       │  < 1 小時│
│ 重要系統       │  < 4 小時│
│ 一般系統       │  < 24小時│
└────────────────┴──────────┘

RPO (Recovery Point Objective) 目標:
┌────────────────┬──────────┐
│   資料類型     │   RPO    │
├────────────────┼──────────┤
│ 金融交易       │  0 (即時)│
│ 用戶資料       │  < 15 分 │
│ 分析資料       │  < 1 小時│
└────────────────┴──────────┘
```

---

## 11.10 延伸閱讀與參考文獻

### 核心書籍

1. **Martin Kleppmann**, *Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems*, O'Reilly, 2017
   - 資料密集型應用的聖經
   - 深入探討資料模型、儲存引擎、分散式系統

2. **Alex Petrov**, *Database Internals: A Deep Dive into How Distributed Data Systems Work*, O'Reilly, 2019
   - 資料庫內部實作細節
   - B-Tree、LSM-Tree 深入分析

### 重要論文

1. **J. C. Corbett et al.**, "Spanner: Google's Globally Distributed Database," OSDI 2012
   - https://research.google.com/archive/spanner-osdi2012.pdf
   - 全球分散式資料庫的里程碑

2. **Giuseppe DeCandia et al.**, "Dynamo: Amazon's Highly Available Key-value Store," SOSP 2007
   - NoSQL 設計的基礎
   - 最終一致性模型

3. **Fay Chang et al.**, "Bigtable: A Distributed Storage System for Structured Data," OSDI 2006
   - 列式資料庫設計
   - LSM-Tree 應用

### 線上資源

**分片與分散式**:
- [Azure Sharding Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/sharding)
- [GeeksforGeeks: Database Sharding](https://www.geeksforgeeks.org/system-design/database-sharding-a-system-design-concept/)
- [DigitalOcean: Understanding Database Sharding](https://www.digitalocean.com/community/tutorials/understanding-database-sharding)

**Google Spanner**:
- [Google Cloud: Spanner 文件](https://cloud.google.com/spanner/docs)
- [Life of Spanner Reads & Writes](https://cloud.google.com/spanner/docs/whitepapers/life-of-reads-and-writes)
- [Spanner Schema Design Best Practices](https://cloud.google.com/spanner/docs/schema-design)

**複製與一致性**:
- [GeeksforGeeks: Replication Types](https://www.geeksforgeeks.org/types-of-database-replication-system-design/)
- [Medium: Master-Master vs Master-Slave](https://medium.com/@techsuneel99/database-replication-master-slave-architecture-1a1246397a22)

**索引結構**:
- [TiKV: B-Tree vs LSM-Tree](https://tikv.org/deep-dive/key-value-engine/b-tree-vs-lsm/)
- [USENIX: B+-tree vs LSM-tree 再探討](https://www.usenix.org/publications/loginonline/revisit-b-tree-vs-lsm-tree-upon-arrival-modern-storage-hardware-built)
- [ScyllaDB: LSM-Tree 詳解](https://www.scylladb.com/glossary/log-structured-merge-tree/)

**快取模式**:
- [AWS: Database Caching Strategies Using Redis](https://docs.aws.amazon.com/whitepapers/latest/database-caching-strategies-using-redis/welcome.html)
- [Redis.io: Caching Solutions](https://redis.io/solutions/caching/)
- [Azure: Cache-Aside Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/cache-aside)

**儲存分層**:
- [Elastic: Data Tiers](https://www.elastic.co/docs/manage-data/lifecycle/data-tiers)
- [Opster: Elasticsearch Hot-Warm-Cold Architecture](https://opster.com/guides/elasticsearch/capacity-planning/elasticsearch-hot-warm-cold-frozen-architecture/)
- [Azure: Hot, Warm, Cold Data Path](https://www.futurelearn.com/info/courses/introduction-azure-architect-technologies/0/steps/325799)

**效能與監控**:
- [Azure: Data Performance Strategies](https://learn.microsoft.com/en-us/azure/architecture/framework/data/performance-best-practices)
- [Google SRE Book: Monitoring Distributed Systems](https://sre.google/sre-book/monitoring-distributed-systems/)

### 實作工具與平台

**關聯式資料庫**:
- PostgreSQL: https://www.postgresql.org/
- MySQL: https://www.mysql.com/
- Google Cloud Spanner: https://cloud.google.com/spanner

**NoSQL 資料庫**:
- MongoDB: https://www.mongodb.com/
- Cassandra: https://cassandra.apache.org/
- ScyllaDB: https://www.scylladb.com/

**快取系統**:
- Redis: https://redis.io/
- Memcached: https://memcached.org/

**資料倉儲**:
- Google BigQuery: https://cloud.google.com/bigquery
- Snowflake: https://www.snowflake.com/
- Amazon Redshift: https://aws.amazon.com/redshift/

**時序資料庫**:
- InfluxDB: https://www.influxdata.com/
- TimescaleDB: https://www.timescale.com/

**搜尋引擎**:
- Elasticsearch: https://www.elastic.co/
- OpenSearch: https://opensearch.org/

---

## 11.11 實戰練習題

### 練習 1: Schema 設計

**場景**:設計一個多租戶的 IoT 資料收集系統

**要求**:
1. 每個組織有多個設備
2. 每個設備每秒產生 10 條資料
3. 需要支援即時查詢和歷史分析
4. 預計 1000 個組織,每個 100 台設備

**思考**:
- 主鍵如何設計?
- 需要哪些索引?
- 如何分片?
- 如何處理冷熱資料?

### 練習 2: 快取策略

**場景**:電商平台商品資訊快取

**要求**:
1. 商品庫存即時性要求高
2. 商品詳情可接受秒級延遲
3. 熱門商品查詢頻繁
4. 促銷期間流量暴增

**思考**:
- 使用哪種快取模式?
- TTL 如何設定?
- 如何避免快取雪崩?
- 庫存更新如何處理?

### 練習 3: 儲存分層

**場景**:醫療影像系統

**要求**:
1. 最近 1 個月影像需要快速存取
2. 1 年內影像偶爾查詢
3. 歷史影像需保留 10 年(合規要求)
4. 影像檔案大小 10-50MB

**思考**:
- 如何設計儲存分層?
- 自動化策略如何實作?
- 查詢時如何路由?
- 成本如何優化?

---

## 附錄:架構圖索引

本教材中提到的所有架構圖資源整理:

### 分片架構
1. [Azure Sharding Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/sharding)
2. [Database Sharding Patterns](https://www.geeksforgeeks.org/system-design/database-sharding-a-system-design-concept/)

### 複製架構
1. [Master-Slave Replication](https://www.geeksforgeeks.org/dbms/single-master-and-multi-master-replication-in-dbms/)
2. [Master-Master Replication](https://akashrajpurohit.com/blog/exploring-master-master-replication-in-databases-how-it-works-and-its-benefits/)

### Google Spanner
1. [Spanner Architecture Overview](https://research.google/pubs/spanner-googles-globally-distributed-database-2/)
2. [Life of Reads and Writes](https://cloud.google.com/spanner/docs/whitepapers/life-of-reads-and-writes)

### 索引結構
1. [B-Tree vs LSM-Tree](https://tikv.org/deep-dive/key-value-engine/b-tree-vs-lsm/)
2. [Database Index Structures](https://dev.to/aws-builders/understanding-database-indexes-and-their-data-structures-hashes-ss-tables-lsm-trees-and-b-trees-2dk5)

### 快取模式
1. [AWS Caching Patterns](https://docs.aws.amazon.com/whitepapers/latest/database-caching-strategies-using-redis/caching-patterns.html)
2. [Redis Caching Solutions](https://redis.io/solutions/caching/)

### 儲存分層
1. [Elasticsearch Data Tiers](https://www.elastic.co/docs/manage-data/lifecycle/data-tiers)
2. [Hot-Warm-Cold Architecture](https://opster.com/guides/elasticsearch/capacity-planning/elasticsearch-hot-warm-cold-frozen-architecture/)

---

**文件版本**: v1.0
**最後更新**: 2025-11-16
**作者**: Database Architecture Team
**授權**: CC BY-NC-SA 4.0