Elasticsearch Bị Spam Index: Nguyên Nhân và Cách Xử Lý
Hướng dẫn chi tiết cách phát hiện, debug và xử lý tình trạng Elasticsearch bị spam bởi logging middleware ghi toàn bộ request/response, gây ra cảnh báo DatasourceNoData trên Grafana.
Table of Contents
Tóm tắt vấn đề
Grafana bắn cảnh báo DatasourceNoData với alert rule tên Spam Alert, đồng nghĩa Grafana không thể query được dữ liệu từ Elasticsearch/Kibana. Nguyên nhân không phải Elasticsearch bị chết, mà là một hoặc nhiều index bị spam hàng trăm nghìn document mỗi ngày khiến cluster bị quá tải khi query.
Nguyên nhân gốc rễ
1. Logging middleware ghi toàn bộ request/response
Service TCHSYSTEM có một middleware logging ghi lại toàn bộ HTTP request và response vào Elasticsearch. Mỗi document chứa:
- Full request headers (bao gồm JWT token, cookie, v.v.)
- Full response body (toàn bộ JSON trả về, có thể 5–10KB mỗi doc)
Ví dụ một document thực tế nặng hàng KB chỉ vì field response chứa toàn bộ object đơn hàng:
{
"application": "TCHSYSTEM",
"method": "GET",
"path": "api/get/699912801d3e2ef0dbc5f4a1",
"response": "{ toàn bộ object đơn hàng bao gồm coupon, shipper, orderlines... }"
}
2. Python bot polling tần suất cao
Một Python bot (user-agent: python-requests/2.25.1) gọi endpoint api/store/current-process liên tục. Chỉ trong một ngày, endpoint này tạo ra 29,177 document. Endpoint này chỉ trả về {"status": true} nhưng vẫn bị log đầy đủ.
3. Kết quả
Các index tch_system-* phình lên đến 8–13GB mỗi ngày thay vì vài trăm KB như bình thường:
tch_system-2026-02-09 → 13.6 GB (814k documents)
tch_system-2026-02-08 → 8.6 GB (482k documents)
tch_system-2026-02-06 → 9.6 GB (593k documents)
Elasticsearch phải scan lượng data khổng lồ này mỗi khi Grafana query, dẫn đến timeout → Grafana báo DatasourceNoData.
Cách debug từng bước
Lần sau nếu gặp tình huống tương tự, làm theo quy trình sau:
Bước 1: Kiểm tra sức khỏe cluster
curl http://localhost:9200/_cluster/health
Xem field status:
green→ hoàn toàn bình thườngyellow→ có shard chưa được assign (thường do single-node), không nguy hiểmred→ có shard bị mất, cần xử lý ngay
Nếu status là yellow hoặc green mà Grafana vẫn báo lỗi → vấn đề nằm ở dữ liệu, không phải cluster.
Bước 2: Tìm index bị phình
curl "http://localhost:9200/_cat/indices?v&s=store.size:desc"
Sort theo store.size để tìm index lớn bất thường. So sánh với các index cùng loại ở ngày khác — nếu một ngày nào đó đột nhiên lớn hơn bình thường nhiều lần thì đó là thủ phạm.
Dấu hiệu nhận biết:
- Index cùng loại nhưng một ngày lớn gấp 10–100x ngày khác
- Số
docs.countquá cao so với bình thường
Bước 3: Xem nội dung bên trong index
curl -s "http://localhost:9200/{index_name}/_search?pretty" \
-H "Content-Type: application/json" \
-d '{"size": 2}'
Nếu bị lỗi No mapping found for [@timestamp], tức là index không dùng @timestamp chuẩn. Query không cần sort:
curl -s "http://localhost:9200/{index_name}/_search?pretty" \
-H "Content-Type: application/json" \
-d '{"size": 2}'
Nhìn vào _source để hiểu document chứa gì, từ service nào, field nào nặng.
Bước 4: Tìm endpoint nào spam nhiều nhất
curl -s "http://localhost:9200/{index_name}/_search?pretty" \
-H "Content-Type: application/json" \
-d '{
"size": 0,
"aggs": {
"top_paths": {
"terms": {
"field": "path.keyword",
"size": 10
}
}
}
}'
Output sẽ cho thấy endpoint nào tạo nhiều document nhất. Đây là điểm cần can thiệp trong code.
Bước 5: Kiểm tra mapping của index
curl -s "http://localhost:9200/{index_name}/_mapping?pretty"
Xác nhận field nào đang được index, field nào có thể bỏ qua hoặc truncate.
Cách xử lý
Xử lý tức thời: Xóa các index bị spam
Lưu ý: Thao tác này xóa dữ liệu vĩnh viễn. Chỉ làm nếu chấp nhận mất log lịch sử của các ngày đó.
curl -X DELETE "http://localhost:9200/tch_system-2026-02-04,\
tch_system-2026-02-05,\
tch_system-2026-02-06,\
tch_system-2026-02-07,\
tch_system-2026-02-08,\
tch_system-2026-02-09,\
tch_system-2026-02-10,\
tch_system-2026-02-11,\
tch_system-2026-02-12,\
tch_system-2026-02-13,\
tch_system-2026-02-14,\
tch_system-2026-02-15,\
tch_system-2026-02-20,\
tch_system-2026-02-21"
Sau khi xóa, Elasticsearch sẽ nhẹ hơn đáng kể và Grafana có thể query trở lại bình thường.
Nếu muốn chỉ xóa document spam mà giữ lại document hợp lệ, dùng delete by query:
curl -X POST "http://localhost:9200/tch_system-*/_delete_by_query" \
-H "Content-Type: application/json" \
-d '{
"query": {
"terms": {
"path.keyword": [
"api/store/current-process",
"api/cancel_reason",
"api/reason_cancel_shipper",
"api/alert_reason",
"api/sorry_reason"
]
}
}
}'
Xử lý gốc rễ: Sửa logging middleware
Tìm đến middleware hoặc decorator logging của service TCHSYSTEM. Thêm logic lọc các endpoint có tần suất cao hoặc không cần log chi tiết.
Option 1: Bỏ qua hoàn toàn các endpoint không quan trọng
SKIP_LOG_PATHS = [
"api/store/current-process",
"api/cancel_reason",
"api/reason_cancel_shipper",
"api/alert_reason",
"api/sorry_reason",
]
def should_log(path: str) -> bool:
return path not in SKIP_LOG_PATHS
Option 2: Giữ log nhưng bỏ field nặng
HIGH_VOLUME_PATHS = [
"api/store/current-process",
"api/cancel_reason",
]
def build_log_document(request, response, path):
doc = {
"application": "TCHSYSTEM",
"method": request.method,
"path": path,
"status_code": response.status_code,
"response_time_ms": ...,
}
# Không lưu response body và headers cho endpoint tần suất cao
if path not in HIGH_VOLUME_PATHS:
doc["response"] = response.body
doc["headers"] = dict(request.headers)
return doc
Option 3: Truncate response body thay vì xóa hoàn toàn
MAX_RESPONSE_SIZE = 500 # bytes
def truncate_response(response_body: str) -> str:
if len(response_body) > MAX_RESPONSE_SIZE:
return response_body[:MAX_RESPONSE_SIZE] + "...[truncated]"
return response_body
Xử lý dài hạn: Thiết lập Index Lifecycle Management (ILM)
Để tránh index phình to mãi mãi, cấu hình ILM tự động xóa index cũ:
curl -X PUT "http://localhost:9200/_ilm/policy/tch_system_policy" \
-H "Content-Type: application/json" \
-d '{
"policy": {
"phases": {
"hot": {
"actions": {}
},
"delete": {
"min_age": "7d",
"actions": {
"delete": {}
}
}
}
}
}'
Chỉnh min_age theo nhu cầu giữ log của bạn (7 ngày, 30 ngày, v.v.).
Checklist khi gặp lại vấn đề tương tự
[ ] 1. Kiểm tra cluster health → curl localhost:9200/_cluster/health
[ ] 2. Liệt kê index theo kích thước → _cat/indices?v&s=store.size:desc
[ ] 3. Xem nội dung index nghi ngờ → _search?size=2
[ ] 4. Aggregate theo path để tìm endpoint spam → _search với aggs terms
[ ] 5. Quyết định: xóa index hay delete by query
[ ] 6. Sửa middleware để lọc/truncate log
[ ] 7. Verify Grafana hoạt động lại bình thường
[ ] 8. Cài ILM để tự động dọn dẹp index cũ
Kết luận
Vấn đề không phải Elasticsearch bị hỏng mà là logging quá nhiều thứ không cần thiết. Một middleware ghi đầy đủ request/response kết hợp với một bot polling tần suất cao có thể tạo ra hàng chục GB dữ liệu rác mỗi ngày, làm tê liệt toàn bộ observability stack.
Nguyên tắc quan trọng khi thiết kế logging:
- Chỉ log những gì thực sự cần để debug
- Không log response body của các endpoint trả về object lớn trừ khi có lỗi
- Loại trừ các health check và polling endpoint khỏi log
- Luôn có ILM hoặc retention policy để tự động dọn dẹp