Elasticsearch 資料塑型 — Mapping, Template, Component Template
蓋房子之前要先畫藍圖。這個道理大家都懂,但很多人用 Elasticsearch 的時候選擇跳過這一步——直接丟資料進去,讓 Dynamic Mapping 自己猜型別。
一開始很順。直到某天你寫入一筆小數,系統跟你說:「不行,這個欄位是整數。」
第一筆資料進來的那一刻,型別就鎖死了。之後想改?只能砍掉重建。沒有 ALTER TABLE。
Dynamic Mapping:方便的糖衣,危險的炸彈
Elasticsearch 的 Dynamic Mapping 會在你第一次寫入資料時自動推斷欄位型別。你丟 "hello",它猜 text + keyword;你丟 123,它猜 long;你丟 "2026-04-29",它猜 date。
聽起來很聰明。問題是,它只看第一筆。
五個一定會爆的地雷
第一筆決定命運。你的第一筆訂單 price: 100 → 欄位被設為 long。第二筆 price: 99.5 → 直接報 mapper_parsing_exception。long 欄位塞不下 float。事後想改?砍 Index 重建。
字串自動雙倍空間。Dynamic Mapping 對每個字串欄位同時建 text(分詞搜尋)+ keyword 子欄位(精確比對)。你明明只需要精確比對的 status 欄位,它也幫你建了一份分詞索引。空間直接翻倍。
Mapping Explosion。使用者上傳結構不固定的 JSON,每筆都多一個新欄位名。Dynamic Mapping 照單全收,欄位數暴增到上限(預設 1,000),然後 IllegalArgumentException。某團隊讓使用者自由上傳,最後搞出 50,000 個欄位,Cluster State 膨脹到 2 GB,Master Node 直接 OOM。
日期格式鎖定。第一筆用 ISO 格式 "2026-04-29T10:00:00Z",日期格式就鎖定了。第二筆用 epoch_millis 1745836800000,格式不匹配,寫入失敗。
object 跟 nested 搞混。Dynamic Mapping 把巢狀物件推斷為 object——扁平化儲存。items: [{name: "小籠包", qty: 2}, {name: "炒飯", qty: 1}] 變成 items.name: ["小籠包", "炒飯"] + items.qty: [2, 1]。關聯性消失了。搜「name=小籠包 AND qty=1」會誤命中。
四種控制模式
Dynamic Mapping 不是只有開跟關。dynamic 參數有四個值:
true(預設):自動偵測建 Mapping。方便但危險。
runtime:新欄位存為 runtime field,不佔索引空間。查的時候動態計算,適合探索階段。
false:新欄位存入 _source 但不索引。資料在,但搜不到。
strict:未定義的欄位寫入直接報錯。正式環境永遠用這個。
1 | PUT /strict_index |
手動 Mapping:每個欄位想清楚
跟 Dynamic Mapping 相反的做法是——自己定義每個欄位的型別。麻煩一點,但不會被第一筆資料綁架。
型別選擇速查
text:全文搜尋用。會過 Analyzer 切成 token 存入倒排索引。不能排序、不能聚合(token 沒有原始值語義)。需要排序就搭配 .keyword 子欄位。
keyword:精確比對用。完全不分詞,原封不動存入。status、category、email、ID、tags 這類欄位用這個。ignore_above: 256 是好習慣,避免超長字串浪費空間。
數值型別:integer、long、float、double、half_float、scaled_float。金額用 scaled_float(例如 scaling_factor: 100,用整數存「分」),不要用 float——浮點精度問題是永恆的痛。
date:支援多種格式,用 || 分隔。ES 內部統一存為 epoch_millis(long),不管你用什麼格式寫入。這就是為什麼 date 可以做 range 查詢和排序。
1 | "order_time": { |
object vs nested:這是最容易搞混的。object 把巢狀結構扁平化,效能好但關聯性消失。nested 為每個巢狀物件建獨立的 Lucene document,保留關聯性但耗資源。如果你需要「商品 A 的數量是 2」這種跨欄位關聯查詢,必須用 nested。
geo_point:地理座標。注意陣列格式是 [lon, lat] 不是 [lat, lon]——每個踩過這坑的人都會記得。
省空間的進階參數
不需搜尋的欄位設 index: false(存 _source 但不索引),不需排序聚合的設 doc_values: false,不需計分的設 norms: false。每個參數都是空間和效能的取捨。
Index Template:一次定義,自動套用
每次建 Index 都要手動寫一大堆 Mapping?太累了。
Index Template 讓你定義一個模板,之後所有名字符合 pattern 的 Index 自動套用。
1 | PUT _index_template/logs_template |
之後建任何 logs- 開頭的 Index,Mapping 和 Settings 自動到位:
1 | PUT /logs-nginx-2026.05.04 |
兩件事要注意。第一,一定要設 priority。多個 Template 的 pattern 都匹配、priority 又相同,ES 會報錯。第二,index_patterns 記得加萬用字元。寫成 ["logs"] 而不是 ["logs-*"],只有名字剛好叫 logs 的 Index 才會套用。
Component Template:積木式的模組化設計
如果你有 10 種 Index 都需要「相同的基底欄位 + 不同的專用欄位」,Index Template 會讓你複製貼上到崩潰。
Component Template 把設定拆成積木——通用 Settings 一塊、通用 Mappings 一塊、專用 Mappings 一塊——然後在 Index Template 裡用 composed_of 組裝。
1 | [base_settings] [base_mappings] [logs_mappings] |
1 | // 積木一:通用 Settings |
組裝:
1 | PUT _index_template/logs_template |
另一套 Orders Index 只要換第三塊積木,通用的兩塊共用。DRY 原則在 Elasticsearch 也適用。
想確認某個 Index 會套用哪些設定?用模擬 API:
1 | POST _index_template/_simulate_index/logs-test-2026 |
改了 Template 不影響舊 Index
這是最容易忘記的一件事:Template 只在建立 Index 時套用。
你改了 Component Template 加了新欄位,已經存在的 Index 不會自動更新。要讓舊 Index 跟上,得手動 PUT _mapping 補欄位,或直接 Reindex。
Template 是藍圖,不是遙控器。藍圖改了,已經蓋好的房子不會自己長出新的房間。
八條實戰準則
**1. 正式環境永遠 dynamic: strict**。開發環境可以 true,但 merge 前必須補齊 Explicit Mapping。
2. 字串欄位先想清楚。需要全文搜尋用 text,需要精確比對用 keyword。不確定就先 keyword——省空間,之後要加 text 還可以加。
**3. 金額用 scaled_float**。scaling_factor: 100,用整數存「分」。float 的精度問題不值得冒險。
4. 陣列物件要跨欄位關聯就用 nested。不需要就用 object。nested 每個物件底層是一個 Lucene doc,一筆訂單 100 個品項 = 101 個 doc,效能差距很大。
5. 日期支援多格式。format 用 || 分隔,前端後端格式不一致的時候不會炸。
6. 用 Component Template 做模組化。通用欄位(timestamp、created_at)抽成共用組件。
**7. 不需搜尋的欄位設 index: false**。備註、電話這種存著但不需要搜的欄位,不索引就不佔空間。
8. Template 一定要設 priority。不同 priority 的 Template 匹配同一個 Index 時,高的贏。沒設 priority 會出不可預測的行為。
Mapping 的本質是決策——在資料寫入之前,你就得想清楚每個欄位「是什麼」跟「要怎麼用」。Dynamic Mapping 把這個決策延後了,讓第一筆資料替你決定。問題是,第一筆資料不知道你的業務邏輯。
Template 和 Component Template 則是把決策系統化——定義一次,之後所有符合 pattern 的 Index 自動繼承。改一塊積木,所有新 Index 同步更新。
先決策、再寫入。先藍圖、再蓋房。資料工程跟蓋房子一樣,最貴的不是水泥,是改設計。
參考來源:Elasticsearch 官方文件 — Mapping
參考來源:Elasticsearch 官方文件 — Index Templates









