Поисковые движки
Поисковые движки — специализированные базы данных для полнотекстового поиска на основе инвертированного индекса (слово → список документов). Этапы: индексация (токенизация, нормализация, стемминг, удаление стоп-слов) и поиск с ранжированием (TF-IDF, BM25). Популярные системы: Elasticsearch (REST API, агрегации, Kibana, масштабирование), Solr, Typesense, Manticore. Типы запросов: match, bool, fuzzy (опечатки), wildcard, geo. Агрегации (группировки, гистограммы). Идеальные сценарии: поиск по сайту, логам (ELK), документам, геопоиск. Отличие от реляционных БД, настройка анализаторов, типичные ошибки.
Введение: Как Google находит за миллисекунды
Представьте, что вы ищете в интернете “как испечь хлеб”. Google просматривает миллиарды страниц и возвращает результат за доли секунды. Как это возможно? Неужели Google каждую миллисекунду сканирует весь интернет?
Конечно, нет. Вместо этого поисковые системы заранее строят специальную структуру данных — инвертированный индекс. Это похоже на алфавитный указатель в конце книги: слово “хлеб” встречается на страницах 10, 25, 103, и вы сразу переходите к ним, не листая всю книгу.
Поисковый движок (Search Engine) — это специализированная база данных для полнотекстового поиска. Она умеет находить документы по словам и фразам, ранжировать результаты по релевантности, поддерживать сложные поисковые запросы (AND, OR, NOT, поиск по фразе, поиск с ошибками, поиск по части слова).
Это не просто “еще один тип NoSQL”. Поисковые движки решают задачу, с которой плохо справляются реляционные базы: WHERE text LIKE '%word%' — медленно, не масштабируется, не ранжирует. Поисковые движки делают это быстро, на терабайтах данных, с учетом грамматики и синонимов.
Что такое поисковый движок
Поисковый движок (Search Engine / Search Database) — это система для индексации и поиска неструктурированных и полуструктурированных данных. Основные задачи:
- Индексация: построение инвертированного индекса по тексту
- Поиск: быстрый поиск слов и фраз
- Ранжирование: сортировка результатов по релевантности
- Аналитика: агрегации по результатам поиска (фасеты, гистограммы)
Ключевое отличие от реляционных БД
| Характеристика | Реляционная БД | Поисковый движок |
|---|---|---|
| Поиск по тексту | LIKE '%word%' — полное сканирование | Инвертированный индекс — мгновенно |
| Ранжирование | Нет (только ORDER BY) | Релевантность (TF-IDF, BM25) |
| Опечатки | Нет | Fuzzy search (расстояние Левенштейна) |
| Морфология | Нет | Стемминг, лемматизация |
| Синонимы | Нет | Да |
| Язык запросов | SQL | Специализированный (Query DSL) |
Как работает поисковый движок
Этап 1: Индексация (Indexing)
Исходный документ → Анализ → Инвертированный индекс
Документ: "Быстрая коричневая лиса прыгает через ленивую собаку"
Шаг 1: Токенизация (разбивка на слова)
["Быстрая", "коричневая", "лиса", "прыгает", "через", "ленивую", "собаку"]
Шаг 2: Нормализация (нижний регистр)
["быстрая", "коричневая", "лиса", "прыгает", "через", "ленивую", "собаку"]
Шаг 3: Стемминг/лемматизация (приведение к основе)
["быстр", "коричнев", "лис", "прыга", "через", "ленив", "собак"]
Шаг 4: Удаление стоп-слов (слишком частые)
["быстр", "коричнев", "лис", "прыга", "ленив", "собак"]
Шаг 5: Построение инвертированного индекса
"быстр" → [документ1, позиция1]
"коричнев" → [документ1, позиция2]
"лис" → [документ1, позиция3]
"прыга" → [документ1, позиция4]
"ленив" → [документ1, позиция6]
"собак" → [документ1, позиция7]Этап 2: Поиск (Searching)
Запрос пользователя → Анализ (те же шаги) → Поиск в индексе → Ранжирование
Запрос: "лиса собака"
Анализ: ["лис", "собак"]
Поиск в индексе:
"лис" → документ1
"собак" → документ1
Результат: документ1 (релевантность: 2 слова из 2)Ранжирование: Как сортируются результаты
TF-IDF (Term Frequency — Inverse Document Frequency)
Классический алгоритм ранжирования, используемый в Lucene (основа Elasticsearch).
- TF (Term Frequency): Как часто слово встречается в документе. Чем чаще, тем важнее.
- IDF (Inverse Document Frequency): Насколько слово редкое во всей коллекции. Редкие слова важнее.
TF = частота слова в документе
IDF = log(общее количество документов / количество документов со словом)
TF-IDF = TF * IDFПример:
- Слово “кот” встречается 5 раз в документе (TF=5) и есть в 10% документов (IDF=log(10)=1) → вес = 5
- Слово “и” встречается 100 раз (TF=100), но есть в 99% документов (IDF≈0.01) → вес ≈ 1
BM25 (Best Matching 25)
Современная версия TF-IDF, используется в Elasticsearch по умолчанию. Учитывает длину документа.
BM25 ≈ TF-IDF с насыщением (чтобы слишком частые слова не доминировали)Практическое ранжирование в Elasticsearch
// Поиск с кастомным ранжированием
GET /articles/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "хлеб", "boost": 3 } }, // заголовок важнее
{ "match": { "content": "хлеб", "boost": 1 } } // содержание
]
}
}
}Популярные поисковые движки
Elasticsearch
Самый популярный поисковый движок. Построен на Apache Lucene, имеет REST API, горизонтальное масштабирование.
// Индексация документа
POST /articles/_doc/1
{
"title": "Как испечь хлеб",
"content": "Для выпечки хлеба нужны мука, вода, дрожжи и соль...",
"author": "Иван",
"published_at": "2024-01-15"
}
// Поиск
GET /articles/_search
{
"query": {
"match": {
"content": "хлеб дрожжи"
}
},
"sort": [
{ "published_at": "desc" }
],
"aggs": {
"by_author": {
"terms": { "field": "author" }
}
}
}Характеристики Elasticsearch:
- Инвертированный индекс (Lucene)
- Горизонтальное масштабирование (шардирование)
- REST API (JSON over HTTP)
- Агрегации (фасеты, гистограммы, метрики)
- Поддержка 30+ языков
- Визуализация (Kibana)
Когда использовать:
- Поиск по логам (ELK Stack: Elasticsearch, Logstash, Kibana)
- Поиск по каталогу товаров
- Поиск по документам (статьи, база знаний)
- Аналитика логов (метрики, дашборды)
- Геопоиск (магазины рядом)
Apache Solr
Второй по популярности поисковый движок на Lucene. Более зрелый, чем Elasticsearch.
<!-- Solr запрос через HTTP -->
<select?q=хлеб+дрожжи&fq=author:Иван&sort=published_at desc&facet=true&facet.field=author/>Характеристики Solr:
- Lucene под капотом
- XML/JSON интерфейс
- Более богатые возможности настройки
- Зрелые функции (ZooKeeper для кластеризации)
Когда использовать:
- Большие проекты с устоявшейся инфраструктурой
- Требуется тонкая настройка (чем Elasticsearch)
- Уже используется Solr
Algolia / Typesense / Meilisearch
Современные API-first поисковые движки с упором на скорость и простоту.
// Typesense
const client = new Typesense.Client({
nodes: [{ host: 'localhost', port: '8108', protocol: 'http' }],
apiKey: 'xyz'
});
client.collections('products').documents().search({
q: 'iphone',
query_by: 'name,description',
sort_by: 'price:asc'
});Характеристики:
- Очень быстрые (в память)
- Простое API
- Автодополнение, опечатки “из коробки”
- Гибридное ранжирование
Когда использовать:
- Поиск на сайте (интернет-магазин, блог)
- Быстрый прототип
- Не хочется администрировать Elasticsearch
Sphinx / Manticore Search
Поисковые движки с фокусом на интеграцию с реляционными БД.
-- Manticore Search (SQL-подобный)
SELECT * FROM products WHERE MATCH('хлеб дрожжи') AND price < 500;Характеристики:
- SQL-интерфейс
- Интеграция с MySQL/PostgreSQL
- Легковесный
Когда использовать:
- Интеграция с существующей реляционной БД
- Легковесное решение
Инвертированный индекс в деталях
Структура инвертированного индекса
Словарь (Dictionary) Постинг-листы (Posting Lists)
"быстр" → [doc1:pos1, doc5:pos3, doc10:pos7]
"коричнев" → [doc1:pos2, doc8:pos1]
"лис" → [doc1:pos3, doc3:pos5, doc7:pos2]
"прыга" → [doc1:pos4, doc4:pos6]
"ленив" → [doc1:pos6, doc6:pos3]
"собак" → [doc1:pos7, doc2:pos4, doc9:pos5]Типы индексов
Индекс по словам (Term Index): Базовый. Слово → список документов.
Индекс с позициями (Positional Index): Слово → (документ, позиция). Позволяет искать фразы.
Индекс с частотами (Frequency Index): Слово → (документ, частота). Позволяет ранжировать по TF.
Индекс n-грамм (n-gram Index): Разбивает слова на части. Позволяет искать по части слова или с опечатками.
"хлеб" → 2-граммы: "хл", "ле", "еб"
Запрос "хлебный" → 2-граммы: "хл", "ле", "еб", "бн", "ны" → пересечениеАнализ текста (Text Analysis)
Компоненты анализатора
Tokenizer (Токенизатор): Разбивает текст на токены.
| Tokenizer | Пример |
|---|---|
standard | “Иван, Петр!” → [“Иван”, “Петр”] |
whitespace | “Иван Петр” → [“Иван”, “Петр”] |
letter | “Иван123” → [“Иван”] |
keyword | “Иван Петр” → [“Иван Петр”] (один токен) |
Token Filter (Фильтры): Преобразуют токены.
| Фильтр | Пример |
|---|---|
lowercase | “ИВАН” → “иван” |
stop (стоп-слова) | “и”, “в”, “на” → удалить |
stemmer (стемминг) | “бежал” → “беж” |
synonym (синонимы) | “iPhone” → “айфон” |
ngram (n-граммы) | “хлеб” → “хл”, “ле”, “еб” |
Пример настройки анализатора в Elasticsearch
PUT /my_index
{
"settings": {
"analysis": {
"analyzer": {
"russian_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "russian_stop", "russian_stemmer"]
}
},
"filter": {
"russian_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"russian_stemmer": {
"type": "stemmer",
"language": "russian"
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "russian_analyzer"
}
}
}
}Типы запросов
Match (полнотекстовый поиск)
// Простой поиск (AND по умолчанию)
{ "match": { "content": "хлеб дрожжи" } }
// OR
{ "match": { "content": { "query": "хлеб дрожжи", "operator": "or" } } }
// Фраза (точный порядок слов)
{ "match_phrase": { "content": "как испечь хлеб" } }Bool (комбинация условий)
{
"bool": {
"must": [ // AND (должны совпадать)
{ "match": { "status": "published" } }
],
"should": [ // OR (повышают релевантность)
{ "match": { "title": "хлеб" } },
{ "match": { "content": "хлеб" } }
],
"must_not": [ // NOT
{ "term": { "author": "спам" } }
],
"filter": [ // Фильтр (без влияния на релевантность)
{ "range": { "published_at": { "gte": "2024-01-01" } } }
]
}
}Fuzzy (поиск с опечатками)
// Расстояние Левенштейна = 2 (опечатки)
{ "match": { "name": { "query": "хлебны", "fuzziness": 2 } } }
// Найдет "хлебный", "хлебной", "хлебцы"...Wildcard (поиск по маске)
{ "wildcard": { "name": "хлеб*" } } // хлеб, хлебный, хлебопечкаPrefix (поиск по префиксу)
{ "prefix": { "name": "хлеб" } }Range (диапазон)
{ "range": { "price": { "gte": 100, "lte": 1000 } } }Geo (геопоиск)
{
"geo_distance": {
"distance": "10km",
"location": { "lat": 55.75, "lon": 37.61 }
}
}Агрегации (Analytics)
Поисковые движки умеют не только искать, но и агрегировать результаты.
GET /sales/_search
{
"query": { "match": { "product": "хлеб" } },
"aggs": {
"by_city": { // Группировка по городу
"terms": { "field": "city" }
},
"by_price_range": { // Гистограмма по цене
"histogram": { "field": "price", "interval": 100 }
},
"avg_price": { // Средняя цена
"avg": { "field": "price" }
},
"sales_over_time": { // Временной ряд
"date_histogram": {
"field": "sold_at",
"interval": "day"
}
}
}
}Результат:
{
"aggregations": {
"by_city": {
"buckets": [
{ "key": "Москва", "doc_count": 150 },
{ "key": "СПб", "doc_count": 80 }
]
},
"avg_price": { "value": 45.5 }
}
}Когда использовать поисковые движки
Идеальные сценарии
| Сценарий | Почему подходит |
|---|---|
| Поиск по сайту (интернет-магазин, блог, документация) | Быстрый полнотекстовый поиск, автодополнение, опечатки |
| Поиск по логам (ELK Stack) | Миллиарды логов, мгновенный поиск, агрегации |
| Поиск по документам (база знаний, юридические документы) | Текстовые запросы, ранжирование по релевантности |
| Геопоиск (магазины рядом, точки интереса) | Встроенная геопространственная поддержка |
| Аналитика логов (ошибки, метрики) | Агрегации, временные ряды, дашборды |
| Рекомендательные системы (похожие товары) | “More like this” запросы |
| Поиск с опечатками (пользователь может ошибиться) | Fuzzy search “из коробки” |
Сомнительные сценарии
| Сценарий | Почему плохо подходит |
|---|---|
| Система транзакций (OLTP) | Нет ACID, нет сложных транзакций |
| Хранилище “источник истины” (source of truth) | Потеря данных при сбое (может быть) |
| Данные с жесткой схемой и связями | Реляционные БД подходят лучше |
| Очень маленькие объемы (сотни документов) | Оверхед; можно использовать LIKE в SQL |
Сравнение поисковых движков
| Характеристика | Elasticsearch | Solr | Typesense | Manticore |
|---|---|---|---|---|
| Язык | JSON DSL | XML/JSON | JSON | SQL |
| Интерфейс | REST API | REST API / Admin UI | REST API | SQL / HTTP |
| Масштабирование | Горизонтальное | Горизонтальное | Горизонтальное | Горизонтальное |
| Ранжирование | BM25 (настраиваемый) | BM25 (настраиваемый) | Ranker (правила) | BM25 |
| Агрегации | Да | Да | Ограниченно | Да |
| Визуализация | Kibana | Banana / Grafana | Нет | Нет |
| Скорость | Очень высокая | Высокая | Очень высокая (in-memory) | Высокая |
| Сложность | Средняя | Высокая | Низкая | Средняя |
| Популярность | Очень высокая | Средняя | Растущая | Низкая |
Распространенные ошибки
Ошибка 1: Использование Elasticsearch как основной БД
Хранение единственной копии данных в Elasticsearch, использование его как “источника истины”.
Как исправить: Elasticsearch — для поиска. Храните основную копию в реляционной или документной БД. Синхронизируйте данные через CDC или двойную запись.
Ошибка 2: Неправильный анализ текста (не тот язык)
Поиск по русским текстам с анализатором по умолчанию (standard). “бежал” не найдет “бежать”.
Как исправить: Настройте анализатор для вашего языка (stemmer, stop words).
Ошибка 3: Слишком много шард (shards)
Создание тысячи шард для небольшого объема данных (каждая шарда — накладные расходы).
Как исправить: 1 шарда на 10-50 ГБ данных. Для небольших проектов 1-5 шард достаточно.
Ошибка 4: Игнорирование реплик
Одна копия данных, потеря шарды — потеря данных.
Как исправить: Всегда используйте реплики (min 1 реплика, лучше 2).
Ошибка 5: Поиск по полю, которое не индексировано
// Поле не индексировано (index: false)
PUT /users/_mapping
{
"properties": {
"email": { "type": "text", "index": false }
}
}
// Поиск по email не работает
GET /users/_search
{
"query": { "match": { "email": "ivan@example.com" } } // Ошибка!
}Как исправить: Убедитесь, что поле имеет index: true (по умолчанию true).
Резюме для системного аналитика
Поисковые движки — это специализированные базы данных для полнотекстового поиска. Они строят инвертированный индекс (слово → документы), что позволяет находить документы по словам за миллисекунды, даже на терабайтах данных.
Elasticsearch — безусловный лидер. Самый популярный, богатая экосистема (Kibana, Logstash, Beats), мощный Query DSL, агрегации, масштабирование.
Основные этапы работы: индексация (токенизация, нормализация, стемминг, построение индекса) → поиск (анализ запроса, поиск в индексе, ранжирование).
Ранжирование определяет порядок результатов. BM25 (Elasticsearch по умолчанию) — улучшенная версия TF-IDF, учитывает длину документа и насыщение.
Анализ текста критически важен. Неправильный токенизатор или отсутствие стемминга для русского языка сделают поиск бесполезным.
Поисковые движки не заменяют реляционные БД. Они для поиска, не для ACID-транзакций. Основное хранилище — PostgreSQL/MongoDB, поисковый движок — для индексации и поиска.