Лог-индексы растут бесконечно, если их не убирать: место на дисках кончается, кластер деградирует, а старые данные, которые никто не читает, продолжают занимать дорогое горячее хранилище наравне со свежими. Убирать их руками — задачей по расписанию, которая ищет индексы старше N дней и удаляет — можно, но это внешний cron-скрипт, отдельная точка отказа и ещё один процесс, за которым нужно следить отдельно от самого кластера. Index State Management (ISM) в OpenSearch решает ту же задачу декларативно и изнутри: политика, которая сама гоняет индексы по состояниям — от только что созданного hot до warm, снапшота и итогового удаления — без внешнего планировщика.
Мост уже был обозначен дважды. В статье про установку кластера retention был явно вынесен за скобки как отдельная большая тема. В статье про индексы и маппинги — короткий teaser: санитизированная политика warm → delete по возрасту и упоминание ism_template, который цепляет политику к app-logs-* на создании индекса. Это статья, где та же модель раскрывается полностью: не два состояния, а полный жизненный цикл индекса с allocation, снапшотом и мониторингом зависших переходов.
О версии и стенде. Примеры проверены на OpenSearch 3.5.0, мульти-нодовом стенде (hot/warm-ноды с разными
node.attr.box_type) с S3-совместимым хранилищем снапшотов. Пороги переходов в статье — ускоренные (минуты вместо дней), чтобы прогнать весь цикл живьём за разумное время; в проде те же условия задаются днями и десятками гигабайт, а не минутами и килобайтами.
В статье
- Анатомия ISM-политики
- Rollover под write-алиасом
- Переходы между состояниями
- Allocation: hot → warm
- Снапшот перед удалением
- Delete: конец жизненного цикла
- Мониторинг и эксплуатация
- Типичные ошибки
- Что дальше
- Источники
stateDiagram-v2
[*] --> hot: rollover создаёт индекс
hot --> warm: min_index_age
warm --> snapshot: min_index_age
snapshot --> delete: min_index_age
delete --> [*]: индекс удалён
Четыре состояния, три перехода, у каждого — своё условие и своё действие. Дальше — что стоит за каждой стрелкой на диаграмме, начиная с того, как это описывается одной политикой.
Анатомия ISM-политики
ISM-политика — это JSON-документ, который описывает конечный автомат: набор именованных состояний (states), в каждом состоянии — список действий (actions), которые ISM выполняет над индексом, пока он в этом состоянии, и список переходов (transitions) — условий, при выполнении которых индекс перемещается в другое состояние. default_state задаёт, в каком состоянии стартует индекс, впервые попавший под управление политики.
Teaser из статьи про маппинги показывал урезанную версию с двумя состояниями (warm → delete). Вот структура полной политики стенда — четыре состояния вместо двух, ровно та модель, что и на диаграмме выше:
{
"policy": {
"policy_id": "app-logs-policy",
"description": "app-logs: rollover, hot -> warm -> snapshot -> delete",
"default_state": "hot",
"states": [
{
"name": "hot",
"actions": [
{ "rollover": { "min_doc_count": 5 } }
],
"transitions": [
{ "state_name": "warm", "conditions": { "min_index_age": "1m" } }
]
},
{
"name": "warm",
"actions": [
{ "allocation": { "require": { "box_type": "warm" } } }
],
"transitions": [
{ "state_name": "snapshot", "conditions": { "min_index_age": "2m" } }
]
},
{
"name": "snapshot",
"actions": [
{ "snapshot": { "repository": "ism-snapshots", "snapshot": "app-logs" } }
],
"transitions": [
{ "state_name": "delete", "conditions": { "min_index_age": "3m" } }
]
},
{
"name": "delete",
"actions": [
{ "delete": {} }
],
"transitions": []
}
],
"ism_template": [
{ "index_patterns": ["app-logs-*"], "priority": 1 }
]
}
}Порядок переходов важен: transitions внутри состояния проверяются по очереди, и сработает первое условие, которое выполнилось — в этой политике у каждого состояния ровно один переход, но в политиках сложнее (например, с ветвлением по размеру ИЛИ по возрасту) порядок в списке определяет приоритет проверки. У состояния delete список transitions пуст — это терминальное состояние: после того как действие delete отработало, индекса больше не существует, и переходить ему больше некуда.
Действие внутри состояния и условие перехода из состояния — разные вещи, которые легко перепутать на первом чтении политики. actions — то, что ISM делает, пока индекс находится в состоянии (rollover в hot, allocation в warm, snapshot в одноимённом состоянии). conditions внутри transitions — то, что ISM проверяет, чтобы решить, пора ли переходить дальше (в этой политике — всегда min_index_age, возраст индекса). Действие выполняется один раз при входе в состояние (или повторяется по retry, если настроен и если не удалось с первой попытки); условие перехода проверяется на каждом цикле ISM (job_interval), пока не станет истинным.
Последний блок политики — ism_template, и это то место, где заканчивается сходство с ручным attach политики к индексу. index_patterns: ["app-logs-*"] означает: любой индекс, чьё имя подходит под маску, автоматически получает эту политику в момент создания, без отдельного вызова PUT _plugins/_ism/add/<index>. priority разруливает конфликт, если под один и тот же индекс подходят несколько ism_template из разных политик — побеждает более высокий приоритет. Именно этот механизм в разделе «Rollover под write-алиасом» объясняет, откуда у только что созданного app-logs-000002 уже есть policy_id в _ism/explain, хотя никто не привязывал политику к нему вручную.
Rollover под write-алиасом
Самый простой способ отправлять новые данные не в один разрастающийся индекс, а в серию — это делать это руками: создавать app-logs-2026.07.03, завтра app-logs-2026.07.04, и так каждый день по cron. Работает, пока не начинает не работать: скрипт ротации — ещё один процесс со своим расписанием, отдельно от кластера и отдельно от политики хранения; момент переключения на новый индекс завязан на календарь, а не на то, сколько документов реально влетело; а если скрипт не отработал (упал раннер, не хватило прав), пишущие приложения продолжают долбить во вчерашний индекс, раздувая его сверх меры, и никто этого не заметит, пока индекс не станет слишком большим для одного шарда. rollover-action в ISM решает ту же задачу изнутри кластера: индекс переключается не по календарю, а по реальному состоянию — размеру, возрасту или количеству документов, — и переключение делает сам ISM, атомарно, на каждом цикле проверки.
Технически это опирается на write-алиас — тот же механизм, что был показан в статье про индексы и маппинги: один алиас указывает на несколько индексов, но только один из них помечен is_write_index: true и принимает запись. Rollover — это ISM, который делает то самое переключение is_write_index за вас, по условию, а не вручную через _aliases.
Bootstrap на стенде выглядит так: первый индекс создаётся с флагом is_write_index: true, именем алиаса в rollover_alias, требованием размещения на hot-ноде и — явно — привязкой к политике через policy_id:
{
"aliases": {
"app-logs": { "is_write_index": true }
},
"settings": {
"plugins.index_state_management.rollover_alias": "app-logs",
"index.routing.allocation.require.box_type": "hot",
"plugins.index_state_management.policy_id": "app-logs-policy"
}
}Почему policy_id здесь задан явно, хотя политика уже содержит ism_template с маской app-logs-* из раздела «Анатомия ISM-политики»? Потому что bootstrap-индекс создаётся сразу вслед за политикой, и авто-привязка через ism_template в этот момент — гонка: если шаблон ещё не разошёлся по кластеру к моменту создания индекса, индекс останется без политики навсегда — ism_template срабатывает только на создании и не привязывается задним числом. Внешне это самый обидный класс сбоя: индекс есть, документы пишутся, а _plugins/_ism/explain пуст и никаких переходов не происходит. Явный policy_id на самом первом индексе эту гонку убирает. Для последующих app-logs-000002, -000003 … её нет: они рождаются rollover-ом уже заметно позже политики, и ism_template подхватывает их надёжно (см. «Rollover под write-алиасом»).
Дальше в алиас app-logs залито 12 документов — запись шла не в имя индекса, а в алиас, и до rollover _cat/aliases показывает ровно один индекс с правом записи:
alias index is_write_index
app-logs app-logs-000001 trueВ политике app-logs-policy состояние hot содержит действие rollover с условием min_doc_count: 5 — на следующем цикле ISM (job_interval на стенде — минута) индекс app-logs-000001 уже содержит 12 документов, порог превышен, и rollover срабатывает. Появляется новый индекс с именем по шаблону -NNNNNN — предсказуемым, инкрементным, без ручного выбора даты или суффикса:
health status index pri rep docs.count store.size
green open app-logs-000001 1 0 12 20.2kb
green open app-logs-000002 1 1 0 416bА в _cat/aliases видно главное — write-индекс переехал атомарно, без окна, в котором запись могла бы уйти в никуда или задвоиться в оба индекса:
alias index is_write_index
app-logs app-logs-000001 false
app-logs app-logs-000002 trueGET _plugins/_ism/explain/app-logs-000001 подтверждает произошедшее с точки зрения самого ISM — не только новый индекс существует, но и explain явно фиксирует, что переход был rollover-ом, а не ручным действием, и что условие сработало именно по количеству документов:
{
"app-logs-000001" : {
"policy_id" : "app-logs-policy",
"rolled_over" : true,
"rolled_over_index_name" : "app-logs-000002",
"state" : { "name" : "hot" },
"action" : { "name" : "rollover", "failed" : false },
"step" : { "name" : "attempt_rollover", "step_status" : "completed" },
"info" : {
"message" : "Successfully rolled over index [index=app-logs-000001]",
"conditions" : { "min_doc_count" : { "condition" : 5, "current" : 12 } }
}
}
}Важная деталь, которую легко пропустить: rollover не забирает app-logs-000001 из-под управления ISM — индекс просто перестаёт принимать запись и остаётся в состоянии hot политики, откуда пойдёт дальше по своим переходам (следующий раздел). А policy_id: app-logs-policy у нового app-logs-000002 появился без единого ручного вызова _plugins/_ism/add — сработал ism_template из раздела «Анатомия ISM-политики»: маска app-logs-* подхватила индекс в момент его создания rollover-ом.
Условие min_doc_count: 5 в примере — учебное, чтобы rollover наступил за один цикл ISM. В rollover-action доступны и другие условия: min_size (суммарный размер индекса), min_primary_shard_size (размер одного primary-шарда — полезно, когда шардов несколько и важен размер каждого в отдельности) и min_index_age (возраст с момента создания). Условия комбинируются через ИЛИ: rollover срабатывает по первому выполнившемуся, что в проде обычно означает пороги вида «50 ГБ или 1 день, что наступит раньше» — так индекс не растёт бесконечно даже во время затишья по трафику, и не ротируется на пустом месте при всплеске записи.
Переходы между состояниями
Rollover в hot — только первый переход на диаграмме жизненного цикла. Дальше индекс идёт по цепочке hot → warm → snapshot → delete, и каждый шаг в этой цепочке управляется тем же механизмом transitions, что уже был показан в разделе «Анатомия ISM-политики»: список условий внутри состояния, которые ISM проверяет на каждом цикле job_interval, пока одно из них не станет истинным.
В политике стенда все три перехода после hot используют одно и то же условие — min_index_age, возраст индекса с момента создания:
hot → warm:min_index_age: "1m"warm → snapshot:min_index_age: "2m"snapshot → delete:min_index_age: "3m"
Это не единственное доступное условие. Кроме min_index_age в transitions можно использовать min_size (совокупный размер индекса — тот же смысл, что и в rollover-action, но теперь как условие для смены состояния, а не для ротации) и min_doc_count (число документов). На стенде для наглядности использован именно возраст — с ним проще воспроизвести полный цикл живьём за несколько минут и предсказать момент срабатывания, не подгоняя объём тестовых данных под пороги размера.
Когда в одном состоянии несколько transitions с разными условиями (например, «в warm, если индекс старше 30 дней ИЛИ больше 50 ГБ»), правило то же, что и для условий rollover: сработает первое условие в списке, которое стало истинным на момент проверки, а не то, что «сильнее» по смыслу. Отсюда практическое следствие для проектирования политики — порядок условий в массиве transitions стоит выбирать осознанно, а не как попало, если у состояния их больше одного.
Проверить, где сейчас находится индекс и почему он ещё не перешёл дальше, можно тем же _plugins/_ism/explain, что уже был показан выше для rollover — только теперь интересны поля state.name (текущее состояние) и transitions_conditions внутри explain-ответа (значение условия против текущего). Подробный разбор explain как инструмента диагностики — в разделе «Мониторинг и эксплуатация» дальше по статье; здесь важно только то, что explain — это не отдельный лог, а живой снимок того, что ISM думает об индексе прямо сейчас.
Пороги 1m/2m/3m в примерах выше — стендовые, подобранные, чтобы прогнать весь цикл hot → warm → snapshot → delete за считаные минуты и увидеть каждый переход живьём. В проде те же условия задаются на порядки крупнее: min_index_age — днями или неделями, min_size — десятками гигабайт. Механизм при этом не меняется ни на шаг: то же transitions, та же проверка на каждом job_interval, то же правило «первое сработавшее условие» — разница только в числах. Чтобы контраст был предметным, вот те же четыре состояния с прод-порогами вместо стендовых минут (числа иллюстративные — отталкивайтесь от своего трафика и требований к хранению):
"states": [
{ "name": "hot",
"actions": [ { "rollover": { "min_size": "50gb", "min_index_age": "1d" } } ],
"transitions": [ { "state_name": "warm", "conditions": { "min_index_age": "14d" } } ] },
{ "name": "warm",
"actions": [ { "allocation": { "require": { "box_type": "warm" } } } ],
"transitions": [ { "state_name": "snapshot", "conditions": { "min_index_age": "30d" } } ] },
{ "name": "snapshot",
"actions": [ { "snapshot": { "repository": "app-logs-repo", "snapshot": "app-logs" } } ],
"transitions": [ { "state_name": "delete", "conditions": { "min_index_age": "90d" } } ] },
{ "name": "delete", "actions": [ { "delete": {} } ], "transitions": [] }
]Читается так: rollover закрывает индекс по достижении 50 ГБ или суток жизни (что наступит раньше), через две недели индекс уходит в warm, на 30-й день с него снимается снапшот, а удаляется он на 90-й — снимок к этому моменту давно в репозитории, а горячее и тёплое хранилище освобождается по графику. Все min_index_age отсчитываются от создания индекса, поэтому в цепочке они обязаны только расти: 14d < 30d < 90d.
Allocation: hot → warm
Переход в состояние warm в политике из раздела «Анатомия ISM-политики» — не просто смена имени в _ism/explain. У состояния warm есть собственное действие, allocation, и оно физически перемещает шард с одной ноды на другую. Чтобы это действие сработало, кластер должен уметь различать ноды не только по имени, но и по роли в жизненном цикле данных.
Механизм — произвольные node-атрибуты. На стенде у каждой ноды задан атрибут box_type при старте (node.attr.box_type), и _cat/nodeattrs подтверждает разметку:
node attr value
ism-os-hot box_type hot
ism-os-warm box_type warmТот же атрибут уже использовался раньше в статье, просто с другой стороны: в разделе «Rollover под write-алиасом» bootstrap-индекс app-logs-000001 создавался с index.routing.allocation.require.box_type: "hot" — это то же требование к размещению, но заданное на самом индексе, а не через ISM. allocation-action в политике делает ровно то же самое действие программно, в нужный момент жизненного цикла:
{
"name": "warm",
"actions": [
{ "allocation": { "require": { "box_type": "warm" } } }
],
"transitions": [
{ "state_name": "snapshot", "conditions": { "min_index_age": "2m" } }
]
}require: {box_type: warm} — это ограничение размещения шардов индекса: OpenSearch обязан держать их только на нодах с атрибутом box_type=warm. Как только индекс app-logs-000001 попадает в состояние warm (по условию min_index_age: 1m из предыдущего раздела), ISM применяет это ограничение к индексу, и балансировщик кластера начинает релокацию — переносит primary-шард с hot-ноды на единственную ноду, которая теперь удовлетворяет условию.
_cat/shards до и после перехода показывает эту релокацию напрямую — не косвенно через _ism/explain, а по факту того, где физически лежит шард:
index shard prirep state node
app-logs-000001 0 p STARTED ism-os-hotindex shard prirep state node
app-logs-000001 0 p STARTED ism-os-warmНода сменилась с ism-os-hot на ism-os-warm, при этом никто не вызывал _cluster/reroute руками — перенос выполнил сам ISM как побочный эффект входа в состояние warm.
На стенде два node-атрибута воспроизводят идею простейшим способом: одна и та же нода физически ничем не отличается от другой, разница — только в ярлыке box_type. В проде тот же приём применяется на неоднородном железе: box_type=hot вешают на ноды с NVMe/SSD и большим объёмом RAM под кеш файловой системы, box_type=warm — на ноды с HDD или более дешёвыми дисками, где данные лежат, но читаются редко. Разница в железе — вся экономика warm-тира: старые данные продолжают быть доступны для поиска, но не занимают дорогое хранилище. Сам ярлык может быть любым — box_type, temp или что угодно своё: для OpenSearch это произвольный node-атрибут, на который ссылается allocation-правило ISM, и именно node-атрибуты — штатный способ разметить ноды под hot/warm в этом контексте. Ещё один шаг дальше по этой же оси — searchable snapshots и полноценный cold-тир, где данные вообще не хранятся на диске ноды постоянно, а подтягиваются из S3 по запросу; это отдельная и существенно более сложная тема, за рамками этой статьи.
Снапшот перед удалением
Состояние delete в конце цепочки необратимо: delete-action стирает индекс из кластера, и без резервной копии данные исчезают безвозвратно. Состояние snapshot, стоящее в политике перед delete, снимает этот риск — индекс уходит из кластера, но не из существования: снимок остаётся во внешнем хранилище, и данные при необходимости можно поднять обратно через restore, уже вне рамок retention-политики как таковой.
Снапшоты в OpenSearch пишутся в заранее зарегистрированный репозиторий. На стенде это S3-совместимое хранилище (MinIO), подключённое через плагин repository-s3; регистрация репозитория — разовая настройка кластера, а не часть ISM-политики:
{
"type": "s3",
"settings": {
"bucket": "snapshots",
"client": "default",
"base_path": "ism"
}
}Реквизиты доступа к S3 (endpoint, access/secret key) в это тело не входят — они настраиваются отдельно через secure settings кластера (opensearch-keystore), а не передаются в API как обычные параметры. Дальше в политике состояние snapshot содержит единственное действие — снять снимок конкретного индекса в этот репозиторий:
{
"name": "snapshot",
"actions": [
{ "snapshot": { "repository": "ism-snapshots", "snapshot": "app-logs" } }
],
"transitions": [
{ "state_name": "delete", "conditions": { "min_index_age": "3m" } }
]
}Как только индекс app-logs-000001 достигает состояния snapshot, ISM инициирует снимок в репозиторий ism-snapshots, и _snapshot/<repo>/_all подтверждает, что снимок реально создан и завершён успешно:
{
"snapshots": [
{
"snapshot": "app-logs-2026.07.03-20:25:42.090",
"state": "SUCCESS",
"indices": ["app-logs-000001"],
"shards": { "total": 1, "failed": 0, "successful": 1 }
}
]
}Имя снимка ISM сгенерировал сам, добавив к заданному в политике app-logs метку времени — так снимки одного и того же индекса на разных прогонах не перезаписывают друг друга. state: SUCCESS и shards.successful: 1 при shards.failed: 0 — снимок единственного шарда индекса прошёл целиком, без частичных сбоев. Дальше сработает переход min_index_age: 3m, и delete-action уже безопасно удалит индекс из кластера — данные к этому моменту физически лежат в бакете снапшотов, а не только в кластере.
Здесь проходит граница между тем, что делает retention-политика, и тем, что называется полноценным backup. Snapshot-action в ISM решает узкую задачу — не потерять данные конкретного индекса в момент его планового удаления по возрасту. Управление снапшотами как таковыми — расписание, ретенция самих снимков, восстановление, снапшоты состояния всего кластера, а не отдельного индекса — задача Snapshot Management (так этот механизм называется в OpenSearch) или внешнего инструмента резервного копирования, и это отдельная тема, требующая отдельного разбора.
Delete: конец жизненного цикла
Последнее звено цепочки — состояние delete из политики в разделе «Анатомия ISM-политики». Действие внутри него простое до предела:
{
"name": "delete",
"actions": [
{ "delete": {} }
],
"transitions": []
}Пустой объект {} — у delete-action нет параметров: снять индекс с кластера можно только целиком, частичного удаления документов или диапазона дат этот механизм не делает. Удалить отдельные документы по условию можно через delete_by_query, но для логов это дорого и не отменяет роста индекса — правильнее с самого начала проектировать индексы по диапазонам (через rollover, как выше) и удалять их целиком, что delete-action и делает. Пустой массив transitions — сигнал того же рода, что уже был отмечен в разделе про анатомию политики: из delete переходить больше некуда, потому что после того как действие отработало, индекса, которым можно было бы управлять дальше, физически не существует.
Индекс app-logs-000001 в этой политике проходит весь путь без ручного вмешательства: rollover выводит его из-под записи («Rollover под write-алиасом»), min_index_age: 1m переводит в warm, allocation-action переносит шард на warm-ноду («Allocation: hot → warm»), min_index_age: 2m переводит в snapshot, snapshot-action снимает копию в MinIO («Снапшот перед удалением»), и наконец min_index_age: 3m переводит в delete, где индекс исчезает. Весь путь — от bootstrap до исчезновения индекса — проходит сам, без единого ручного шага после первоначального PUT индекса и политики. А вот на «сколько это займёт по часам» полагаться нельзя, и это важный нюанс механики ISM: пороги min_index_age задают лишь минимальный возраст для перехода, но фактический момент срабатывания привязан к периодическому запуску фоновой ISM-джобы (job_interval, по умолчанию порядка нескольких минут). Индекс не переходит «ровно в возрасте 1m» — он переходит на ближайшем после этого прогоне джобы. Поэтому даже с ускоренными порогами 1m/2m/3m полный цикл — это не фиксированные «N минут», а несколько последовательных прогонов ISM-джобы: на demo-стенде — порядка нескольких минут, но конкретное время плавает в зависимости от job_interval и загрузки кластера. Отслеживать прогресс между переходами правильнее не по секундомеру, а по _plugins/_ism/explain (см. «Мониторинг и эксплуатация»).
_cat/indices после того как delete-action отработал, показывает итог напрямую — app-logs-000001 в списке больше нет:
health status index docs.count store.size
green open app-logs-000002 0 416bИндекс, который на предыдущих шагах статьи проходил rollover, allocation и снапшот, из вывода пропал целиком — не помечен как закрытый, не переведён в какое-то промежуточное состояние, а физически отсутствует в кластере. В списке остаётся только app-logs-000002 — тот самый rolled-over индекс из раздела про rollover, который на момент проверки ещё не успел состариться до warm.
Это и есть смысл того, что снапшот стоит в политике перед delete, а не после: app-logs-000001 больше не существует в кластере, но снимок, снятый в состоянии snapshot, остался в бакете snapshots в MinIO — те же объекты (snap-*.dat, meta-*.dat, индексные метаданные), что были записаны туда до удаления. Delete необратим для кластера — данные из hot/warm-хранилища ушли безвозвратно, шард нельзя вернуть командой отмены. Но он не необратим для самих данных: restore из репозитория ism-snapshots поднимет индекс заново с тем содержимым, что было на момент снимка. Retention-политика в этом смысле не «стирает» данные, а перекладывает их из дорогого горячего хранилища кластера в дешёвое объектное хранилище — то же разделение по стоимости хранения, что и в переходе hot → warm, только доведённое до предела: индекс вообще перестаёт занимать место на нодах.
Мониторинг и эксплуатация
Политика ISM — это автомат, который работает сам, но «сам» не значит «без наблюдения». У автомата есть состояние, которое можно проверить, и есть сбои, которые не останавливают его демонстративно — индекс просто перестаёт двигаться по переходам и остаётся в текущем состоянии, пока кто-то не заметит и не вмешается. Основной инструмент для этого — тот же _plugins/_ism/explain, что уже использовался в разделах про rollover и переходы, только здесь он рассматривается как инструмент эксплуатации, а не как способ проиллюстрировать конкретный шаг.
На стенде именно так и произошло — на первом прогоне, когда индекс-шаблона с rollover_alias ещё не было: app-logs-000002 — индекс, появившийся в результате rollover («Rollover под write-алиасом») — застрял, не дойдя даже до первого перехода. _plugins/_ism/explain показал состояние и причину прямо в ответе:
Про демо-скрипт. В
ism.shиз cookbook этот сбой уже предотвращён:setupзаводит index-шаблон, проставляющийrollover_aliasвсемapp-logs-*(включая rolled-over индексы), поэтому чистый прогон скрипта до ошибки не доходит. Разбор ниже — реальный случай с первого прогона без шаблона, оставленный как обучающий пример того, как ISM сообщает о зависании и как его расшить.
{
"app-logs-000002": {
"policy_id": "app-logs-policy",
"state": { "name": "hot" },
"action": {
"name": "rollover",
"failed": true
},
"info": {
"message": "Missing rollover_alias index setting [index=app-logs-000002]"
}
}
}Причина — не сбой ISM как такового, а различие в том, как формировались два индекса. Bootstrap-индекс app-logs-000001 создавался вручную («Rollover под write-алиасом») с явной настройкой plugins.index_state_management.rollover_alias: "app-logs" в теле PUT. Индекс app-logs-000002, который появился не через ручной PUT, а как результат самого rollover, эту настройку не унаследовал — rollover создаёт новый индекс, но не копирует в него settings родителя автоматически. В результате, когда для app-logs-000002 в будущем должен сработать его собственный rollover, ISM не находит, по какому алиасу его вообще ротировать, и действие падает с ошибкой прямо в explain, а не молча.
Чинится это без пересоздания индекса — двумя вызовами. Сначала настройка добавляется индексу напрямую:
{
"plugins.index_state_management.rollover_alias": "app-logs"
}Ответ подтверждает применение:
{ "acknowledged": true }Затем ISM явно просят повторить упавшее действие — сам по себе автомат не переопрашивает индексы, зависшие в ошибке, на каждом цикле, это нужно инициировать вручную через _plugins/_ism/retry:
{ "updated_indices": 1, "failures": false, "failed_indices": [] }updated_indices: 1 и пустой failed_indices — retry принят и применён к одному индексу без ошибок. Повторный _ism/explain после этого показывает, что индекс вышел из состояния ошибки и снова в игре:
{
"app-logs-000002": {
"failed": false,
"info": { "message": "Pending retry of failed managed index" }
}
}failed: false — ошибка снята, а info.message прямо называет происходящее: индекс не «починен» мгновенно, а поставлен в очередь на повторную попытку действия на следующем цикле ISM. В проде эта конкретная причина сбоя устраняется не ручным _settings на каждый индекс по факту зависания, а на уровне создания индексов: index-шаблон, который проставляет rollover_alias всем индексам маски app-logs-* при их создании — включая те, что появляются в результате rollover, а не только bootstrap-индекс. Тогда retry остаётся инструментом на случай непредвиденных сбоев (недоступность S3 в момент снапшота, нехватка места под allocation), а не штатной процедурой после каждого rollover.
explain работает не только по одному индексу — тот же вызов по маске (GET _plugins/_ism/explain/app-logs-*) возвращает состояние сразу всех управляемых индексов под этим паттерном, и на практике это основной способ получить обзор: сколько индексов сейчас в hot, сколько в warm, есть ли среди них хоть один с failed: true. Для регулярной эксплуатации это ровно то, что стоит завести отдельной проверкой — не разово через API-вызов из терминала, а метрикой или алертом, который уведомляет, как только failed в explain-ответе какого-либо индекса становится true, вместо того чтобы обнаруживать зависший индекс по разросшемуся диску спустя недели.
Типичные ошибки
Большая часть проблем с ISM не в сложности механизма, а в мелочах, которые не видны, пока не наступишь на них живьём — как и произошло на стенде этой статьи.
- Политика без
ism_template. Без блокаism_templateиз раздела «Анатомия ISM-политики» политика не цепляется к новым индексам автоматически — её приходится привязывать вручную через_plugins/_ism/add/<index>к каждому индексу по отдельности. Для одного индекса это терпимо, для растущего потокаapp-logs-*— постоянный ручной шаг, который рано или поздно забудут сделать, и новый индекс останется вообще без управления жизненным циклом. - Rollover без корректного
rollover_aliasна новых индексах. Это ровно та ситуация, что показана в разделе «Мониторинг и эксплуатация»: rolled-over индекс не наследует настройкуplugins.index_state_management.rollover_aliasот родителя и застревает на «Missing rollover_alias index setting». Разовыйretryчинит конкретный индекс, но не проблему — правильное решение — index-шаблон, который проставляетrollover_aliasвсем индексам маскиapp-logs-*при создании, включая те, что рождаются самим rollover-ом, а не только bootstrap-индекс. - Агрессивный delete без снапшота. Если состояние
deleteстоит в политике сразу послеhot/warm, без промежуточногоsnapshotиз раздела «Снапшот перед удалением», — данные не вернуть: как толькоdelete-action отработал, индекс физически перестаёт существовать в кластере, и никакого «отмени последнее действие» в ISM нет. Снапшот перед delete — не опциональная предосторожность, а единственный способ сохранить возможность восстановления. - Rollover без write-алиаса. Без
is_write_index/rollover_alias, показанных в разделе «Rollover под write-алиасом», у rollover нет способа понять, какой индекс сейчас «текущий» и куда переключать запись — действие либо падает с ошибкой, либо (при ручной ротации без алиаса вовсе) переключение приходится делать вручную, теряя весь смысл автоматизации. - Игнор зависших переходов. Индекс, застрявший в состоянии —
failed: trueв_plugins/_ism/explain— не чинится сам: ISM не переопрашивает упавшие действия на каждом цикле, для этого существует отдельный вызов_plugins/_ism/retry, разобранный в разделе «Мониторинг и эксплуатация». Если никто не смотрит в explain и не вызывает retry, retention для этого индекса молча не работает — данные продолжают копиться там, где их должно было не быть. - Забыли вернуть ускоренные пороги в прод-значения. Пороги вида
min_index_age: "1m", удобные для демонстрации полного цикла за несколько минут (как на стенде этой статьи), в проде означают одно: данные удалятся через минуту после создания индекса, а не через положенные14d. Политику, скопированную из тестового прогона, всегда стоит перечитать перед выкатом в прод — и сверить каждоеmin_index_age/min_size/min_doc_countс реальными требованиями хранения, а не с тем, что было удобно для отладки.
Что дальше
Всё, что показано в статье — вызовы _plugins/_ism/explain, _cat/shards, _snapshot/.../_all — читается напрямую из API, но визуальный контроль ISM удобнее вести не построчным curl’ом, а панелью: состояния индексов, история переходов и failed-действия сразу на одном экране. Этому посвящена следующая статья серии — OpenSearch Dashboards. Полноценная стратегия резервного копирования — расписание снапшотов, их собственная ретенция, восстановление всего кластера, а не одного индекса — задача Snapshot Management или внешнего backup-инструмента и выходит за рамки этой статьи, где снапшот — лишь один шаг retention-цепочки. Ещё дальше по той же оси экономии хранилища — searchable snapshots и полноценный cold-тир, где данные лежат в S3 и остаются доступны для поиска без полного restore; это отдельная инфраструктурная тема, здесь только обозначенная как следующий шаг за пределами retention.
Источники
- Index State Management — обзор ISM: политики, states, transitions, actions
- ISM policies — структура политики,
default_state,ism_template - ISM API —
_plugins/_ism/explain,_plugins/_ism/retry, add/remove policy - Rollover — условия
min_doc_count,min_size,min_primary_shard_size,min_index_age - Allocation action — размещение шардов по node-атрибутам
- Snapshot management — снапшоты и восстановление
- Register repository — регистрация snapshot-репозитория
- S3 repository plugin — плагин
repository-s3, secure settings для credentials
Комментарии