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
PhaseMục đíchHardware đặc trưngThao tác có thể
hotIndex doc mới, query nóngSSD NVMe, RAM nhiềurollover, set priority
warmRead nhiều, ít writeSSD SATA, RAM vừashrink, force-merge, allocate, readonly
coldRead hiếm, không writeHDD, RAM ítfreeze (legacy), searchable snapshot
frozenLazy load từ S3, query phútBlock storage hoặc S3searchable snapshot mounted partial
deleteXoá vĩnh viễnn/adelete

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_age tí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: 50gb plus max_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.
  • shrink xuố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: 1 gộ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:

  1. Index readonly: index.blocks.write: true.
  2. Mọi shard primary plus replica gom về 1 node.
  3. 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ệcCách
Tạo policyPUT /_ilm/policy/<name>
Gắn policyindex.lifecycle.name trong settings hoặc index template
Rollover aliasis_write_index: true plus rollover_alias
Data streamIndex template với data_stream: {}
Check index ILMGET /<index>/_ilm/explain
Test nhanhSet indices.lifecycle.poll_interval: 10s
Shrinkshrink.number_of_shards, phải chia hết
Force-mergeforcemerge.max_num_segments: 1 ở warm
Searchable snapshotsearchable_snapshot.snapshot_repository ở cold/frozen
Retry sau errorPOST /<index>/_ilm/retry
Stop/start ILMPOST /_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.