Elasticsearch Bài học 16: Mapping 101 - Dynamic Mapping vs Explicit Mapping (Tại sao không nên để Elastic tự đoán kiểu?)
Trong thế giới của cơ sở dữ liệu quan hệ (MySQL/PostgreSQL), trước khi bạn có thể INSERT một bản ghi, bạn phải chạy lệnh CREATE TABLE để định nghĩa rõ ràng cột nào là VARCHAR, cột nào là INT. Bản định nghĩa đó gọi là Schema.
Trong Elasticsearch, khái niệm tương đương với Schema được gọi là Mapping. Nó định nghĩa các trường (fields) trong một Document có kiểu dữ liệu gì, và quan trọng nhất là chúng sẽ được đánh chỉ mục (Index) như thế nào.
Điều thú vị (và cũng là cạm bẫy lớn nhất) của Elasticsearch là bạn không bắt buộc phải định nghĩa Mapping trước khi đẩy dữ liệu vào. Cơ chế này được gọi là Dynamic Mapping.
1. Dynamic Mapping: "Phép thuật" tiện lợi nhưng nguy hiểm
Hãy nhớ lại Bài 5, khi chúng ta gọi API POST products/_doc và đẩy thẳng một cục JSON vào. Elasticsearch không hề báo lỗi "Table not found" hay "Column not found". Nó âm thầm phân tích cục JSON đó và tự động tạo ra một bản Mapping.
Quy tắc đoán kiểu của Elasticsearch:
-
Nhìn thấy
"price": 250000(số nguyên) -> Áp kiểulong. -
Nhìn thấy
"is_active": true(boolean) -> Áp kiểuboolean. -
Nhìn thấy
"created_at": "2026-10-13"(chuỗi chuẩn ISO) -> Áp kiểudate. -
Nhìn thấy
"name": "Sữa rửa mặt"(chuỗi) -> Đây là chỗ gây tai họa nhất! Nó sẽ áp dụng kiểu Multi-field (vừa làtext, vừa làkeyword).
Tại sao Dynamic Mapping là "Kẻ thù của Production"?
Nếu bạn chỉ đang test ở local, Dynamic Mapping rất tuyệt. Nhưng khi đưa lên hệ thống thật (như hệ thống E-commerce của Hasaki), dựa dẫm vào nó sẽ dẫn đến 3 thảm họa:
Thảm họa 1: Lãng phí 50% dung lượng lưu trữ ổ cứng (Disk space) Khi Elasticsearch thấy một chuỗi string, nó không biết bạn định dùng chuỗi này để tìm kiếm full-text (như tìm tên sản phẩm) hay dùng để lọc/thống kê chính xác (như lọc theo mã SKU). Vì vậy, nó "ôm đồm" bằng cách tạo ra cả 2 loại Index:
JSON
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
Việc lưu thừa thãi kiểu keyword cho trường mô tả sản phẩm (dài hàng nghìn ký tự) sẽ làm dung lượng Index của bạn phình to gấp đôi, tốn RAM và làm chậm toàn bộ Cluster.
Thảm họa 2: Xung đột kiểu dữ liệu (Type Coercion) Giả sử bản ghi đầu tiên đẩy vào là "discount": 15 (Elasticsearch set mapping là long). Ngày hôm sau, hệ thống gửi lên bản ghi thứ hai do lỗi code frontend: "discount": "15%" (chuỗi). Elasticsearch sẽ ném ra lỗi mapper_parsing_exception và từ chối lưu Document thứ hai. Từ đó dữ liệu của bạn bị mất trắng mà không hay biết.
Thảm họa 3: Không thể đổi kiểu dữ liệu sau khi đã tạo Giống như số lượng Primary Shard (Bài 7), bạn không thể thay đổi kiểu dữ liệu của một trường đã tồn tại. Nếu Elastic lỡ đoán sai, bạn chỉ có cách tạo Index mới và chép lại dữ liệu (Reindex).
2. Explicit Mapping: Cách làm việc của Backend Engineer chuyên nghiệp
Để tránh mọi rủi ro trên, chuẩn mực trên Production là phải tạo Explicit Mapping (Định nghĩa chủ động) ngay từ lúc tạo Index (trước khi đưa bất kỳ dữ liệu nào vào).
Hãy mở Kibana Dev Tools và tạo một Index chuẩn mực cho sản phẩm:
JSON
PUT products
{
"mappings": {
"properties": {
"product_id": {
"type": "keyword"
},
"name": {
"type": "text"
},
"price": {
"type": "integer"
},
"created_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
Lợi ích:
-
Tối ưu dung lượng: Mã
product_idchỉ cầnkeyword(để gộp nhóm/lọc), không cầntext.namechỉ cầntext(để search), không cầnkeyword. -
Ép chuẩn format thời gian (
formatcho trườngdate). -
RAM và Disk được giải phóng, tốc độ truy vấn tăng vọt.
3. Best Practice: Sử dụng tham số dynamic: "strict"
Ngay cả khi bạn đã khai báo Explicit Mapping, nếu API Backend gửi lên một trường mới (ví dụ: "new_field": "hello"), mặc định Elasticsearch vẫn sẽ kích hoạt Dynamic Mapping để âm thầm thêm new_field vào schema. Lâu dần, Index của bạn sẽ chứa đầy "rác" do các trường không mong muốn bị đẩy vào.
Để tạo ra sự kỷ luật thép giống hệt RDBMS, hãy đặt tham số "dynamic": "strict" ở cấp độ cao nhất của Mapping:
JSON
PUT products
{
"mappings": {
"dynamic": "strict",
"properties": {
"product_id": { "type": "keyword" },
"name": { "type": "text" }
}
}
}
Với cấu hình "strict" này:
-
Nếu code Golang/PHP của bạn cố tình
POSTmột Document chứa trường"brand": "Cetaphil"(chưa từng được khai báo trong schema), Elasticsearch sẽ từ chối toàn bộ Document và trả về lỗiStrictDynamicMappingException. -
Điều này ép các kỹ sư backend phải cẩn thận với payload của mình và phải thực hiện quy trình "migration" đàng hoàng mỗi khi muốn thêm trường dữ liệu mới (bằng cách gọi API
PUT products/_mapping).
Tóm tắt: Không bao giờ đưa lên Production một Index dựa vào Dynamic Mapping. Hãy luôn tự định nghĩa kiểu dữ liệu và sử dụng
strictmode để bảo vệ cấu trúc hệ thống.
Ở bài này, chúng ta đã nhắc nhiều đến text và keyword - hai kiểu dữ liệu phổ biến nhất nhưng cũng gây nhầm lẫn nhiều nhất trong Elasticsearch. Sự khác biệt thực sự của chúng là gì và nó ảnh hưởng tới kết quả search ra sao?
All rights reserved