Информационный поиск
Distributed Search: Elasticsearch
Когда Github перевёл код-поиск на Elasticsearch в 2021 году, индекс составил 15 ТБ кода. Один узел вместил бы 0.3% этого объёма. Без шардирования Github Code Search не существовал бы.
- **Wikipedia Search:** 6.7 млн статей в Elasticsearch с 5 шардами и 1 репликой - поиск по всему корпусу за 50-200 мс
- **E-commerce каталоги:** Zalando использует Elasticsearch для поиска по 500+ тыс. товаров с real-time обновлением цен и остатков через агрегации
- **Log aggregation:** ELK Stack (Elasticsearch + Logstash + Kibana) - стандарт для сбора и анализа логов в enterprise, миллиарды событий в день
Шардирование индекса
Wikipedia в Elasticsearch содержит 6,7 млн статей - больше не влезет в один узел ни по памяти, ни по диску. Шардирование решает это разбивая индекс на N независимых шардов, каждый из которых - полноценный инвертированный индекс на отдельном узле. При поиске координатор рассылает запрос на все шарды параллельно и мержит результаты. Число шардов задаётся при создании индекса и не меняется без reindex.
Правило выбора числа шардов: целевой объём индекса / 20-40 ГБ на шард. Слишком мало шардов - узкое место при масштабировании; слишком много - overhead на координацию запросов и сборку мусора JVM. Оптимальный размер шарда для Elasticsearch: 10-50 ГБ.
Почему нельзя изменить число первичных шардов в существующем индексе Elasticsearch без reindex?
Репликация и отказоустойчивость
Каждый первичный шард в Elasticsearch имеет N реплик - синхронные копии на других узлах. При записи документа: (1) координатор роутит на первичный шард, (2) первичный записывает локально и параллельно реплицирует на все реплики, (3) только после подтверждения всех реплик возвращает ack клиенту. При падении узла с первичным шардом одна из реплик автоматически повышается до первичной.
Параметр wait_for_active_shards управляет балансом между latency и durability: 1 = ack только от первичного (быстро, риск потери при сбое), all = ждать все реплики (медленно, максимальная надёжность). По умолчанию = 1.
Что означает статус 'yellow' в Elasticsearch cluster health?
Роутинг и custom routing
По умолчанию Elasticsearch роутит документы равномерно: shard = hash(doc_id) % num_primary_shards. Запрос к индексу идёт на все шарды - scatter-gather. Custom routing позволяет гарантировать, что связанные документы попадут на один шард: тогда запрос идёт только на один шард вместо всех. Для multi-tenant систем это снижает latency в N раз, где N - число шардов.
Custom routing создаёт риск 'горячих шардов' (hot shards): если все документы одного tenant активно запрашиваются, один шард перегружен. Решение - routing_partition_count: документы tenant роутятся не на один шард, а на подмножество шардов.
Какое главное преимущество custom routing в Elasticsearch для multi-tenant систем?
Агрегации и аналитические запросы
Агрегации - аналог GROUP BY в SQL, но выполняемый параллельно на всех шардах. Каждый шард вычисляет частичный результат, координатор их мержит. Bucket aggregations группируют документы (terms, histogram, date_histogram). Metric aggregations вычисляют статистики (avg, sum, percentiles). Pipeline aggregations применяются к результатам других агрегаций.
Terms aggregation собирает топ-N значений по шардам с погрешностью: каждый шард возвращает свой топ-N, координатор мержит. Реальное топ-N по всему индексу может отличаться. Параметр shard_size увеличивает точность за счёт объёма передаваемых данных.
Elasticsearch агрегации точны как SQL GROUP BY
Terms aggregation возвращает приближённый топ-N из-за распределённой природы вычислений - каждый шард видит только свои данные
SQL GROUP BY выполняется на полных данных централизованно. В distributed aggregation каждый шард возвращает частичный результат; мерж частичных топ-N не гарантирует глобальный топ-N. Параметр shard_size увеличивает точность, но ценой трафика и памяти
Почему terms aggregation в Elasticsearch может возвращать неточный топ-N?
Ключевые идеи
- **Шардирование:** индекс разбивается на N шардов, число фиксировано при создании - формула hash(id) % N определяет шард документа
- **Репликация:** каждый первичный шард имеет реплики на других узлах; yellow = реплики недоступны, red = первичные шарды недоступны
- **Custom routing + агрегации:** routing ограничивает scatter-gather до одного шарда; terms aggregation приближённая - shard_size повышает точность
Связанные темы
Distributed Elasticsearch строится поверх базовых концепций поиска:
- Инвертированный индекс — Каждый шард Elasticsearch - это независимый Lucene индекс с собственными инвертированными списками
- Search Infrastructure at Scale — Tiering, caching и federation - следующий уровень масштабирования поверх базового кластера ES
Вопросы для размышления
- Число шардов нельзя изменить без reindex. Как бы спроектировали начальную конфигурацию для проекта, объём данных которого непредсказуем?
- Terms aggregation даёт приближённый результат. В каких бизнес-сценариях эта погрешность критична - и есть ли workarounds для получения точных результатов?
- Custom routing ускоряет запросы для конкретного tenant, но создаёт риск hot shards. Как бы сбалансировали эти трейдоффы для системы с 10 крупными tenant и 10 000 мелкими?