Интеграция Meilisearch для поиска на сайте
Meilisearch — движок полнотекстового поиска, написанный на Rust. Время ответа на большинстве корпусов до 50 мс, поддержка опечаток из коробки, нечёткий поиск, фасетная фильтрация. Устанавливается как отдельный процесс рядом с основным приложением.
Когда это нужно
Стандартный LIKE '%query%' в PostgreSQL перестаёт справляться примерно при 50 000 записей в индексе — растут время запроса и нагрузка на базу. Meilisearch решает задачу иначе: строит перевёрнутый индекс отдельно, запросы идут мимо БД.
Подходит для:
- каталогов товаров с фильтрами по атрибутам
- блогов и баз знаний (10 000+ статей)
- справочников пользователей, адресов, товаров в B2B-кабинетах
- поиска по документации
Архитектура интеграции
Browser → Backend API → Meilisearch HTTP API
↓
PostgreSQL (источник данных)
Индексатор (Queued Job / Cron)
Meilisearch не является заменой основной БД. Данные живут в PostgreSQL, в Meilisearch попадает только то, что нужно для поиска. Синхронизация — через очереди при изменении записей или периодическим ребилдом индекса.
Установка и конфигурация
Docker Compose:
services:
meilisearch:
image: getmeili/meilisearch:v1.7
environment:
MEILI_MASTER_KEY: "${MEILI_MASTER_KEY}"
MEILI_ENV: production
volumes:
- meili_data:/meili_data
ports:
- "7700:7700"
Настройка атрибутов индекса:
{
"searchableAttributes": ["name", "description", "sku", "brand"],
"filterableAttributes": ["category_id", "brand", "in_stock", "price"],
"sortableAttributes": ["price", "created_at", "rating"],
"rankingRules": ["words", "typo", "proximity", "attribute", "sort", "exactness"],
"typoTolerance": {
"enabled": true,
"minWordSizeForTypos": { "oneTypo": 5, "twoTypos": 9 }
}
}
Синхронизация данных
Laravel Scout + официальный драйвер:
// composer require laravel/scout meilisearch/meilisearch-php
use Laravel\Scout\Searchable;
class Product extends Model
{
use Searchable;
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => strip_tags($this->description),
'brand' => $this->brand->name,
'category_id' => $this->category_id,
'price' => $this->price,
'in_stock' => $this->stock > 0,
];
}
}
Первичная индексация:
php artisan scout:import "App\Models\Product"
Поиск с фильтрами:
$results = Product::search($query)
->where('in_stock', true)
->where('category_id', $categoryId)
->orderBy('price')
->paginate(20);
Фасетная фильтрация
Meilisearch возвращает агрегации по фасетам за один запрос:
$client = new \Meilisearch\Client('http://localhost:7700', $apiKey);
$response = $client->index('products')->search($query, [
'filter' => ['category_id = 5', 'price 100 TO 5000'],
'facets' => ['brand', 'color', 'in_stock'],
'hitsPerPage' => 20,
'page' => 1,
]);
// $response->getFacetDistribution() — массив с количествами по каждому фасету
Поиск напрямую из браузера
Прямые запросы из JavaScript к Meilisearch возможны через Search-only API Key — ключ с ограниченными правами (только поиск по конкретным индексам). Это снижает задержку, убирая лишний hop через бэкенд.
import { MeiliSearch } from 'meilisearch'
const client = new MeiliSearch({
host: 'https://search.example.com',
apiKey: SEARCH_ONLY_KEY,
})
const results = await client.index('products').search(query, {
filter: 'in_stock = true',
limit: 10,
})
Мультиязычный поиск
Для каждого языка рекомендуется отдельный индекс (products_ru, products_en) или единый с языковым полем в фильтре. Meilisearch поддерживает стемминг для русского через словари — нужно указать dictionary в настройках индекса.
Сроки и этапы работ
| Этап | Содержание | Время |
|---|---|---|
| Настройка инфраструктуры | Docker, SSL, API-ключи | 1 день |
| Схема индекса | Атрибуты, ранжирование, фасеты | 1 день |
| Интеграция в бэкенд | Scout / прямой клиент, синхронизация | 2–3 дня |
| UI компонент поиска | Автодополнение, фасеты, пагинация | 2–3 дня |
| Тестирование | Нагрузка, релевантность, edge cases | 1 день |
Итого: 7–9 рабочих дней для типового каталога.
Мониторинг
Meilisearch отдаёт метрики через /metrics в Prometheus-формате (при включённой опции). Ключевые показатели: размер индекса, время индексирования задач, количество запросов в секунду. Статус задач индексации доступен через /tasks.







