Elasticsearch 資料的進與出 — Data In/Out 與 REST API 大補帖
你知道怎麼用 Google 搜尋,但你有沒有想過「搜尋引擎的資料是怎麼進去的」?
Google 有爬蟲。Elasticsearch 沒有——你得自己把資料餵進去。而且不只是餵進去就好,還得告訴它每筆資料長什麼樣、哪些欄位要能搜、哪些只要精確比對、哪些需要排序。
這就像開餐廳。菜單不是隨便一張紙——你得決定每道菜的名稱格式(文字還是編號)、價格精度(整數還是小數)、分類方式(依口味還是依烹飪法)。這些決定一旦做了就很難改,改了就得整本菜單重印。
Elasticsearch 的 Mapping 就是這本菜單的格式定義。今天從開菜單開始,教到點菜、上菜、換菜、退菜的全套流程。
開菜單:Index 建立與 Mapping
建立一個 Index 等於在 Elasticsearch 裡開一本新菜單。最簡單的方式一行就搞定:
1 | PUT /menu |
這樣 ES 會用預設值——1 個 primary shard、1 個 replica。正式環境通常要自己設:
1 | PUT /menu |
用 PUT 不是 POST。PUT 是冪等的——打一百次結果一樣。Index 已存在的話會回 400 resource_already_exists_exception。
Mapping:每道菜的營養標示
Mapping 定義了每個欄位的資料型別和索引方式。不定義的話,ES 會在第一筆資料寫入時自動推斷——字串同時建 text + keyword 子欄位、數字推成 long、長得像日期的推成 date。方便歸方便,但推錯了就回不去。Mapping 一旦建立,欄位型別不能改。
正式環境一律手動定義:
1 | PUT /menu |
這裡最容易搞混的是 text 跟 keyword 的差別。text 會經過 Analyzer 分詞,適合全文搜尋——「鼎泰豐招牌小籠包」會被拆成「鼎泰豐」「招牌」「小籠包」幾個 token。keyword 不分詞,整串存、整串比對,適合精確匹配、排序、聚合。ES 預設對字串兩個都建:field 是 text,field.keyword 是 keyword。
Mapping 只能加欄位,不能改已有的。如果 price 不小心設成 text 想改成 integer,唯一的辦法是建新 Index → Reindex 搬資料 → 用 Alias 切換。文章最後會教這個。
點菜上菜換菜退菜:CRUD 四大操作
SQL 有 INSERT / SELECT / UPDATE / DELETE,Elasticsearch 用 RESTful API 對應。
新增(Create)
1 | POST /menu/_doc |
POST 自動產生 _id。想自己指定就用 PUT:
1 | PUT /menu/_doc/xiaolongbao |
踩坑提醒:PUT 指定 ID 時,如果 ID 已存在會覆蓋整份文件,不是更新。要嚴格只在不存在時新增,用 _create endpoint——ID 已存在會回 409。
讀取(Read)
1 | GET /menu/_doc/xiaolongbao // 單筆 |
更新(Update)
ES 沒有真正的 in-place update。底層永遠是「標記刪除舊版 → 寫入新版」。但 _update API 省了你手動 GET + 修改 + 重新 index 的麻煩:
1 | POST /menu/_update/xiaolongbao |
需要做計算的話用 Painless script:
1 | POST /menu/_update/xiaolongbao |
還有一個很實用的 upsert——存在就更新,不存在就新增:
1 | POST /menu/_update/hotpot |
刪除(Delete)
1 | DELETE /menu/_doc/xiaolongbao // 單筆 |
delete_by_query 底層是 scroll + bulk delete,大量刪除可能很慢。而且刪除只是標記——磁碟空間要等 Segment merge 才真正釋放。
效能大殺器:_bulk 批次處理
真實世界不會一筆一筆塞資料。100 萬筆商品 × 單筆 API = 100 萬次 HTTP 請求。_bulk 讓你一次請求處理上千筆,效能提升 10 到 100 倍不誇張。
語法用 NDJSON(Newline Delimited JSON)——每行一個 JSON,不是 JSON Array:
1 | POST _bulk |
四種操作混著用:index(新增或覆蓋)、create(嚴格新增)、update(部分更新)、delete(刪除,不需要下一行 body)。
_bulk 五條鐵律
最後一行必須以換行結尾。 少了這個換行,最後一筆會被靜默忽略。Kibana Dev Tools 不會有這問題,但 curl 要注意。
單次建議 5-15 MB。 太小浪費 HTTP overhead,太大壓垮 coordinating node。100 MB 以上容易 OOM。
回應要逐筆檢查。 _bulk 不是全有或全無——其中一筆失敗,其他照跑。你的程式碼必須解析 response 的 errors 欄位。
大量匯入前關掉 refresh。 設 refresh_interval: -1 + number_of_replicas: 0,匯完再改回來,效能提升 30-50%。
匯完記得 forcemerge。 大量寫入會產生一堆小 Segment,手動跑一次 POST /menu/_forcemerge?max_num_segments=5 整理一下。
Search API 入門:Query DSL 基本功
CRUD 是基本功,搜尋才是 Elasticsearch 的核心。SQL 用 WHERE,ES 用 Query DSL——基於 JSON 的查詢語法。
match:全文搜尋
1 | GET /menu/_search |
預設是 OR——包含「招牌」或「多汁」都命中。要 AND 的話加 "operator": "and"。
term:精確比對
1 | GET /menu/_search |
term 只能用在 keyword 欄位。拿它去查 text 欄位會踩坑——「小籠包」被分詞成 [“小”, “籠”, “包”],你 term 搜「小籠包」反而搜不到,因為原始值已經不存在了。
bool:組合查詢
1 | GET /menu/_search |
四個子句各有分工:must(AND,算分)、should(OR,影響排名)、must_not(NOT,不算分)、filter(AND,不算分但可快取)。
效能黃金法則:不需要相關性排名的條件,一律放 filter。不算分 = 更快 + 結果可被 ES 快取。
分頁
1 | GET /menu/_search |
from + size 有上限,預設 10,000。深分頁要改用 search_after 或 scroll API——後面的模組會教。
Alias:零停機的秘密武器
Mapping 設錯了怎麼辦?如果程式碼寫死 Index 名稱,你就得改程式碼 + 重新部署。但如果從第一天就用 Alias(別名),換 Index 只要一個 API,程式碼零改動。
Alias 就是 Index 的暱稱。程式碼永遠只跟 menu 溝通,背後指向 menu_v1 還是 menu_v2,你隨時可以切。
1 | POST /_aliases |
原子切換
一個請求同時移除舊的、加上新的,中間零停機:
1 | POST /_aliases |
actions 陣列裡的操作是原子的——全部成功或全部失敗,不會出現「舊的移除了但新的還沒加」的空窗期。
帶 Filter 的 Alias
同一個 Index 可以用 filter 建立不同視角:
1 | POST /_aliases |
搜 menu_spicy 自動只看辣的菜。不用改查詢邏輯,Index 層面就過濾好了。
完整 Reindex + Alias 切換 SOP
這是業界標準的 Mapping 修改流程:
- 建新 Index(正確的 Mapping)
_reindex從舊 Index 搬資料- 原子切換 Alias
- 確認無誤後刪舊 Index
1 | PUT /menu_v2 |
整個過程對使用者完全無感。這就是為什麼 Alias 應該從第一天就用——不是因為你現在需要切換,是因為你不知道什麼時候會需要。
回到最初那個比喻。開餐廳,菜單格式(Mapping)決定了後面所有操作的效率和彈性。格式定錯了,後面每一步都在補救。格式定對了,CRUD 順暢、_bulk 高效、搜尋精準。
而 Alias 是你的保險——就算菜單真的定錯了,你還是能零停機重印一本新的。唯一的前提是你從一開始就用暱稱點菜,而不是直接喊菜單的印刷編號。
這個系列的下一篇會鑽進 Elasticsearch 的身體裡,看寫入和搜尋在底層到底是怎麼運作的——Translog、Segment、Merge,那些決定效能天花板的機制。
本文改寫自 Elasticsearch 課程筆記,所有 API 範例可直接在 Kibana Dev Tools 中執行。






