Elasticsearch Bài học 12: Delete by Query & Update by Query - Thao tác dữ liệu hàng loạt bằng điều kiện
Trong các bài trước, chúng ta đã thao tác với dữ liệu (CRUD) thông qua việc truyền trực tiếp một _id cụ thể. Nhưng trên thực tế vận hành (ví dụ: gỡ bỏ toàn bộ sản phẩm của một thương hiệu ngừng kinh doanh, hoặc giảm giá 20% cho toàn bộ nhóm hàng "Chăm sóc da"), bạn không thể lặp qua hàng triệu Document để lấy ID rồi gọi API xóa từng cái một.
Đó là lúc hai vũ khí hạng nặng xuất hiện: _delete_by_query và _update_by_query.
1. Delete by Query: Xóa hàng loạt theo điều kiện
Lệnh này cho phép bạn truyền vào một câu truy vấn (Query DSL) và Elasticsearch sẽ tìm tất cả các Document thỏa mãn điều kiện đó để xóa đi.
Cú pháp:
JSON
POST products/_delete_by_query
{
"query": {
"match": {
"brand": "Thương hiệu A"
}
}
}
Bản chất hoạt động (Rất quan trọng):
Khi bạn chạy lệnh này, Elasticsearch không xóa bùm một phát ngay lập tức như lệnh DELETE FROM table WHERE ... của MySQL. Thực chất, nó chạy một tiến trình ngầm gồm các bước:
-
Lấy Snapshot (Ảnh chụp): Elasticsearch "chụp ảnh" trạng thái của Index ngay tại thời điểm bạn nhấn Enter.
-
Scroll Search (Tìm kiếm cuốn): Nó dùng cơ chế Scroll (lấy từng lô 1000 kết quả một) để lôi dần các Document thỏa mãn điều kiện lên bộ nhớ.
-
Bulk Delete: Với mỗi lô 1000 ID vừa tìm được, nó chạy lệnh xóa nội bộ (đánh dấu vào file
.delvà tăng_seq_nonhư Bài 11).
2. Update by Query: Sửa hàng loạt với Script
Tương tự như xóa, bạn có thể tìm ra một nhóm Document và áp dụng một "Script" (mã kịch bản) để tính toán lại giá trị mới. Ngôn ngữ script mặc định của Elasticsearch là Painless (cú pháp khá giống Java/Groovy).
Ví dụ: Giảm giá 20% cho toàn bộ sản phẩm thuộc danh mục "Sữa rửa mặt".
JSON
POST products/_update_by_query
{
"query": {
"match": {
"category": "Sữa rửa mặt"
}
},
"script": {
"source": "ctx._source.price = ctx._source.price * 0.8",
"lang": "painless"
}
}
(Trong script, ctx._source là biến đại diện cho cục JSON của Document hiện tại đang được duyệt qua).
3. Vấn đề chí mạng: Version Conflict (Xung đột phiên bản)
Vì quá trình by_query mất thời gian (có thể mất vài phút cho triệu dòng dữ liệu), điều gì xảy ra nếu trong lúc tiến trình đang chạy, một khách hàng lại bấm mua và thay đổi trạng thái của 1 trong các Document đó?
Kịch bản lỗi:
-
Khởi chạy
_update_by_query(chụp Snapshot). Bắt đầu lướt qua lô 1, lô 2... -
Ở lô thứ 5, tiến trình lấy Document ID
100ra, chuẩn bị giảm giá. Version hiện tại trong Snapshot làv=5. -
Bất ngờ, một API khác của khách hàng sửa ID
100thànhv=6. -
Tiến trình update đè giá mới xuống ID
100với yêu cầuif_seq_no=5. -
Elasticsearch báo lỗi: Version Conflict (409)!
Mặc định, khi gặp lỗi này, toàn bộ tiến trình _update_by_query sẽ lập tức bị hủy bỏ (Abort). Những Document đã cập nhật trước đó (lô 1 đến lô 4) sẽ giữ nguyên (không rollback), những cái chưa chạy sẽ không được chạy. Dữ liệu của bạn sẽ rơi vào trạng thái "nửa nạc nửa mỡ".
Cách xử lý:
Hãy thêm tham số conflicts=proceed vào URL.
JSON
POST products/_update_by_query?conflicts=proceed
{
...
}
Tham số này bảo Elasticsearch rằng: "Nếu gặp Document bị thay đổi bởi người khác, hãy đếm nó vào danh sách thất bại, bỏ qua nó và tiếp tục chạy cho các Document còn lại". Sau khi xong, API sẽ trả về thông số "version_conflicts": 1 để bạn biết có bao nhiêu cái bị trượt.
4. Kiểm soát sát thủ thầm lặng: I/O & CPU Throttle
Hai lệnh by_query này cực kỳ ngốn tài nguyên. Nếu bạn chạy nó trên tập 10 triệu bản ghi, Cluster sẽ gầm rú, Disk I/O chạy 100% và ảnh hưởng nghiêm trọng đến các truy vấn tìm kiếm của khách hàng (Search latency sẽ tăng vọt).
Giải pháp: Giới hạn tốc độ (Throttling)
Elasticsearch cho phép bạn "bóp" tốc độ chạy của tiến trình này lại bằng tham số requests_per_second. Ví dụ:
JSON
POST products/_update_by_query?requests_per_second=500
Lệnh này ép Elasticsearch chỉ được phép xử lý tối đa 500 Document mỗi giây. Tiến trình sẽ chạy chậm hơn, mất nhiều thời gian hơn để hoàn thành, nhưng đổi lại Cluster sẽ "dễ thở" và không làm sập các API khác trên Production.
Trong bài tới, chúng ta sẽ học một tuyệt chiêu khác để tối ưu hiệu năng ghi số lượng lớn, không phải từ phía Elasticsearch mà từ phía API Backend đẩy lên: Bulk API.
All Rights Reserved