Технологии и Инфраструктура → Elasticsearch и полнотекстовый поиск
Elasticsearch и полнотекстовый поиск
1) Роль Elasticsearch
Elasticsearch (ES) — распределенная поисковая и аналитическая система на базе инвертированных индексов и колоночных структур для агрегаций. Она дает:- Полнотекст: релевантность (BM25), морфология, fuzzy/typo tolerant.
- Фасеты и агрегации: быстрые срезы по атрибутам.
- Гибридный поиск: BM25 + векторные kNN (семантика).
- Скорость разработки: Query DSL, ingest pipelines, богатая экосистема.
Для iGaming/финтех: поиск игр/провайдеров, промо и правил, быстрореагирующие фасеты (провайдер, волатильность, RTP, язык), поиск по KYC/AML журналам, разбор логов и алертов.
2) Модель данных и маппинги
2.1 Индекс и типы полей
`text` (анализируемое поле) — для полнотекста.
`keyword` — точные значения/агрегации/сортировка.
`date`, `long/double`, `boolean`, `ip`, `geo_point`.
`nested` — массивы объектов с корректной корреляцией полей.
`dense_vector` — векторные представления (эмбеддинги).
2.2 Мультиполевая стратегия
Храните поле в нескольких видах: `name.text` (анализируемое), `name.raw` (keyword), `name.ngram` (для автодополнения).
json
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ru_morph",
"fields": {
"raw": { "type": "keyword", "ignore_above": 256 },
"ngram": { "type": "text", "analyzer": "edge_ngram_2_20" }
}
},
"provider": { "type": "keyword" },
"tags": { "type": "keyword" },
"rtp": { "type": "float" },
"released_at": { "type": "date" },
"lang": { "type": "keyword" },
"embedding": { "type": "dense_vector", "dims": 384, "index": true, "similarity": "cosine" }
}
},
"settings": {
"analysis": {
"filter": {
"ru_stop": { "type": "stop", "stopwords": "_russian_" },
"ru_stemmer": { "type": "stemmer", "language": "russian" },
"syn_ru": { "type": "synonym", "lenient": true, "synonyms": [
"слот,игровой автомат => слот",
"джекпот,суперприз => джекпот"
] }
},
"analyzer": {
"ru_morph": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "ru_stop", "ru_stemmer", "syn_ru"]
},
"edge_ngram_2_20": {
"type": "custom",
"tokenizer": "edge_ngram",
"filter": ["lowercase"],
"char_filter": [],
"tokenizer": "edge_ngram"
}
},
"tokenizer": {
"edge_ngram": { "type": "edge_ngram", "min_gram": 2, "max_gram": 20 }
}
}
}
}
2.3 Nested для фасетов
Атрибуты вида `features: [{name, value}]` оформляйте `nested`, иначе фасеты дадут ложные совпадения.
3) Релевантность: BM25, буст и гибрид
3.1 Классика (BM25)
Комбинируйте поля с весами (title^4, tags^2, description).
Используйте `minimum_should_match` для контроля «шумных» совпадений.
3.2 Векторы (kNN) + BM25 (rerank)
Эмбеддинги (например, 384–768) в `dense_vector`.
Сначала kNN по вектору (top 200–500), затем rescore BM25 + бизнес-бусты (новизна, RTP, лицензия региона).
json
{
"knn": {
"field": "embedding",
"query_vector": [/... /],
"k": 400, "num_candidates": 2000
},
"query": {
"bool": {
"should": [
{ "multi_match": {
"query": "египетские слоты джекпот",
"fields": ["title^4","tags^2","description"],
"type": "best_fields",
"minimum_should_match": "60%"
}}
],
"filter": [
{ "term": { "region": "TR" }},
{ "range": { "rtp": { "gte": 94.0 }}}
]
}
},
"rescore": {
"window_size": 400,
"query": {
"rescore_query": {
"function_score": {
"query": { "match_all": {} },
"boost_mode": "sum",
"functions": [
{ "gauss": { "released_at": { "scale": "180d", "offset": "30d", "decay": 0.5 } } },
{ "field_value_factor": { "field": "popularity", "factor": 0.2, "modifier": "log1p" } }
]
}
}
}
},
"highlight": { "fields": { "title": {}, "description": {} } }
}
4) Автодополнение и подсказки
Подходы:- Edge N-gram на подполе `title.ngram` (быстро, просто).
- Completion suggesters (`completion` поле) — быстрые подсказки, но отдельный путь индексации.
- Search-as-you-type — объединяет токенизацию для начала слова и словосочетаний.
json
{ "suggest": { "game-suggest": { "prefix": "book o", "completion": { "field": "title_suggest", "fuzzy": { "fuzziness": 1 }}}}}
5) Синонимы, опечатки и мультиязычие
Синонимы: загружайте файл/список через `synonym` фильтр; разделяйте домены (казино/спорт).
Опечатки: `fuzziness: AUTO` в `multi_match`, ограничивайте длиной и полями. Для подсказок — `fuzzy` режиме completion.
- Индекс-per-локаль (ru/en/tr/pt-BR) или многоанализаторная схема: `title_ru`, `title_en`.
- Разные analyzers: `russian`, `english`, `turkish`, `portuguese`.
- Перекладывайте язык в ключ маршрутизации (routing), чтобы держать «горячие» локали ближе к пользователю.
6) Фильтры, фасеты и агрегации
Для фасетов используйте `keyword` и `nested` агрегации.
Избегайте кардинальных полей (уникальные ID) в агрегациях — вынесите в `runtime fields` или предварительные витрины.
json
{
"size": 20,
"aggs": {
"by_provider": { "terms": { "field": "provider", "size": 20 } },
"by_volatility": { "terms": { "field": "volatility" } },
"rtp_hist": { "histogram": { "field": "rtp", "interval": 1 } }
}
}
7) Ввод данных и очистка текста
Ingest pipelines: нормализация, извлечение полей, гео-энкодинг, удаление HTML.
Attachment/ingest-ocr (по необходимости): индексация PDF/изображений (внимательно к PII).
Лемматизация: через анализаторы или внешние пайплайны (precompute токены).
8) Шарды, реплики и ILM
8.1 Размеры и шардинг
Меньше шардов — лучше. Цель: 10–50 ГБ на шард для смешанных нагрузок.
Начните с `number_of_shards: 1–3`, масштабируйте по факту. Реплики — минимум 1 в проде.
8.2 ILM (Lifecycle)
hot → warm → cold → delete для логов/истории промо.
Сжатие (force merge) для «холодных» сегментов.
Для каталогов и поиска по продукту — «бессрочный» hot с периодической оптимизацией.
8.3 Алгоритм миграций без даунтайма
Новый индекс `games_v2` → alias `games` переключается после `reindex` и backfill. Deprecated поля — убирайте постепенно.
9) Снапшоты, DR и обновления
Snapshots в объектное хранилище (S3/GCS), расписание и проверка восстановления.
Роллинг-обновления нод, проверка shard allocation awareness (по зонам).
Планы DR: кросс-регион репликация (CCR) для критичных индексов (справочники, каталоги).
10) Безопасность и PII
TLS/mTLS между клиентом и кластером.
RBAC: роли на индекс/операции; Dev/Stage/Prod — раздельно.
PII/PCI: не индексируйте поля с персональными данными без необходимости; используйте ingest-маскирование.
Right to be forgotten: храните ссылки на документы для удаления по user_id; soft-delete + reindex/анонализация.
11) Наблюдаемость и SLO поиска
Метрики:- P50/P95/P99 latency на query, ошибки 4xx/5xx.
- Cache hit (query cache / shard request cache).
- Heap usage, GC паузы, segment merges, threadpools (search/write).
- Hot shards/hot nodes, rejections.
- KNN: `graph_hits`, `search_k`, latency, recall@k.
- Поиск игр: P95 ≤ 200 мс, ошибок < 0.5% в 30-мин окне.
- Подсказки: P95 ≤ 80 мс.
- KNN гибрид: P95 ≤ 350 мс для top-20 результатов.
12) FinOps: стоимость и производительность
Размер индекса: экономьте токенизацией, отключайте ненужные `fielddata`, используйте `doc_values` только там, где надо.
Сегменты: планируйте merge-политику, не допускайте «дробления».
KNN дороже по RAM/CPU: ограничьте dims, `num_candidates`, pre-filter на BM25.
Горячие поля в RAM: мониторьте field data/heap; уводите «тяжелые» агрегации в отдельные индексы.
13) Примеры запросов
13.1 Мультиполевая полнотекстовая с бустом
json
{
"query": {
"multi_match": {
"query": "book of",
"fields": ["title^4","title.ngram^2","tags^2","description"]
}
},
"sort": ["_score", { "released_at": "desc" }]
}
13.2 Фильтры + фасеты
json
{
"query": {
"bool": {
"must": [{ "match": { "title": "египет" }}],
"filter": [
{ "terms": { "provider": ["Novomatic","PragmaticPlay"]}},
{ "range": { "rtp": { "gte": 95 }}}
]
}
},
"aggs": {
"by_provider": { "terms": { "field": "provider" } },
"by_year": { "date_histogram": { "field": "released_at", "calendar_interval": "year" } }
}
}
13.3 Nested фильтрация атрибутов
json
{
"query": {
"nested": {
"path": "features",
"query": { "bool": {
"must": [
{ "term": { "features.name": "volatility" }},
{ "term": { "features.value": "high" }}
]
}}
}
}
}
13.4 Поиск по логам (ECS) с хайлайтом
json
{
"query": {
"bool": {
"must": [{ "match_phrase": { "message": "payment declined" }}],
"filter": [
{ "term": { "service.name": "payments" }},
{ "range": { "@timestamp": { "gte": "now-1h" }}}
]
}
},
"highlight": { "fields": { "message": { "number_of_fragments": 0 } } }
}
14) Мульти-тенант и изоляция
Индекс на тенанта (лучше) или поле `tenant_id` + фильтр ACL (дороже на агрегациях).
Routing по `tenant_id` для локализации шардов.
Ограничивайте запросы тенанта лимитами/таймаутами, `query.phase` guard-rails.
15) Чек-лист внедрения
1. Схема: `text/keyword/nested` + мультиполя, `dense_vector` при необходимости.
2. Анализаторы per-язык, синонимы, edge-ngram для автодополнения.
3. Релевантность: BM25 бусты + гибрид kNN→rescore.
4. Фасеты: keyword/nested, агрегации только по «здоровым» полям.
5. Индексация: ingest pipelines (нормализация), батч-загрузка.
6. Шардирование: начните с малого, alias для переездов, ILM для «длинных» логов.
7. DR: snapshots расписание, проверка восстановления, CCR для критичных индексов.
8. Безопасность: TLS, RBAC, маскирование PII, политика удаления.
9. Наблюдаемость: latency, heap/GC, cache hit, hot shards, rejections.
10. FinOps: размер индекса, kNN параметризация, отключение лишних `doc_values/fielddata`.
16) Анти-паттерны
Один индекс «на все»: разные домены (каталог, логи, транзакции) требуют разных настроек.
Бездумное `fuzziness: AUTO` на всех полях → медленно и шумно.
Синонимы «съедают смысл»: не разделять домены словарей.
Без nested там, где нужны связки полей → ложные фасеты.
Слишком много шардов (по одному на документ) — накладные расходы на cluster state.
Неиспользование alias при миграциях — простои и «битые» ссылки.
Индексация PII «как есть» — регуляторные риски и дорогие реиндексы.
17) Контекст iGaming/финтех: быстрые рецепты
Поиск игр: `multi_match` с бустом `title^4`, `tags^2`, фасеты по провайдеру/волатильности, фильтры по региону/валюте, гибрид с векторами для «тематики» (например, «египет», «fruit classic»).
Промо/бонусы: синонимы («фриспины», «free spins»), дата-фильтры `active_from/active_to`, подсказки через completion.
KYC/AML журналы: ECS-схема, полнотекст по `message`, агрегации по `rule_name`, `country`, аномалии по `@timestamp` гистограмме.
Справочник провайдеров: keyword поля для фасетов и сортировок; текстовые описания — `text` с морфологией.
Регуляторные страницы: мультиязычные поля, `search_as_you_type` для мягких подсказок.
Итог
Эффективный поиск на Elasticsearch — это не только «включить BM25»: это правильные анализаторы и маппинги, мультиполя и nested, гибрид BM25+векторы, аккуратные фасеты и агрегации, дисциплина шардирования и ILM, четкие SLO и наблюдаемость, а также безопасность и FinOps. С этими принципами ваш поиск будет быстрым, релевантным и предсказуемым — и выдержит пики трафика продуктовой платформы.