# 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