Вы вводите запрос в строку поиска документации. Появляются результаты. Но что произошло между нажатием клавиши и результатами? Для большинства платформ документации ответ — либо «немного» (простое сопоставление ключевых слов, которое пропускает очевидные результаты), либо «много инфраструктуры» (кластер Elasticsearch, который ваша команда эксплуатации должна поддерживать).
Есть золотая середина, о которой большинство команд не знают: встроенный полнотекстовый поиск. DocPlatform использует Bleve, поисковую библиотеку на Go, скомпилированную прямо в бинарный файл. Никаких внешних сервисов, сетевых вызовов, кластеров для управления — но с функциями, которые вам реально нужны: стемминг, нечёткий поиск, бустинг полей и ранжирование по релевантности.
Вот как это работает изнутри.
Почему сопоставление по ключевым словам не работает
Простейшая реализация поиска — LIKE '%query%' в SQL. Это то, что вы получаете, когда разработчик добавляет поиск как побочную мысль. Работает для точных совпадений, но проваливается во всех остальных случаях:
- Поиск «install» не находит страницы с «installation» или «installing»
- Поиск «authn» не находит страницы об «authentication»
- Опечатка «deploymnet» не возвращает ничего
- Все результаты имеют одинаковый рейтинг — страница с заголовком «Installation Guide» ранжируется так же, как страница, где «install» упоминается один раз в подвале
Некоторые платформы улучшают это расширением SQLite FTS5, которое добавляет токенизацию и базовое ранжирование. Это шаг вперёд, но всё ещё без стемминга, нечёткого поиска и возможности повышать вес определённых полей (например, заголовков) над другими.
Что на самом деле делает полнотекстовый поиск
Полноценный поисковый движок обрабатывает текст в две фазы: индексирование (когда контент создаётся) и запрос (когда кто-то ищет). Обе фазы делают больше работы, чем можно ожидать.
Индексирование: что происходит при сохранении страницы
Когда вы создаёте или обновляете страницу в DocPlatform, контент проходит через аналитический пайплайн перед индексированием:
1. Токенизация — Текст разбивается на отдельные термины. «Getting started with DocPlatform» превращается в ["getting", "started", "with", "docplatform"].
2. Приведение к нижнему регистру — Все токены нормализуются к нижнему регистру. «DocPlatform» и «docplatform» совпадают.
3. Удаление стоп-слов — Распространённые слова вроде «the», «is», «with», «a» удаляются. Они встречаются почти в каждом документе и не помогают различать релевантные результаты.
4. Стемминг — Слова сводятся к корневой форме. «installing», «installation» и «installed» все становятся «instal». Это означает, что поиск по любому из этих слов находит все остальные.
5. Разделение по полям — Разные части документа индексируются отдельно. Заголовок, тело, теги и путь получают свои поля в индексе. Это позволяет бустинг полей при запросе.
Результирующий индекс — это структура данных, называемая обратным индексом — карта от каждого термина к списку документов, содержащих его, вместе с позиционной информацией (где в документе появляется термин и как часто).
"instal" → [doc_3 (title, pos:0), doc_7 (body, pos:45), doc_12 (body, pos:102)]
"deploy" → [doc_3 (body, pos:23), doc_5 (title, pos:0)]
"kubernetes" → [doc_5 (body, pos:15), doc_5 (body, pos:89)]
Запрос: что происходит при поиске
Когда пользователь вводит запрос, поисковый движок пропускает текст запроса через тот же аналитический пайплайн (токенизация, приведение к нижнему регистру, стемминг), затем ищет каждый термин в обратном индексе.
Но реальная ценность — в ранжировании. Не все совпадения равны. Bleve использует вариант TF-IDF (частота термина, обратная частота документа) в сочетании с бустингом полей для вычисления оценки релевантности:
- Частота термина: Страница, упоминающая «deployment» 10 раз, вероятно, более релевантна, чем та, что упоминает его один раз.
- Обратная частота документа: Термин, встречающийся только в 3 из 500 документов, более отличительный (и более полезный для ранжирования), чем термин, встречающийся в 400 документах.
- Бустинг полей: Совпадение в заголовке стоит больше, чем совпадение в теле. В DocPlatform совпадения в заголовке получают 3-кратный буст, совпадения в тегах — 2-кратный, совпадения в теле — 1-кратный.
Формула выдаёт числовую оценку для каждого совпавшего документа, и результаты возвращаются отсортированными по оценке.
Нечёткий поиск: обработка опечаток
Реальные пользователи делают опечатки. «Kuberntes» вместо «Kubernetes». «Authentcation» вместо «Authentication». Базовый поиск ничего не возвращает для таких запросов.
Bleve поддерживает нечёткий поиск с использованием расстояния редактирования (расстояния Левенштейна). Термин запроса совпадает с термином документа, если они отличаются на N или менее символьных операций (вставка, удаление, замена). DocPlatform использует расстояние редактирования 1 по умолчанию — достаточно для отлова одиночных опечаток без слишком большого числа ложных срабатываний.
Запрос: "authentcation"
Расстояние редактирования 1: совпадает с "authentication" (одна пропущенная 'i')
Результаты: все документы, содержащие "authentication"
Это происходит прозрачно. Пользователям не нужно знать о нечётком поиске или что-то настраивать. Они просто ищут и получают результаты, даже если ошиблись при наборе.
Как DocPlatform поддерживает индекс в синхронизации
Сложная часть встроенного поиска — не сам поиск, а поддержание согласованности индекса с контентом. DocPlatform имеет два источника контента: веб-редактор и git. Оба могут создавать, обновлять и удалять страницы.
Вот поток индексирования:
Сохранение из веб-редактора: Пользователь нажимает «сохранить» → контент записывается в базу данных → поисковый индексатор обновляет индекс Bleve → движок git-синхронизации коммитит изменение.
Получение git push: Срабатывает git webhook → движок синхронизации забирает изменения → новые/изменённые страницы записываются в базу данных → поисковый индексатор обновляет индекс Bleve.
Массовые операции: При импорте репозитория с сотнями markdown-файлов индексатор обрабатывает их пакетами. Импорт 500 страниц занимает около 3 секунд на скромном оборудовании.
Удаления: При удалении страницы (из веб-интерфейса или git) соответствующий документ удаляется из индекса Bleve. Никаких осиротевших результатов поиска.
Важная деталь: индексирование синхронно с операцией записи. Когда сохранение/синхронизация завершается, поисковый индекс уже обновлён. Нет задержки «ожидайте переиндексации». Это возможно, потому что Bleve работает в том же процессе — нет сетевого хопа к отдельному кластеру Elasticsearch.
Подробнее о работе движка синхронизации — в нашей статье о том, почему git-синхронизация ломается, и паттерне Content Ledger, который решает эту проблему.
Сравнение: встроенный vs. внешний поиск
| Возможность | SQL LIKE | SQLite FTS5 | Bleve (встроенный) | Elasticsearch |
|---|---|---|---|---|
| Токенизация | Нет | Да | Да | Да |
| Стемминг | Нет | Ограниченный | Да | Да |
| Нечёткий поиск | Нет | Нет | Да | Да |
| Бустинг полей | Нет | Нет | Да | Да |
| Ранжирование релевантности | Нет | Базовое | TF-IDF | BM25 |
| Дополнительный сервис | Нет | Нет | Нет | Да |
| Накладные расходы памяти | Нет | ~1 МБ | ~10 МБ | 1-4 ГБ (JVM) |
| Сложность эксплуатации | Нет | Нет | Нет | Высокая |
Elasticsearch выигрывает по продвинутым функциям: агрегации, запросы к вложенным документам, кастомные анализаторы, распределённый поиск по кластерам. Если вы создаёте поисковый продукт, он вам, вероятно, нужен.
Но для поиска по документации — где корпус составляет тысячи страниц, а не миллионы записей — встроенный Bleve покрывает требования с нулевыми операционными накладными расходами. Вам не нужен отдельный кластер для поиска по документации.
Как мы рассказали в статье об архитектуре одного бинарного файла, размещение всего в одном процессе устраняет целый класс операционных проблем.
Что пользователи реально ищут
Мы настроили конфигурацию поиска под то, как люди реально ищут в документации:
Точные названия функций: «RBAC», «WebAuthn», «git sync» — они должны точно совпадать в заголовках и тегах.
Концептуальные запросы: «как настроить права доступа» — они опираются на стемминг и поиск по тексту.
Частичное воспоминание: «та страница про деплой…» — нечёткий поиск и бустинг заголовков помогают найти правильный результат даже при неполном запросе.
Сообщения об ошибках: Пользователи вставляют сообщения об ошибках в поиск. Они выигрывают от точного совпадения длинных последовательностей токенов.
Конфигурация по умолчанию хорошо справляется со всем этим. Никакой настройки не требуется.
Попробуйте
Если хотите увидеть это в действии, установите DocPlatform и создайте несколько страниц. Строка поиска на опубликованном сайте документации использует именно ту систему, что описана здесь. Введите запрос, намеренно допустите опечатку и наблюдайте, как система всё равно находит нужное.
Community Edition включает полные поисковые возможности — без ограничений функций, без «обновитесь для лучшего поиска». Руководство по настройке поиска содержит детали кастомизации поискового поведения, если вам это нужно.