Интеграция Sphinx для поиска на сайте
Sphinx Search и его активно развиваемый форк Manticore Search — проверенные поисковые движки с многолетней историей в highload-проектах. Используются там, где важна совместимость с MySQL-протоколом, зрелость решения и прямой контроль над индексами. Manticore Search добавил поддержку JSON-документов, колоночного хранилища, HTTP API и RT-индексов реального времени.
Когда выбирают Sphinx / Manticore
- Легаси-проекты с существующей Sphinx-инфраструктурой
- Нужна MySQL-совместимость — подключение через стандартные MySQL-клиенты
- Корпус в сотни миллионов документов: новостные агрегаторы, архивы
- Полный контроль над токенизатором и морфологией
- Интеграция с существующей MySQL/MariaDB-репликацией
Manticore Search vs Sphinx
| Характеристика | Sphinx 3.x | Manticore Search 6.x |
|---|---|---|
| Разработка | Замедлена | Активная |
| RT-индексы | Базовые | Полноценные |
| JSON-документы | Нет | Да |
| HTTP API | Нет | Да (Elasticsearch-совместимый) |
| Колоночное хранилище | Нет | Да |
| MySQL-протокол | Да | Да |
Для новых проектов предпочтительнее Manticore Search.
Установка Manticore Search
# docker-compose.yml
services:
manticore:
image: manticoresearch/manticore:6.2.12
environment:
- EXTRA=1
ports:
- "9306:9306" # MySQL-совместимый порт
- "9308:9308" # HTTP API
volumes:
- manticore_data:/var/lib/manticore
- ./manticore.conf:/etc/manticoresearch/manticore.conf
Конфигурация индекса
# manticore.conf
index articles {
type = rt
path = /var/lib/manticore/articles
rt_field = title
rt_field = body
rt_field = author
rt_attr_uint = category_id
rt_attr_bigint = created_at
rt_attr_float = rating
rt_attr_string = slug
morphology = stem_ru, stem_en
min_word_len = 2
expand_keywords = 1
min_infix_len = 3
stopwords = /etc/manticoresearch/stopwords_ru.txt
}
searchd {
listen = 0.0.0.0:9306:mysql41
listen = 0.0.0.0:9308:http
log = /var/log/manticore/searchd.log
query_log = /var/log/manticore/query.log
max_matches = 10000
}
Подключение через MySQL-клиент (PHP)
Manticore принимает SQL на порту 9306 по MySQL-протоколу:
$pdo = new PDO('mysql:host=localhost;port=9306;charset=utf8', '', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Вставка документа:
$stmt = $pdo->prepare("
INSERT INTO articles (id, title, body, author, category_id, created_at, rating)
VALUES (:id, :title, :body, :author, :category_id, :created_at, :rating)
");
$stmt->execute([
'id' => $article->id,
'title' => $article->title,
'body' => strip_tags($article->content),
'author' => $article->user->name,
'category_id' => $article->category_id,
'created_at' => $article->created_at->timestamp,
'rating' => $article->rating,
]);
Полнотекстовый поиск с весами полей:
$stmt = $pdo->prepare("
SELECT id, title, author, rating,
WEIGHT() AS relevance
FROM articles
WHERE MATCH(:query)
AND category_id = :category_id
ORDER BY relevance DESC, rating DESC
LIMIT :offset, :limit
OPTION ranker=bm25, field_weights=(title=10, body=1, author=2)
");
HTTP API (Elasticsearch-совместимый)
# Индексация
curl -X POST 'http://localhost:9308/articles/_doc/1' \
-H 'Content-Type: application/json' \
-d '{
"title": "Заголовок статьи",
"body": "Текст статьи",
"category_id": 5
}'
# Полнотекстовый поиск
curl -X POST 'http://localhost:9308/articles/_search' \
-H 'Content-Type: application/json' \
-d '{
"query": { "match": { "title": "поиск по сайту" } },
"sort": [{ "_score": "desc" }],
"size": 20
}'
Синхронизация с основной БД
Batch-индексация через chunk:
public function handle(): void
{
$pdo = $this->getManticoreConnection();
Article::with('user', 'category')
->where('published', true)
->chunk(1000, function ($articles) use ($pdo) {
$pdo->beginTransaction();
foreach ($articles as $article) {
$pdo->prepare("REPLACE INTO articles ...")->execute(
$this->toDocument($article)
);
}
$pdo->commit();
});
}
Event-driven обновление через Observer:
class ArticleObserver
{
public function saved(Article $article): void
{
ManticoreIndexJob::dispatch($article->id);
}
public function deleted(Article $article): void
{
ManticoreDeleteJob::dispatch($article->id);
}
}
Морфология русского языка
Sphinx/Manticore включает встроенный стеммер stem_ru. Для более точной морфологии используется morphology = lemmatize_ru_all — требует отдельных словарей lemmatize. Это позволяет находить «бегущий» по запросу «бег» и наоборот.
Стоп-слова подключаются файлом — список предлогов, союзов, частиц, которые исключаются из индекса.
Подсветка результатов
SELECT id, title,
SNIPPET(body, :query,
'limit=200, around=5, html_strip_mode=strip') AS excerpt
FROM articles
WHERE MATCH(:query)
LIMIT 20
SNIPPET() возвращает фрагмент текста с выделением найденных слов — готово для отображения в результатах поиска.
Сроки работ
| Этап | Время |
|---|---|
| Установка, конфигурация, схема индекса | 1 день |
| Первичная индексация + синхронизатор | 2 дня |
| API поиска в приложении + тесты | 2 дня |
| UI: результаты, сниппеты, пагинация | 1–2 дня |
Итого: 6–7 рабочих дней.







