Log index Elasticsearch giống file log của Linux: ghi liên tục, không rotate là disk đầy. Khác với logrotate, Elasticsearch tự rotate theo nhiều chiều cùng lúc (size, age, doc count), kèm migrate giữa node tier khác nhau và force-merge để giảm segment. Đây là Index Lifecycle Management (ILM).
Đây là bài 16 trong series Kibana từ A đến Z. Sau bài này bạn sẽ làm được:
- Phân biệt hot/warm/cold/frozen/delete và mục đích từng phase
- Viết policy ILM hoàn chỉnh cho
app-logs-*retention 90 ngày - Hiểu rollover alias và data stream khác nhau gì
- Áp dụng shrink, force-merge, searchable snapshot đúng thời điểm
- Tránh pitfall trộn rollover thủ công với ILM
Phần 1: Mental model 5 phase
[INDEX MỚI] ====> hot ====> warm ====> cold ====> frozen ====> delete
^ ^ ^ ^ ^
ingest read read S3 lazy gone
fast low rare retrieval
| Phase | Mục đích | Hardware đặc trưng | Thao tác có thể |
|---|---|---|---|
| hot | Index doc mới, query nóng | SSD NVMe, RAM nhiều | rollover, set priority |
| warm | Read nhiều, ít write | SSD SATA, RAM vừa | shrink, force-merge, allocate, readonly |
| cold | Read hiếm, không write | HDD, RAM ít | freeze (legacy), searchable snapshot |
| frozen | Lazy load từ S3, query phút | Block storage hoặc S3 | searchable snapshot mounted partial |
| delete | Xoá vĩnh viễn | n/a | delete |
Mỗi index ELK rolling thường đi qua hot plus warm plus cold plus delete. Frozen là tuỳ chọn cho compliance retention dài.
Note: phase freeze action đã deprecated từ 7.14. Thay bằng searchable snapshot (cold/frozen). Đọc bài 17 cho snapshot/restore.
Phần 2: Setup node tier
ILM cần biết node nào tier nào. Trong elasticsearch.yml của từng node:
# Hot node
node.roles: [data_hot, data_content, ingest, master]
# Warm node
node.roles: [data_warm, data_content]
# Cold node
node.roles: [data_cold]
# Frozen node
node.roles: [data_frozen]
Một node có thể có nhiều role (data_hot, data_warm) khi cluster nhỏ. Cluster lớn nên tách rõ ràng để hardware budget khớp tier.
Verify tier:
GET _cat/nodeattrs?h=node,attr,value
GET _cat/nodes?h=name,node.role
Cluster nhỏ (3-4 node), một pattern phổ biến: 2 node data_hot, data_warm, data_content, 1 node data_cold. Không cần frozen nếu retention dưới 90 ngày.
Phần 3: ILM policy đầu tay
Yêu cầu: index app-logs-* rolling daily, giữ 90 ngày, sang warm sau 7 ngày, cold sau 30 ngày, delete sau 90 ngày.
PUT _ilm/policy/app-logs-policy
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "50gb",
"max_age": "1d"
},
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "7d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 },
"set_priority": { "priority": 50 },
"allocate": {
"number_of_replicas": 1,
"include": { "_tier_preference": "data_warm,data_hot" }
}
}
},
"cold": {
"min_age": "30d",
"actions": {
"allocate": {
"number_of_replicas": 0,
"include": { "_tier_preference": "data_cold,data_warm,data_hot" }
},
"set_priority": { "priority": 0 }
}
},
"delete": {
"min_age": "90d",
"actions": { "delete": {} }
}
}
}
}
Phân tích:
min_agetính từ thời điểm rollover (không phải từ index creation). Lý do tinh tế ở phần 4.max_primary_shard_size: 50gbplusmax_age: 1d: rollover khi index hiện tại đạt 50GB shard chính HOẶC 1 ngày, lấy điều kiện nào tới trước.shrinkxuống 1 shard giảm overhead segment khi index không còn write. Yêu cầu số shard ban đầu chia hết cho số đích (3 plus 1 OK, 5 plus 2 không OK).forcemerge max_num_segments: 1gộp segment xuống 1 mỗi shard. Tăng query speed, giảm RAM.replicas: 0ở cold tier để tiết kiệm storage (chấp nhận giảm độ bền).
Phần 4: Rollover alias plus index template
Index template gắn policy plus alias rollover:
PUT _index_template/app-logs-template
{
"index_patterns": ["app-logs-*"],
"template": {
"settings": {
"index.lifecycle.name": "app-logs-policy",
"index.lifecycle.rollover_alias": "app-logs",
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"Level": { "type": "keyword" }
}
}
}
}
Tạo index đầu tiên kèm alias:
PUT app-logs-000001
{
"aliases": {
"app-logs": { "is_write_index": true }
}
}
App ingest dùng alias app-logs, không phải tên index trực tiếp. Khi rollover, alias tự chuyển sang index mới (app-logs-000002), app không cần biết.
Hoặc dùng data stream (đề xuất từ ES 7.9 trở đi)
Data stream là alias plus rollover plus naming auto:
PUT _index_template/app-logs-template
{
"index_patterns": ["app-logs-*"],
"data_stream": {},
"template": {
"settings": {
"index.lifecycle.name": "app-logs-policy"
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" }
}
}
}
}
Tạo data stream:
PUT _data_stream/app-logs
ES tự sinh backing index .ds-app-logs-2026.05.17-000001. Ingest gửi vào tên app-logs, ES route vào backing index hiện tại. Rollover auto sinh .ds-app-logs-2026.05.18-000002.
Data stream BẮT BUỘC mapping có @timestamp. App phải gửi @timestamp khi POST.
Đề xuất: dùng data stream cho log mới. Pattern alias plus rollover thủ công chỉ giữ cho data cũ.
Phần 5: Test ILM nhanh không chờ 7 ngày
Setting cluster để ILM check mỗi 10 giây thay vì 10 phút mặc định:
PUT _cluster/settings
{
"transient": { "indices.lifecycle.poll_interval": "10s" }
}
Policy test nhanh, phase hết hạn sau vài phút:
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": { "max_docs": 5 }
}
},
"warm": {
"min_age": "30s",
"actions": {
"shrink": { "number_of_shards": 1 }
}
},
"delete": {
"min_age": "2m",
"actions": { "delete": {} }
}
}
}
}
Index 6 doc, đợi 2 phút, check _cat/indices. Phải thấy index đã rollover plus delete.
Nhớ revert poll_interval về 10m cho production. Polling 10s liên tục là load không cần thiết.
Phần 6: Shrink action
Khi nào dùng shrink?
- Index không còn write (đã rollover).
- Số shard ban đầu cao (3-5) nhưng dữ liệu không lớn lắm.
- Muốn giảm số shard mỗi cluster (tránh hit shard limit 1000/node default).
Yêu cầu shrink:
- Index readonly:
index.blocks.write: true. - Mọi shard primary plus replica gom về 1 node.
- Cluster health green.
ILM tự lo cả 3 bước trong action shrink. Khi làm thủ công:
PUT app-logs-000001/_settings
{
"index.routing.allocation.require._name": "warm-node-1",
"index.blocks.write": true
}
POST app-logs-000001/_shrink/app-logs-000001-shrunk
{
"settings": {
"index.number_of_shards": 1,
"index.number_of_replicas": 1
}
}
Index shrunk là index mới với prefix tuỳ ý. ILM action shrink sinh tên shrink-<UUID>-<source>.
Pitfall: shrink không tự xoá index gốc. ILM action shrink xoá nguồn sau khi success, nhưng nếu làm tay phải dọn.
Phần 7: Force-merge
Mỗi search trên Elasticsearch quét nhiều segment. Index hot có 50-200 segment mỗi shard. Index không còn write nên force-merge xuống 1 segment để:
- Giảm RAM file pointer (FD).
- Tăng tốc query 30-50% trên index lớn.
- Cho phép searchable snapshot mount nhanh hơn.
Tránh force-merge index còn write. Sẽ tạo segment lớn không bao giờ được tỉa, tốn disk.
POST app-logs-000001/_forcemerge?max_num_segments=1
Thao tác heavy, chạy off-peak. Một force-merge index 50GB có thể chiếm 1-2 giờ disk I/O.
Phần 8: Searchable snapshot và frozen tier
Cold tier truyền thống: shard nằm trên HDD, replicas = 0 để tiết kiệm. Vẫn ăn disk node.
Searchable snapshot: shard ở S3, node chỉ mount metadata. Query lazy fetch từng segment cần thiết. Cold tier mới thường dùng pattern này.
Phase cold với searchable snapshot:
"cold": {
"min_age": "30d",
"actions": {
"searchable_snapshot": {
"snapshot_repository": "s3-cold-repo"
}
}
}
Phase frozen (lazy hơn nữa, chỉ cache 10% segment):
"frozen": {
"min_age": "90d",
"actions": {
"searchable_snapshot": {
"snapshot_repository": "s3-frozen-repo"
}
}
}
Trade-off: query frozen có thể mất vài giây tới phút cho lần đầu (lazy fetch). Phù hợp compliance retention, không phù hợp dashboard hàng ngày.
Bài 17 sẽ đi vào setup snapshot repository S3 chi tiết.
Phần 9: Monitoring ILM
Check status policy
GET _ilm/policy/app-logs-policy
Status từng index
GET app-logs-*/_ilm/explain
Response cho biết phase hiện tại, action đang chạy, lỗi nếu có:
{
"indices": {
"app-logs-000003": {
"policy": "app-logs-policy",
"phase": "warm",
"action": "shrink",
"step": "set-single-node-allocation",
"step_time_millis": 1716000000000
}
}
}
Retry sau lỗi
Step fail (ví dụ shrink fail do không đủ node warm):
POST app-logs-000003/_ilm/retry
Sửa nguyên nhân trước (thêm node warm, edit policy), rồi retry.
Stop/start ILM
Cần maintenance toàn cluster:
POST _ilm/stop
# do upgrade, restart, etc.
POST _ilm/start
Phần 10: Pitfall hay gặp
Pitfall 1: trộn rollover thủ công với ILM
Một team tôi từng thấy chạy cron job mỗi đêm POST app-logs/_rollover. Đồng thời ILM cũng rollover. Kết quả: rollover 2 lần/ngày, index không đầy ngưỡng, shard 200MB nhỏ vụn. Fix: chọn 1 cách. ILM thì gỡ cron, hoặc cron thì gỡ rollover khỏi ILM policy.
Pitfall 2: shard count chia không hết
ILM shrink fail nếu number_of_shards cũ không chia hết cho number_of_shards mới. Ví dụ 5 shard không shrink xuống 2 được. Fix: chọn shard count 1, 2, 3, 4, 6, 8, 12 (chia hết cho nhau).
Pitfall 3: ILM không chạy do alias lệch
Index template có rollover_alias: app-logs nhưng index đầu tay tạo không có alias đó. ILM bị stuck ở phase hot. Fix: alias is_write_index: true plus đúng tên với template.
Pitfall 4: forcemerge trên hot tier
Set forcemerge trong phase hot là sai. Forcemerge yêu cầu index readonly. Đặt vào phase warm trở đi.
Pitfall 5: cold tier hết disk vì không có policy cleanup
Cold tier disk có giới hạn. Nếu phase cold không có delete trong policy, log sẽ tích tụ mãi. SOC2 thường yêu cầu 1 năm, đặt delete.min_age: 395d để có dư.
Pitfall 6: data stream không xoá index thủ công
Data stream backing index quản lý bởi data stream. Đừng DELETE .ds-app-logs-... trực tiếp. Dùng DELETE _data_stream/app-logs để xoá toàn bộ, hoặc rely on ILM delete phase.
Cheatsheet
| Việc | Cách |
|---|---|
| Tạo policy | PUT /_ilm/policy/<name> |
| Gắn policy | index.lifecycle.name trong settings hoặc index template |
| Rollover alias | is_write_index: true plus rollover_alias |
| Data stream | Index template với data_stream: {} |
| Check index ILM | GET /<index>/_ilm/explain |
| Test nhanh | Set indices.lifecycle.poll_interval: 10s |
| Shrink | shrink.number_of_shards, phải chia hết |
| Force-merge | forcemerge.max_num_segments: 1 ở warm |
| Searchable snapshot | searchable_snapshot.snapshot_repository ở cold/frozen |
| Retry sau error | POST /<index>/_ilm/retry |
| Stop/start ILM | POST /_ilm/stop plus POST /_ilm/start |
Lời kết
ILM là cơ chế “tự lái” cho log index. Không có ILM, dev sẽ tự cron job rollover, tự script delete cũ, và sớm muộn quên một bước rồi disk full. Có ILM, policy nằm trong git, audit được, recreate được. Đầu tư 1 ngày setup ILM cho cluster mới là khoản đầu tư rẻ nhất.
Bài 17 sẽ đi vào Snapshot và Restore: setup repository S3, schedule snapshot lifecycle (SLM), restore index, test disaster recovery. Searchable snapshot ở cold/frozen tier mà bài này nhắc tới dựa hoàn toàn trên snapshot repository, nên bài 17 là prerequisite thực hành.