Elasticsearch Bài học 18: Các kiểu dữ liệu chuyên sâu - Date, Numeric, Array, Object và Nested Types
Qua bài 17, bạn đã thấy sự khác biệt một trời một vực giữa text và keyword. Tuy nhiên, dữ liệu thực tế của một hệ thống backend phức tạp (như log trạm thu phí AFC hay sản phẩm E-commerce) không chỉ toàn là chuỗi. Chúng ta còn phải xử lý ngày tháng, các con số tính toán, mảng dữ liệu, và đặc biệt là các cấu trúc JSON lồng nhau (Nested JSON).
Việc chọn đúng kiểu dữ liệu cho những trường này không chỉ giúp tránh bug logic mà còn tối ưu hàng chục GB RAM cho hệ thống.
1. Kiểu Date (Thời gian): Linh hoạt nhưng cần khuôn khổ
Định dạng JSON chuẩn không hề có kiểu Date (nó chỉ coi thời gian là một chuỗi String hoặc một số Integer). Do đó, Elasticsearch xử lý Date bằng cách đọc chuỗi bạn gửi lên, parse (phân tích) nó dựa trên một format được định nghĩa trước, và cuối cùng lưu trữ dưới ổ cứng dưới dạng số mili-giây (long - epoch_millis).
Việc chuyển thành số long giúp Elasticsearch thực hiện các truy vấn khoảng thời gian (Range query: "Từ ngày A đến ngày B") cực kỳ nhanh.
Khai báo Mapping chuẩn:
JSON
"created_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
Nhờ dấu || (hoặc), API Backend của bạn có thể linh hoạt đẩy dữ liệu lên bằng nhiều cách: "2026-10-13 10:57:00", hoặc "2026-10-13", hoặc trực tiếp truyền số timestamp 1697200000000.
Kinh nghiệm thực chiến: Luôn lưu thời gian trong Elasticsearch ở múi giờ UTC (+0). Hãy để Frontend hoặc API Gateway tự cộng trừ +7 khi hiển thị cho người dùng ở Việt Nam. Nếu bạn lưu hard-code múi giờ local vào Elasticsearch, việc tính toán Aggregations theo ngày/tháng sẽ trở thành thảm họa.
2. Kiểu Numeric (Số học): Đừng lúc nào cũng dùng Long
Giống như MySQL, Elasticsearch cung cấp đầy đủ các kiểu số:
-
Số nguyên:
byte(8-bit),short(16-bit),integer(32-bit),long(64-bit). -
Số thực:
float,double,half_float.
Nguyên tắc tối ưu: Luôn chọn kiểu dữ liệu nhỏ nhất có thể chứa đủ dải giá trị của bạn. Đừng lười biếng gán long cho một trường như status (chỉ có giá trị 0, 1, 2). Mỗi bit dư thừa khi nhân lên 100 triệu bản ghi sẽ ngốn một lượng RAM và Disk khổng lồ.
Vũ khí bí mật cho E-commerce: scaled_float
Khi lưu giá tiền (Price), dùng float hay double rất dễ dính lỗi sai số thập phân kinh điển của khoa học máy tính (0.1 + 0.2 = 0.30000000000000004).
Elasticsearch cung cấp kiểu scaled_float.
JSON
"price": {
"type": "scaled_float",
"scaling_factor": 100
}
Khi API của bạn gửi lên giá 25.50, Elasticsearch sẽ tự động nhân với 100 và lưu xuống ổ cứng dưới dạng số nguyên 2550. Khi đọc lên, nó tự chia lại cho 100. Bạn vừa có tốc độ và sự chính xác tuyệt đối của số nguyên (long), vừa giữ được logic số thập phân.
3. Kiểu Array (Mảng): Sự thật bất ngờ
Một điều thường gây sốc cho người mới học Elasticsearch: Không có kiểu dữ liệu array trong Mapping.
Elasticsearch hỗ trợ mảng một cách "mặc định" (out-of-the-box). Bất kỳ trường nào (dù là keyword, integer hay date) đều có thể nhận một giá trị đơn lẻ, hoặc một mảng các giá trị.
Ví dụ, bạn khai báo Mapping:
JSON
"tags": { "type": "keyword" }
Khi gọi API thêm dữ liệu, bạn hoàn toàn có thể truyền:
JSON
{ "tags": ["trị mụn", "da dầu", "nhạy cảm"] }
Elasticsearch tự động hiểu đây là một mảng và đánh chỉ mục cả 3 từ khóa này cho cùng một Document.
Điều kiện duy nhất: Mọi phần tử trong mảng phải có cùng kiểu dữ liệu. Bạn không thể truyền [123, "chuỗi", true].
4. Object vs Nested: Cạm bẫy chí mạng của JSON lồng nhau
Đây là kiến thức quan trọng nhất của bài học này. Xử lý sai phần này sẽ dẫn đến kết quả tìm kiếm sai lệch hoàn toàn.
Giả sử Hasaki bán một chiếc áo có nhiều phiên bản (variants) chứa màu sắc và kích cỡ. Bạn gửi một cục JSON có mảng Object:
JSON
{
"product": "Áo thun",
"variants": [
{ "color": "red", "size": "S" },
{ "color": "blue", "size": "XL" }
]
}
Thảm họa của kiểu object (Mặc định)
Mặc định, Elasticsearch không hiểu cấu trúc lồng nhau. Khi nhận JSON trên, nó sẽ "làm phẳng" (flatten) dữ liệu ra thành các mảng song song:
JSON
"variants.color": ["red", "blue"]
"variants.size": ["S", "XL"]
Hậu quả: Mối liên kết giữa "red" và "S" đã bị đứt gãy. Nếu khách hàng tìm kiếm: "Áo thun màu red, size XL", Elasticsearch sẽ trả về True (vì mảng color có chứa "red", và mảng size có chứa "XL"). Nhưng thực tế bạn làm gì có cái áo màu đỏ size XL nào!
Cứu tinh: Kiểu nested
Để giữ được tính toàn vẹn của từng object trong mảng, bạn phải khai báo Mapping rõ ràng là nested:
JSON
"variants": {
"type": "nested",
"properties": {
"color": { "type": "keyword" },
"size": { "type": "keyword" }
}
}
Bản chất hoạt động: Khi bạn dùng nested, Elasticsearch sẽ ngầm biến mỗi Object bên trong mảng thành một Document ẩn (Hidden Document) độc lập, và liên kết chúng với Document cha (Áo thun).
Nhờ vậy, nó giữ được ràng buộc chính xác: "red đi với S", "blue đi với XL". Tuy nhiên, cái giá phải trả là:
-
Khi cập nhật 1 item trong mảng
nested, Elasticsearch phải reindex lại toàn bộ Document cha và tất cả các Document con (Rất tốn I/O). -
Bạn không thể dùng cú pháp truy vấn bình thường, mà phải bọc trong một câu lệnh đặc biệt gọi là
nested query.
Best Practice: Chỉ dùng nested khi thực sự cần duy trì mối quan hệ (như ví dụ variants trên). Nếu bạn chỉ cần lưu một object config để đọc (không cần search chéo các trường con), hãy dùng kiểu object mặc định để tiết kiệm tài nguyên.
Đến đây, bạn đã làm chủ được việc thiết kế Mapping (Schema) cho Elasticsearch. Bạn biết cách chọn keyword hay text, biết xử lý mảng và hóa giải cạm bẫy nested.
Nhưng chúng ta vẫn còn một "chiếc hộp đen" chưa mở: Khi một chuỗi text đi qua Analyzer, chính xác thì nó bị băm nhỏ, cắt xén như thế nào?
All Rights Reserved