Elasticsearch как хранилище логов: индексы по времени

Как именно логи ложатся в Elasticsearch и почему индексы разбивают по времени, а не валят в один большой.

Индекс по времени — приём, при котором логи каждого периода (день, неделя) пишутся в отдельный индекс вида logs-2026.06.24, что упрощает поиск по дате и удаление старых данных.

Зачем разбивать индексы

Основы индексов и шардов Elasticsearch разобраны в отдельном учебнике — здесь нас интересует специфика именно логов. А специфика в том, что логи — это поток с временем жизни: их пишут постоянно, старые рано или поздно удаляют, читают чаще всего свежие. Под такой профиль один гигантский индекс «на всё» подходит плохо.

Если все логи лить в единственный индекс logs, то, во-первых, удалить данные старше 30 дней можно только запросом delete by query — он медленный и оставляет фрагментацию. Во-вторых, такой индекс бесконечно растёт, его шарды раздуваются, поиск по свежим данным замедляется из-за объёма старых.

Решение: индекс на период

Поэтому логи кладут в индексы, нарезанные по времени. Раньше это делали суффиксом-датой в имени:

  logs-2026.06.22
  logs-2026.06.23
  logs-2026.06.24   <-- сюда пишутся сегодняшние логи

Удалить логи за день — это просто DELETE logs-2026.06.22: мгновенная операция, удаляющая целый индекс, без фрагментации. Поиск «за вчера» идёт только по одному нужному индексу, а не по всему массиву истории.

Как работает под капотом: data streams и rollover

Современный Elastic не заставляет вас вручную мудрить с датами в имени. Вместо этого есть data stream — абстракция «бесконечного потока логов», за которой скрывается набор скрытых индексов с автоматическими именами вида .ds-logs-app-2026.06.24-000017.

{
  "index_patterns": ["logs-app-*"],
  "data_stream": {},
  "template": {
    "settings": { "number_of_shards": 1 }
  }
}

Вы пишете в один логический поток logs-app, а Elasticsearch сам по правилу rollover создаёт новый физический индекс, когда текущий достигает порога — например, 50 ГБ или возраста 1 день. Так размер каждого индекса остаётся управляемым автоматически, без ручного выбора «по дням или по неделям».

  пишем сюда:  logs-app  (один логический поток)
                  |
   rollover при 50 ГБ / 1 дне создаёт под капотом:
   .ds-logs-app-...000015  (закрыт)
   .ds-logs-app-...000016  (закрыт)
   .ds-logs-app-...000017  (активный, сюда идёт запись)

Маппинг логов — коротко

Маппинг — это схема полей индекса (какое поле какого типа). Для логов важны несколько решений: поле времени @timestamp типа date (по нему всё сортируется и фильтруется), текст сообщения, числа (status, bytes), а строковые идентификаторы лучше делать типом keyword (точное совпадение и агрегации), а не text (полнотекстовый разбор). Подробный разбор маппинга — в учебнике по Elasticsearch; для логов запомните: @timestamp + продуманные типы полей, иначе агрегации и сортировка будут работать неверно.

Почему логи — это append-only поток

Важная особенность логов, определяющая всю стратегию их хранения: логи практически никогда не обновляются и не удаляются по одному. Запись лога фиксирует факт, который уже случился, — её не редактируют. Это отличает логи от типичных данных приложения (где строки таблиц постоянно меняются) и роднит их с временными рядами: только добавление в конец (append-only) и массовое удаление целыми периодами. Elasticsearch и нарезка по времени идеально ложатся на этот профиль: новые события дописываются в активный индекс, а устаревшие удаляются оптом сбросом целого индекса. Любая попытка обращаться с логами как с изменяемой базой (искать и обновлять отдельные документы) идёт против их природы и упирается в неэффективность.

Из append-only природы следует ещё одно: для логов почти всегда отключают или минимизируют то, что нужно изменяемым данным. Например, репликацию на холодных индексах убирают (потеря старого узла не страшна, переиндексировать нечего), refresh_interval увеличивают (мгновенная видимость каждой записи логам не нужна), а сами документы делают неизменяемыми. Понимание, что логи — это поток фактов, а не редактируемая база, помогает принимать все эти решения осознанно.

Частые ошибки

  • Один индекс на всё. Главная ошибка хранения логов: невозможно дёшево удалять старое, индекс растёт бесконечно. Всегда нарезайте по времени (data stream + rollover).
  • Слишком мелкая нарезка. Индекс на каждый час при малом объёме порождает тысячи крошечных шардов — это тоже вредно. Размер шарда логов держат в районе десятков ГБ.
  • Идентификаторы как text. Если trace_id или user_id попадут типом text, по ним нельзя точно агрегировать. Для таких полей нужен keyword.

Итоги

  • Логи — это поток с временем жизни, поэтому их хранят в индексах, нарезанных по времени, а не в одном большом.
  • Нарезка делает удаление старого мгновенным (DELETE целого индекса) и ускоряет поиск по свежим данным.
  • Data stream + rollover автоматизируют создание новых индексов по порогу размера/возраста.
  • Для логов критичны @timestamp типа date и идентификаторы типа keyword.
Проверьте себя
1. Почему логи в Elasticsearch хранят в индексах, нарезанных по времени, а не в одном большом индексе?
AТак требует лицензия Elastic
BЧтобы дёшево удалять старые данные (DELETE целого индекса) и ускорять поиск по свежим
CПотому что один индекс не может хранить больше 1 ГБ
DЧтобы Kibana могла подключиться
2. Что делает механизм rollover в data stream?
AСжимает старые индексы
BАвтоматически создаёт новый физический индекс, когда текущий достигает порога по размеру или возрасту
CПереносит логи в Kibana
DМеняет тип полей в маппинге
3. Какой тип лучше выбрать для поля trace_id, по которому нужны точные совпадения и агрегации?
Atext
Bkeyword
Cdate
Dfloat