搜尋系統 (Search System)
為什麼搜尋是獨立的設計問題
用 LIKE '%running shoes%' 做搜尋會出兩個大問題:
- 無法用索引 → full table scan,幾百萬筆要跑幾十秒
- 只做字串比對 → 「跑步鞋」不匹配「慢跑鞋」、「running」不匹配「run」
DB 查詢做精確比對(user_id = 123);搜尋做相關性排序(找出所有和「跑步鞋」相關的,按相關度排)。需要完全不同的資料結構和查詢引擎。
「搜尋我會用 Elasticsearch 建獨立的搜尋索引,資料透過 CDC + Kafka 從主資料庫非同步同步。」說出這句就展示你理解搜尋是獨立設計問題。
整體架構:兩條管線
┌─ Indexing Pipeline ─┐ ┌─ Query Pipeline ─┐
主資料庫 ──CDC──► Kafka ──► Indexer ──► Elasticsearch ◄── 查詢 ── 用戶
(寫入/一致性) (次要索引/全文搜尋)
搜尋索引不是 primary database,而是建在主資料庫上的次要索引(secondary index)。
倒排索引(搜尋引擎核心)
傳統 DB 是「文件 → 詞彙」;倒排索引反過來「詞彙 → 文件」。搜尋「跑步鞋」時找「跑步」「鞋」各自的文件清單取交集 → 速度和全表掃描不在一個量級。詳見 04-Database-Indexing/06-Inverted-Index。
文字分析(建索引前的處理):
- Tokenization(斷詞):
"Nike Running Shoes"→["nike", "running", "shoes"] - Lowercasing:統一小寫
- Stop word removal:移除「的」「了」「the」
- Stemming:
running/runs/ran→run
確保查詢詞彙和索引詞彙能匹配。
Indexing Pipeline:資料如何進入索引(最易被忽略)
| 方案 | 機制 | 評價 |
|---|---|---|
| 雙寫(Dual Write) | app 同時寫 DB 和 ES | ❌ 兩寫非原子,DB 成功 ES 失敗就不一致;複雜度推到應用層 |
| CDC(Change Data Capture) | 監聽 DB 的 WAL → 每個變更轉事件 → 非同步同步 | ✅ 最推薦,大多數生產系統走這條 |
CDC 優點:app 只寫主 DB(搜尋同步完全解耦)、Kafka 緩衝(ES 暫不可用不丟資料)、Indexer 可做轉換。缺點:延遲幾秒~幾十秒。詳見 09-Messaging-Coord/01-Data-Pipeline。
改 mapping(新增欄位、換分詞器)需重建整個索引。用 alias 切換:建新索引 → 灌資料 → alias 從舊指向新,搜尋零停機。
相關性排序(Relevance Ranking)
BM25(Elasticsearch 預設)
| 因子 | 含義 |
|---|---|
| TF(Term Frequency) | 詞在文件出現越多分數越高,但有上限(非線性) |
| IDF(Inverse Document Frequency) | 詞越罕見分數越高(「鞋」IDF 低、「Gore-Tex」IDF 高、更有辨別力) |
| 欄位長度 | 同樣出現一次,短標題權重 > 長描述 |
Boosting(欄位權重)
"fields": ["name^3", "category^2", "description^1"] → 名稱權重是描述的 3 倍。
業務邏輯排序
純文字相關性往往不夠。真實排序 = 相關性 + 業務指標:
最終分數 = 相關性 × 0.6 + 銷量 × 0.2 + 評分 × 0.1 + 新品加成 × 0.1
常見功能設計
| 功能 | 技術 | 要點 |
|---|---|---|
| Autocomplete | Edge N-gram | 「running」拆成 r, ru, run, runn... 每個前綴建索引;需 100ms 內回應 → 加 Redis 快取 |
| Faceted Search | Aggregation API | 側欄品牌/價格/顏色過濾,一次查詢同時回結果 + 各維度計數(Nike (143)) |
| Pagination | Search After | 無限下拉用 Search After(穩定);有頁碼用 From/Size(限 offset 10,000 防深分頁) |
擴展
資料路由依賴 shard 數量,改只能 Reindex。官方建議每 shard 10~50GB。估算:預估資料量 ÷ 目標 shard 大小(如 500GB ÷ 25GB = 20 個 primary shard)。寧可設多一點。
查詢快取:熱門搜尋詞(「Nike 跑步鞋」)加 Redis 快取,key = 查詢詞 + 過濾條件,TTL 設短(幾分鐘)讓新/下架商品及時反映。
常見 Deep Dive
CDC 有幾秒~幾十秒延遲,大多數場景可接受(新商品幾秒後可搜到合理)。不能接受 → 寫主 DB 同時用 async 任務直接觸發 ES 更新(< 1 秒,但要處理重試和冪等)。真正需即時一致的(庫存)不放搜尋索引,而是從 ES 拿候選後再去主 DB 做最終驗證。
每 shard 控制 10~50GB(太大查詢慢、太小浪費)。估算:預估一年資料量 ÷ 目標 shard 大小。寧可多設,因為之後無法改,少了只能 Reindex。
用滾動時間窗口銷量(讓歷史刷量失效)、異常行為偵測(短時間大量購買、評論雷同)、多維度指標。完全消除難,但讓操控成本足夠高。
常見系統範例
| 系統 | 設計重點 |
|---|---|
| Twitter 搜尋 | MySQL → CDC → Kafka → ES;對內容/用戶名/hashtag 多欄位搜尋,用時間 + 熱度 boosting |
| 電商搜尋 | BM25 基礎 × 銷量/評分;Aggregation 做過濾;Edge n-gram + Redis 做 autocomplete |
| Airbnb 搜尋 | geo_distance 地理過濾;日期可用性用 nested/join 結構 |
Related Notes
- 04-Database-Indexing/06-Inverted-Index — Term Dictionary、Posting List、phrase query
- 09-Messaging-Coord/01-Data-Pipeline — CDC、Kafka、搜尋索引更新管線
- 08-Search-and-AI/02-RAG — semantic search 與 vector database(搜尋的 AI 延伸)
- 10-Design-Patterns/01-Scaling-Reads — 查詢快取、讀取擴展
- 08-Search-and-AI/Practice-Search-and-AI