Как мы оказались здесь

Elasticsearch построен на инвертированных индексах. Для полнотекстового поиска это до сих пор лучшая структура: O(1) на поиск слова, ранжирование по релевантности, сложные булевы запросы. Каталоги, документация, CMS - тут ему равных нет.

Проблема появилась когда индустрия начала ставить ES на логи. Терабайты записей, которые никто не ищет по релевантности. Их фильтруют по времени и сервису, грепают по подстроке, агрегируют для дашбордов. Для этих задач инвертированный индекс избыточен, а его накладные расходы становятся основной строкой в бюджете.

Как Elastic даёт отпор

Elastic видит проблему и активно работает.

Logsdb index mode (ES 8.15+) - главный ответ Elastic на проблему storage. Вместо хранения _source как JSON blob используется synthetic source: документ восстанавливается из индекса при чтении. По данным Elastic (октябрь 2024), это даёт 50-65% экономию хранилища.

Что это значит для сравнения с ClickHouse: вендорский бенчмарк показывал 12-19x разрыв по storage, но он сделан до Logsdb. С Logsdb ES-хранилище сжимается почти вдвое - реальный разрыв скорее 5-10x. Для многих команд это уже не "порядок величины", а "ощутимая, но терпимая разница". Если ваш основной pain point - storage, Logsdb может оказаться достаточным решением без смены стека.

Чего Logsdb не решает:

  • Скорость тяжёлых агрегаций. GROUP BY service_name по миллиарду строк - это не про индексы, это про layout данных на диске. Колоночный движок тут быстрее архитектурно.
  • JVM heap. Агрегации, кеши, circuit breakers по-прежнему живут в heap. 32 GB выделенной памяти - фиксированная стоимость.
  • GC-паузы под нагрузкой. Проблема JVM runtime, не индекса.

TSDB - оптимизации для time-series. Сортировка по времени, дедупликация, эффективное хранение метрик.

Elastic Serverless - разделение compute и storage. Та же идея, что у Hydrolix или ClickHouse Cloud.

ES|QL - новый язык запросов, ближе к piped-стилю. Попытка дать альтернативу KQL для аналитики.

Row-oriented document store и columnar engine решают разные задачи. ES можно бесконечно ускорять на поиске. ClickHouse можно бесконечно ускорять на агрегациях. Lucene активно развивается (SIMD, off-heap storage, vectorized execution), но они не сойдутся, потому что оптимизированы под разные паттерны доступа.

Что показывают реальные миграции

Вендорские бенчмарки1 дают красивые цифры (5x на агрегациях, 6x на фильтрации), но они от заинтересованных сторон. Единственный надёжный источник - production-данные компаний, которые прошли миграцию.

Uber - сократили кластер в два раза при увеличении нагрузки. Write throughput вырос в 3-4 раза. Запросы ускорились примерно в 5 раз. Мигрировали 10,000 дашбордов Kibana через промежуточный слой QueryBridge, постепенно, с feature flags.

Trip.com - начинали с Elasticsearch, столкнулись с проблемами на масштабе ~4 PB. Построили новую платформу на ClickHouse, которая выросла до 50 PB. Это не перенос данных с сжатием, а новая система для новых объёмов. Запросы быстрее в 4-30 раз, P90 < 300 мс.

Didi - 400+ физических нод ClickHouse. Пиковая запись 40 GB/s. ~15 миллионов запросов в день. Железо на 30% дешевле, запросы в 4 раза быстрее, P99 в пределах секунды.

Contentsquare - infra-стоимость в 11 раз ниже. Важная оговорка: это только инфраструктура, без учёта 6 месяцев миграции, обучения людей и перестройки процессов. Ретеншн вырос с 1 месяца до 13 месяцев.

Ви.Tech (Хабр) - 200 GB логов в день. После миграции: 9 нод вместо 18, CPU/RAM на запись в 8 раз меньше, размер лога 60 байт вместо 600. Ретеншн вырос с 5 часов до 4 дней.

Большинство этих компаний работают на масштабах, где ES упирается в архитектурные лимиты. Но даже на умеренных объёмах (Ви.Tech, 200 GB/день) миграция дала кратный рост ретеншна - с 5 часов до 4 дней на вдвое меньшем количестве нод. Паттерн: экономия на инфраструктуре в разы, ускорение запросов в разы, ретеншн растёт кратно.

Почему колоночное хранилище выигрывает на логах

Фраза "читает только нужные колонки" - верхушка айсберга. Под капотом целая цепочка оптимизаций.

ORDER BY ключ таблицы - основа всего. ORDER BY определяет физический порядок данных на диске. Для логов типичный ключ - (service_name, timestamp). Данные одного сервиса лежат рядом, отсортированные по времени. Запрос "ошибки auth-сервиса за последний час" читает один непрерывный блок, а не сканирует весь массив.

SQL
CREATE TABLE logs
(
    timestamp DateTime64(3),
    service_name LowCardinality(String),
    level LowCardinality(String),
    message String,
    trace_id String,
    span_id String,
    attrs JSON -- тип JSON для произвольных атрибутов
)
ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(timestamp)
ORDER BY (service_name, timestamp)
TTL timestamp + INTERVAL 30 DAY
SETTINGS index_granularity = 8192;

PARTITION BY toYYYYMMDD(timestamp) - не декорация. Без него TTL работает неэффективно (ClickHouse удаляет данные по партициям), DROP PARTITION невозможен, и мержи тяжелее. Для логов с ретеншном в дни - партиция по дню. Для месяцев - toYYYYMM(timestamp).

Skip indexes. Для каждого гранула (8192 строк по умолчанию) хранится мини-статистика: min/max для числовых полей, bloom filter для строк, tokenbf для полнотекста. Движок пропускает гранулы, которые гарантированно не содержат нужных данных.

SQL
-- bloom filter для trace_id: пропускает гранулы без нужного trace
ALTER TABLE logs ADD INDEX idx_trace trace_id TYPE bloom_filter GRANULARITY 4;

-- tokenbf для полнотекстового поиска по message
ALTER TABLE logs ADD INDEX idx_msg message TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4;

Компрессия однотипных данных. LowCardinality(String) для полей с малым числом уникальных значений хранит словарь вместо полных строк. ZSTD сжимает колонку с миллионами INFO до ничтожного размера. 600 байт лога в ES -> 60 байт в ClickHouse.

Запрос по миллиарду строк:

SQL
-- Топ сервисов по количеству ошибок за сутки
SELECT
    service_name,
    count() AS errors,
    uniqExact(trace_id) AS affected_traces
FROM logs
WHERE level = 'error'
  AND timestamp >= now() - INTERVAL 1 DAY
GROUP BY service_name
ORDER BY errors DESC
LIMIT 20;

В production-миграциях такие запросы отрабатывают за сотни миллисекунд на ClickHouse против секунд на ES.

Запрос читает только нужные колонки. Остальные не трогает

Боль миграции: SQL вместо KQL и строгая схема

Кейсы выше - истории успеха. Вот о чём они не рассказывают.

Пользователи привыкли к Kibana

В Kibana разработчик пишет в поисковой строке:

Text
error AND "payment failed" AND service: auth

Просто, понятно. В ClickHouse тот же запрос:

SQL
SELECT timestamp, service_name, message
FROM logs
WHERE level = 'error'
  AND message LIKE '%payment failed%'
  AND service_name = 'auth'
ORDER BY timestamp DESC
LIMIT 100

Для инженеров, живущих в SQL - разницы нет. Для продуктовых команд, QA и саппорта - это стена. Grafana предлагает LogQL (похож на PromQL), но это тоже не KQL.

Как решают: Uber построили QueryBridge - прослойку, транслирующую Kibana-запросы в SQL. Quesma от Hydrolix делает то же. Другой путь - обучение и привыкание к SQL в Grafana Explore. Занимает недели, часть людей будет недовольна.

Schema-less vs строгая схема

Elasticsearch съест любой JSON через dynamic mapping. Кинул документ - он сам разберётся с типами.

ClickHouse требует строгую схему колонок. Тип JSON есть, но с нюансами производительности на больших объёмах. На практике нужен пайплайн нормализации:

TOML
# vector.toml - парсинг JSON логов и раскладка по колонкам
[transforms.parse_logs]
type = "remap"
inputs = ["kubernetes_source"]
source = '''
  structured, err = parse_json(.message)
  if err != null {
    .parse_error = true
    .raw_message = .message
    return
  }
  .timestamp = structured.timestamp
  .service_name = structured.service
  .level = structured.level
  .message = structured.msg
  .trace_id = structured.trace_id ?? ""
  .span_id = structured.span_id ?? ""
'''

[sinks.clickhouse]
type = "clickhouse"
inputs = ["parse_logs"]
endpoint = "http://clickhouse:8123"
database = "logs"
table = "app_logs"

Если у вас 50 микросервисов и каждый логирует в своём формате, нормализация становится отдельным проектом.

Kafka - не "буфер", а отдельная система

Типовой стек замены: Vector -> Kafka -> ClickHouse -> Grafana. На практике Kafka - это ZooKeeper (или KRaft), брокеры, топики, consumer groups, мониторинг лага, ротация сегментов. Полноценная инфраструктура.

Классический ELK - три компонента, один вендор. Elastic Agent + Fleet собирают логи, шлют в ES, Kibana показывает. Всё интегрировано.

Vector + Kafka + ClickHouse + Grafana - четыре независимых проекта с разными циклами релизов и точками отказа. Заменить один самодостаточный кластер на зоопарк из четырёх систем - усложнение в разы.

ELK: один вендор. ClickHouse stack: четыре независимых проекта

Альтернативы полновесной Kafka:

  • NATS JetStream - один бинарник, минимальная конфигурация, без ZooKeeper/KRaft.
  • Redpanda - Kafka-compatible API без JVM. C++ движок.
  • Vector -> ClickHouse напрямую - без буфера. Работает для потоков до нескольких GB/s.

Операционная сложность ClickHouse

MergeTree - мощный движок, но требует понимания. Типичные грабли новичков:

  • "Too many parts". Партиционирование по часу вместо дня на высоком потоке - получаешь тысячи партов, мержи не успевают, запросы деградируют. Дебаг занимает дни.
  • Keeper под нагрузкой. ClickHouse Keeper (встроенный аналог ZooKeeper на Raft) нужен для ReplicatedMergeTree. Под нагрузкой Keeper начинает отставать, реплики расходятся. Обнаруживаешь это когда запросы начинают возвращать разные данные с разных реплик.
  • TTL без PARTITION BY работает неэффективно. ClickHouse удаляет по партициям, и если партиции не совпадают с TTL-окном, старые данные висят.
  • Distributed DDL - ALTER TABLE на кластере из десятков нод может зависнуть если одна нода недоступна.

У Elasticsearch есть ILM из коробки: hot-warm-cold, автоматическая ротация, удаление по возрасту. У ClickHouse это ручная настройка или ClickHouse Cloud за деньги.

Полнотекстовый поиск: ES впереди

ClickHouse развивал полнотекстовые индексы несколько лет. Начиная с 2026 года они production-ready: нативные inverted indexes с block-based dictionary layout и roaring bitmaps. Для grep-подобных запросов по логам хватает.

Для NLP-ранжирования, fuzzy search, автодополнения - Elasticsearch впереди. Когда ищешь причину инцидента в 3 часа ночи, удобство поиска стоит денег.

Альтернативы: сравнительная таблица

Grafana Loki OpenSearch ClickHouse Quickwit Hydrolix
Архитектура Label-index + S3 Inverted index (форк ES) Колоночная, MergeTree Inverted index + S3 Колоночная + object storage
Объёмы До сотен GB/день Как ES Петабайты До TB/день Петабайты
Операционная сложность Низкая Средняя (как ES) Высокая Средняя Низкая (managed)
Стоимость входа Бесплатный, минимум инфры Бесплатный, инфра как ES Бесплатный, но нужна экспертиза Бесплатный Платный
Полнотекстовый поиск Нет (grep) Да (как ES) Базовый Да Через Quesma
Совместимость с ELK Нет Высокая Нет Частичная Через Quesma (Kibana)
Зрелость Зрелый Зрелый Зрелый Растущий Растущий
Когда выбирать Мелкие/средние команды, S3-бюджет Уход от лицензии Elastic Масштаб + экспертиза Поиск по логам поверх S3 Масштаб без операционки

Стоимость: считаем честно

Infra cost - не TCO:

  • Миграция. Contentsquare - 6 месяцев. Uber - постепенно через feature flags.
  • Люди. ClickHouse, Kafka, Vector - три разных компетенции. ES-команда закрывает всё одним стеком.
  • Обучение. SQL вместо KQL, Grafana вместо Kibana.
  • Нормализация. Пайплайн раскладки JSON по колонкам - отдельный проект.

Для контекста: self-hosted vs SaaS. Команда ClickHouse Cloud (проект LogHouse) описала платформу на 19 PiB: $23.76 за TiB/месяц self-hosted CH против ~$4,812 за TiB/месяц у Datadog. Это не сравнение с Elasticsearch - это иллюстрация разрыва между self-hosted и SaaS-observability.

Roadmap для тех, кто решился

  1. Dual-write. Два sink'а в Vector: один в ES, один в ClickHouse. Тот же поток данных, две копии.
TOML
# vector.toml - dual write
[sinks.elasticsearch]
type = "elasticsearch"
inputs = ["parse_logs"]
endpoints = ["https://es-cluster:9200"]
# + auth (api_key или strategy = "basic"), TLS по необходимости

[sinks.clickhouse]
type = "clickhouse"
inputs = ["parse_logs"]
endpoint = "http://clickhouse:8123"
database = "logs"
table = "app_logs"
Dual-write: один поток данных, две копии. Откат бесплатный
  1. Нормализуйте данные. Схема таблиц, ORDER BY ключ, PARTITION BY, Vector конфиг с обработкой ошибок. Самый трудоёмкий этап.
  2. Мигрируйте дашборды. 90-95% Kibana-дашбордов конвертируются в Grafana.
  3. Обучите людей. SQL вместо KQL, Grafana вместо Kibana.
  4. Переключайте чтение. Feature flags, постепенно. Go/no-go метрики: latency P99 на одинаковых запросах, полнота данных (count по часам совпадает), совпадение дашбордов. И самое коварное: поиск по message. LIKE '%pattern%' в ClickHouse и full-text query в ES могут давать разные результаты из-за токенизации и стемминга. Проверяйте на реальных инцидентах.
  5. Откат. Dual-write даёт бесплатный откат: переключаете чтение обратно на ES.
  6. Выключайте ES. После 2-4 недель стабильной работы на ClickHouse.
  7. Время: 3-6 месяцев для средней команды, до года для крупной.

Кому что подходит

Выбор по объёму: от Loki до Hydrolix

Не трогайте ES:

  • Полнотекстовый поиск (каталог, документация, CMS)
  • Объёмы умеренные, ретеншн устраивает
  • Logsdb index mode закрывает проблему с хранилищем
  • Пользователи привыкли к Kibana и KQL
  • Команда знает ES, нет боли с операционкой

До 100 GB/день:

  • Grafana Loki - минимум операционки, S3-хранение
  • OpenSearch - если проблема в лицензии Elastic

100 GB - 5 TB/день (серая зона):

Тут нет универсального ответа. Критерии выбора:

  • Kafka уже есть? ClickHouse становится проще: один новый компонент вместо двух.
  • Команда знает SQL? ClickHouse. Не знает - Loki или OpenSearch.
  • Нужен full-text поиск по логам? Оставайтесь на ES/OpenSearch или смотрите Quickwit.
  • Бюджет на обучение? ClickHouse требует 2-4 недели ramp-up для инженера. Loki - дни.
  • Ретеншн критичен? ClickHouse даст месяцы-годы на том же железе. ES - недели.
  • Команда < 5 человек? При небольших объёмах (до десятков GB/день) - Loki. При сотнях GB+ - ClickHouse Cloud. Самостоятельная эксплуатация CH кластера для маленькой команды - overhead.

Больше 5 TB/день:

  • ClickHouse - если есть экспертиза или бюджет на CH Cloud
  • Hydrolix - managed с Kibana-совместимостью через Quesma
  • Quickwit - поиск поверх S3

Итого

ELK не устарел. Elastic активно догоняет: Logsdb закрывает 50-65% проблемы с хранилищем, Serverless решает масштабирование, ES|QL улучшает аналитику. Но монополия закончилась.

В production-миграциях ClickHouse показывает экономию на инфраструктуре в разы, кратное ускорение запросов и рост ретеншна. Но за это платишь зоопарком компонентов, SQL вместо KQL, строгой схемой и переучиванием людей.

Правильный ответ зависит не от бенчмарков, а от вашей команды, масштаба и готовности к сложности. Впервые за 10 лет у инженеров есть настоящий выбор - и это хорошо.


Источники: Elastic Logsdb, Trip.com Migration, Uber Logging Platform, Didi Migration, Contentsquare, Ви.Tech на Хабре, 19 PiB LogHouse

Footnotes

  1. Вендорский бенчмарк ClickHouse Inc. (AWS c6a.8xlarge, 32 CPU, 64 GB RAM, до Logsdb): storage 12-19x меньше, агрегации 5x, фильтрация 6x. На 100B строк (одна машина, 64 GB RAM) ES не смог загрузить датасет. Источник. Бенчмарк GreptimeDB (тоже конкурент ES): CPU 6x меньше, ingestion 12.5x быстрее. Источник. Академических сравнений нет.