Фильтрация и полнотекстовый поиск
1) Зачем нужен поисковый слой
Фильтрация и полнотекстовый поиск (FTS) обеспечивают быстрый доступ к данным «по смыслу», а не только по первичным ключам. Правильно спроектированный слой поиска сочетает:- Строгие фильтры (категории, даты, цены, права доступа)
- Полнотекст (лексический матч и ранжирование)
- Фасеты (агрегаты для навигации)
- Гибридное ранжирование (BM25/TF-IDF + векторные эмбеддинги)
- Надежные протоколы (пагинация курсорами, TTL токенов, кросс-шардинг)
2) Архитектурная картина
Компоненты:1. Ingest/ETL → нормализация, дедупликация, обогащение, построение полей для индекса.
2. Индексатор → обратный индекс (лексемы → документы), колоночные структуры, векторный индекс (HNSW/IVF-PQ).
3. Query Layer → парсер запросов, применение фильтров/прав доступа, планировщик шардов, k-way merge.
4. Ranker → BM25 + (опц.) LTR/Neural re-rank.
5. Serving → кэш, курсоры, фасеты, хайлайты, автокомплит.
6. Наблюдаемость → метрики латентности, качества, A/B-эксперименты.
3) Модель данных и индекса
3.1 Поля и анализаторы
Типы: keyword (ровное совпадение), text (анализируется), numeric/date/geo, vector.
Анализаторы: токенизация, нормализация (lowercase, Unicode NFKC), фильтры (стоп-слова, стемминг/лемматизация).
Мультиязычность: per-field анализаторы (ru, uk, en); ICU-анализ; транслитерация; учет диакритики.
3.2 Обратный индекс (sparse)
Структура: term → posting list (docID, term freq, позиции).
Ранжирование: BM25 (или классический TF-IDF) с полевыми бустами.
3.3 Векторный индекс (dense)
Эмбеддинги текста (например, 384–1024-мерные).
ANN-структуры: HNSW, IVF-PQ, Flat (для малых наборов).
Косинусная близость/inner product; калибровка с BM25 (гибрид).
3.4 Фасеты и агрегаты
Прекомпьют/колоночное хранение значений для быстрых count-ов.
Иерархические фасеты (категория/подкатегория).
Диапазоны (ценовые бины, даты).
4) Запросы: фильтры + полнотекст + сортировка
4.1 Контракты API (REST)
Запрос:
GET /v1/search? q = classic slots & limit = 20 & cursor =... & sort = score: desc, created _ at: desc
&filters=brand:("NetEnt","EGT"); price:[10 TO 50];published_at:[2024-01-01 TO ]
&facets=brand,year,price:range(0,10,20,50,100)
Ответ (фрагмент):
json
{
"items": [ { "id":"...", "title":"...", "score": 12. 3, "highlight": { "content": ["..."] } } ],
"facets": { "brand": [{"value":"NetEnt","count":123},...] },
"page": { "limit":20, "has_more":true, "next_cursor":"opaque-token" }
}
4.2 GraphQL (упрощенно)
graphql type Query {
search(query: String!, filter: SearchFilter, first: Int, after: String, sort: [Sort!]): SearchConnection!
}
4.3 gRPC
proto message SearchRequest {
string query = 1;
map<string,string> filters = 2;
int32 page_size = 3;
string page_token = 4; // курсор repeated string facets = 5;
}
5) Обработка естественного языка (NLP)
Токенизация/нормализация: Unicode-безопасно, учет дефисов/апострофов.
Стоп-слова: настроечные списки по языкам.
Стемминг vs лемматизация: для ru/uk лучше лемматизация (качество > скорость).
Синонимы: двунаправленные/направленные словари; версии словарей с TTL.
Опечатки (fuzzy): Damerau-Levenshtein с ограничением расстояния и бустами точного совпадения.
N-грамы/edge-ngrams: для автокомплита и подсказок.
Транслитерация: «shch» ↔ «щ», «kiev/kyiv» — правила соответствий.
6) Релевантность и ранжирование
6.1 Базовый лексический скоринг
BM25 с настройкой `k1`, `b` по коллекции.
Бусты по полям (title^3, tags^1.5, body^1).
Свежесть: `score += freshness_boost(decay(created_at))`.
6.2 Поведенческие сигналы
Click-through rate, dwell time, сохранения в избранное (с анти-позиционным байасом).
Дедупликация: склейка документов с ~идентичным содержимым (MinHash/SimHash).
6.3 Learning-to-Rank (LTR)
Фичи: BM25 по полям, длина, свежесть, популярность, совпадение по фразе, позиционный скор.
Модели: LambdaMART/XGBoost; офлайн-метрики NDCG@k, MAP, Precision@k; онлайн A/B.
6.4 Нейро-переранжирование
Двухэтапка: recall (BM25/ANN) → top-N (например, 200) → cross-encoder rerank.
Учет стоимости: тайм-бюджет, fallback без нейро-этапа при нагрузке.
6.5 Гибридный поиск (sparse + dense)
Либо fusion (нормализация скоров и сумма), либо multi-stage (dense как rerank).
Важна калибровка: min-max/ z-score/ quantile mapping.
7) Фильтрация, фасеты и доступ
7.1 Фильтры
Операторы: `=`, `IN`, диапазоны, префиксы, geo-bounding box/geo-distance.
Комбинации: `AND` по фильтрам, `OR` внутри множества значений (brand IN …).
Типобезопасность: числовые поля не анализируются как текст.
7.2 Фасеты
Дешевые count-ы по предвычисленным структурам.
«Примененные» фасеты показывают оставшиеся варианты (post-filter faceting).
7.3 Доступ/мульти-тенантность
Фильтры безопасности интегрируются до ранжирования (pre-filter).
ABAC/RBAC поля в документе (`tenant_id`, `visibility`, `acl`).
Токен запроса подписан; при мульти-тенанте — автоматический `tenant_id` фильтр.
8) Пагинация, курсоры и консистентность
Пагинация seek-курсором по `(score, tie-breaker)` или по `(created_at, id)` при сортировке по времени.
Непрозрачные `page_token` c HMAC и TTL.
Консистентность: near-real-time (NRT) индекса: задержка 0.5–2 с между записью и видимостью. Документируйте это в SLA.
Кросс-шард: локальный поиск → k-way merge по глобальному порядку, per-shard курсоры в токене.
9) Автодополнение и подсказки
Suggesters: prefix-trie / edge-ngrams по полю `title`.
Popular queries: лог кликов → подсказки по популярности + персонализация (сегменты).
Spell-as-you-type: быстрый fuzzy-поиск с ограничением расстояния `<=1`.
GET /v1/suggest? q=kaz&limit=8&locale=ru
→ ["casino," "casual games,..."]
10) Хайлайты и сниппеты
Позиционный индекс → извлечение фраз с совпадениями.
Экранирование HTML, лимит длины, объединение соседних фрагментов.
Ранжирование сниппетов по плотности релевантных терминов.
11) Производительность, кэш и SLO
Индексы: горячие сегменты в памяти; компрессия postings; doc values для фасетов.
Кэш: L1 (процесс), L2 (Redis), кэш фасетов/агрегатов; инвалидировать по версии индекса.
SLO: P95 < 150–200 мс при `k<=20`, P99 < 500 мс; доступность 99.9%.
Backpressure: уменьшение `k`, отключение нейро-этапа при перегрузе.
Rate limiting на ключ API/пользователя/тенанта.
12) Наблюдаемость и метрики качества
Техметрики:- `search_latency_ms` (P50/P95/P99), `qps`, `timeouts`, `error_rate`
- `cache_hit_ratio`, `facet_cache_hit`, `rerank_share`
- `shard_fanout`, `merge_time_ms`, `ann_recall@k`
- NDCG@k, MAP, MRR, Recall@k, Precision@k на размеченных выборках.
- CTR@k, sCTR (satisfied clicks), dwell time, отказ (pogostick rate).
A/B: фиксируйте «guardrail» метрики (латентность, ошибки) + целевые (NDCG proxy).
13) Тестирование
Relevance unit tests: проверка ожидаемых матчей по ключевым запросам.
Property-based: устойчивость к опечаткам/синонимам/языкам.
Пагинация: отсутствие дубликатов на границе страниц (seek-контракты).
Безопасность: фильтры доступа применяются всегда (даже на faset-count).
Регрессы словарей: версионирование синонимов и fuzzy-правил.
14) Безопасность и приватность
Поля с PII не индексировать как text; хранить отдельно/шифровать.
Минимизировать хранимые исходные тексты (store=false, только поля для сниппетов).
Query privacy: не логировать сырые запросы с PII; анонимизация/хэширование.
Мульти-тенант: строгая изоляция индексов или обязательный `tenant_id` фильтр.
15) Миграции и совместимость
Версионирование схемы индекса (v1→v2) с двойной записью и постепенным переключением.
Совместимость анализаторов: держать старые цепочки пока не переиндексируете.
Ротация словарей синонимов/стоп-слов: `version`, `activated_at`, rollback.
16) Практические рецепты
16.1 Классический лексический поиск (BM25)
Поля: `title^3`, `tags^2`, `body^1`.
Анализаторы: язык-специфичные + лемматизация.
Fuzzy для коротких запросов (`<=3` токенов), `fuzziness=1`.
16.2 Гибрид sparse+dense
1. ANN-поиск по эмбеддингу запроса (k=200)
2. Объединить с top-200 BM25
3. Калибровать и слить (Reciprocal Rank Fusion)
4. Взять top-N (N=20), опционально — rerank cross-encoder-ом при достаточном бюджете.
16.3 Фасетная навигация каталога
Жесткий pre-filter по правам/тенанту
Пост-фильтровые фасеты (counts с учетом активных фильтров)
Сортировка: по релевантности или по бизнес-полю (цена/новизна)
17) Примеры запросов (псевдо-DSL)
Фильтры и сортировка:json
{
"query": "live casino,"
"filters": {
"country": ["EE","LV","LT"],
"license": ["MGA","UKGC"],
"launched_at": {"gte": "2023-01-01"}
},
"sort": ["_score:desc","launched_at:desc"],
"facets": ["country","license"],
"page": {"limit": 20, "cursor": "opaque"}
}
Геопоиск:
json
{
"query": "casino",
"geo": {"lat": 59. 437, "lon": 24. 753, "radius_km": 50}
}
Автокомплит:
json
{ "prefix": "evo", "field": "brand_suggest", "limit": 8 }
18) UX-паттерны
Чипы активных фильтров + «сбросить все».
Пустые результаты: показать «попробуйте…» (синонимы, убрать фильтр).
«Подсказки при нуле»: популярные запросы/категории.
Пагинация курсором (кнопка «Еще») и бесконечный скролл; фиксированный индикатор примененных фильтров.
Отдельные переключатели «учитывать опечатки», «точное совпадение фразы».
19) Частые ошибки и анти-паттерны
Отсутствие tie-breaker при сортировке → дубли/скачки.
Фасеты без учета активных фильтров → «ложные» counts.
Применение фильтров доступа после ранжирования.
Смешивание разных языков одним анализатором.
Глубокая пагинация OFFSET/LIMIT вместо seek-курсора.
Неограниченный fuzzy → взрыв по латентности.
20) Чеклист внедрения
1. Определите поля и их типы, назначьте анализаторы per-locale.
2. Спроектируйте обратный индекс + (опц.) векторный ANN.
3. Реализуйте парсер запросов и безопасные фильтры доступа (pre-filter).
4. Настройте BM25 и полевые бусты; подключите фасеты.
5. Введите курсоры (opaque, HMAC, TTL) и k-way merge по шардам.
6. Добавьте автокомплит, хайлайты, безопасное экранирование.
7. Метрики: латентность, NDCG@k, CTR; кэш L1/L2.
8. A/B-фреймворк для тюнинга релевантности.
9. Документируйте SLA: NRT-задержку, лимиты `limit`, гарантию консистентности.
10. План миграций: версии индекса, словарей и анализаторов.
Хорошо спроектированный слой фильтрации и полнотекстового поиска — это не только быстрый индекс, но и четкий протокольный контракт с курсорами, безопасностью, предсказуемым UX и измеримой релевантностью. Такой подход масштабируется от тысяч до миллиардов документов и поддерживает как классический лексический поиск, так и современные гибридные сценарии с нейросетевым ранжированием.