Фільтрація та повнотекстовий пошук
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 і вимірною релевантністю. Такий підхід масштабується від тисяч до мільярдів документів і підтримує як класичний лексичний пошук, так і сучасні гібридні сценарії з нейромережевим ранжируванням.