Полнотекстовый поиск — это как поиск иголки в стоге сена, только стог — это ваша база данных, а иголка — нужная информация. Если вы используете Yii2 и хотите, чтобы поиск был быстрым и умным, Elasticsearch станет отличным решением. В этой статье я расскажу, как подружить Yii2 с Elasticsearch и настроить полнотекстовый поиск.

Digital-студия WNDER

Что такое Elasticsearch и зачем он нужен в Yii2?

Elasticsearch — это поисковый движок, построенный на Lucene. Он умеет быстро искать по большим объёмам текста, анализировать запросы, учитывать опечатки и ранжировать результаты по релевантности. В Yii2 есть официальное расширение yiisoft/yii2-elasticsearch, которое упрощает интеграцию.

Зачем это нужно? Представьте, что у вас интернет-магазин с миллионом товаров. Пользователь вводит «красные кроссовки Nike». Обычный SQL LIKE-запрос будет медленным и неучтёт морфологию. Elasticsearch же найдёт все кроссовки, даже если в описании написано «кроссовок» или «Nike Air». Разница очевидна.

Установка и настройка Elasticsearch

Сначала установите Elasticsearch. Скачайте с официального сайта, распакуйте и запустите. Для разработки хватит одного узла. Проверьте работу:

curl -X GET "localhost:9200"

В ответ вы должны увидеть JSON с информацией о кластере. Теперь установим расширение для Yii2 через Composer:

composer require yiisoft/yii2-elasticsearch

После установки настройте компонент в конфиге приложения (обычно config/web.php или config/main.php):

'components' => [
    'elasticsearch' => [
        'class' => 'yii\elasticsearch\Connection',
        'hosts' => [
            'http://localhost:9200',
        ],
    ],
],

Если Elasticsearch требует аутентификации, добавьте логин и пароль в массив hosts.

Создание модели для работы с индексом

В Elasticsearch данные хранятся в индексах, аналогичных таблицам в БД. Создадим модель, которая будет представлять индекс с товарами. Наследуем её от yii\elasticsearch\ActiveRecord.

<?php

namespace app\models;

use yii\elasticsearch\ActiveRecord;

class Product extends ActiveRecord
{
    public static function index()
    {
        return 'products'; // имя индекса
    }

    public static function type()
    {
        return 'product'; // тип (в новых версиях необязателен)
    }

    public function attributes()
    {
        return ['id', 'name', 'description', 'price', 'category'];
    }

    public function rules()
    {
        return [
            [['name', 'description'], 'string'],
            [['price'], 'number'],
            [['category'], 'string'],
        ];
    }
}

Обратите внимание: метод attributes() возвращает список полей, которые будут храниться в индексе. Метод index() задаёт имя индекса. По умолчанию Yii2 использует имя класса, но лучше указать явно.

Правило: Всегда явно указывайте имя индекса в модели. Это избавит от путаницы, если вы переименуете класс.

Индексация данных и поиск

Чтобы добавить документ в индекс, просто создайте экземпляр модели, заполните атрибуты и вызовите save():

$product = new Product();
$product->id = 1;
$product->name = 'Кроссовки Nike Air';
$product->description = 'Удобные красные кроссовки для бега';
$product->price = 7990;
$product->category = 'Обувь';
$product->save();

Для массовой индексации используйте bulk() или createCommand(). Например, перенесём все товары из MySQL:

$products = \app\models\ProductSql::find()->all();
$bulk = Yii::$app->elasticsearch->createBulkCommand();
foreach ($products as $sqlProduct) {
    $bulk->addAction('index', [
        'index' => 'products',
        'type' => 'product',
        'id' => $sqlProduct->id,
        'body' => [
            'name' => $sqlProduct->name,
            'description' => $sqlProduct->description,
            'price' => $sqlProduct->price,
            'category' => $sqlProduct->category,
        ],
    ]);
}
$bulk->execute();

Теперь поиск. Yii2 предлагает удобный метод find(), который возвращает объект запроса. Можно искать по всем полям или по конкретному:

$query = Product::find()->query([
    'multi_match' => [
        'query' => 'красные кроссовки',
        'fields' => ['name', 'description'],
    ],
]);
$results = $query->all();

Elasticsearch автоматически анализирует запрос: разбивает на слова, приводит к нижнему регистру, ищет совпадения. Результаты сортируются по релевантности (score). Вы можете добавить фильтры, сортировку, агрегации — всё через массив.

Продвинутые настройки: анализаторы, маппинг, подсветка

Чтобы поиск был ещё умнее, настройте анализаторы. Например, для русского языка используйте анализатор russian. Создайте индекс с нужными настройками через API Elasticsearch:

PUT /products
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_russian": {
          "type": "russian"
        }
      }
    }
  },
  "mappings": {
    "product": {
      "properties": {
        "name": {
          "type": "text",
          "analyzer": "my_russian"
        },
        "description": {
          "type": "text",
          "analyzer": "my_russian"
        }
      }
    }
  }
}

Теперь при индексации текст будет обрабатываться русским анализатором: учитываться морфология, стоп-слова. Это значительно улучшит качество поиска.

Подсветка найденных фрагментов (highlighting) делается так:

$query = Product::find()->query([
    'multi_match' => [
        'query' => 'кроссовки',
        'fields' => ['name', 'description'],
    ],
])->highlight([
    'fields' => [
        'name' => new \yii\elasticsearch\Query()->highlight(),
        'description' => new \yii\elasticsearch\Query()->highlight(),
    ],
]);

В результатах будет массив highlight с кусками текста, где найдены совпадения, обёрнутыми в <em>.

Лайфхак: Используйте _analyze API, чтобы проверить, как анализатор разбивает текст на токены. Это помогает отладить поиск.

Сравнение возможностей Elasticsearch и SQL LIKE

Критерий Elasticsearch SQL LIKE
Скорость на больших данных Высокая (инвертированный индекс) Низкая (полный перебор)
Морфология Поддерживается через анализаторы Нет
Ранжирование по релевантности Есть (score) Нет
Подсветка результатов Есть Нет

Как видите, Elasticsearch выигрывает по многим параметрам. Но не спешите отказываться от SQL: для простых задач LIKE может быть достаточно. Elasticsearch стоит внедрять, когда поиск — ключевая функция.

Синхронизация данных между БД и Elasticsearch

Одна из главных задач — поддерживать актуальность индекса. Если данные меняются в MySQL, их нужно обновлять и в Elasticsearch. Есть несколько подходов:

  • Синхронно при сохранении. В методе afterSave() модели Yii2 обновляйте документ в Elasticsearch. Просто, но замедляет запись.
  • Через очередь. Отправляйте задание в RabbitMQ или Redis, а воркер обновляет индекс. Асинхронно и надёжно.
  • Периодическая переиндексация. По крону запускайте полную переиндексацию. Подходит, если данные меняются редко.

Вот пример синхронного обновления в модели:

public function afterSave($insert, $changedAttributes)
{
    parent::afterSave($insert, $changedAttributes);
    $elasticProduct = new \app\models\Product();
    $elasticProduct->id = $this->id;
    $elasticProduct->name = $this->name;
    $elasticProduct->description = $this->description;
    $elasticProduct->price = $this->price;
    $elasticProduct->category = $this->category;
    $elasticProduct->save();
}

Не забудьте обработать удаление: в afterDelete() удаляйте документ из индекса.

Совет: Для больших объёмов используйте пакетную индексацию. Это снижает нагрузку на кластер.

Заключение

Интеграция Yii2 с Elasticsearch — это мощный инструмент для создания быстрого и релевантного поиска. Вы настроили соединение, создали модель, научились индексировать и искать данные, а также синхронизировать их с основной БД. Помните о правилах: явно указывайте индекс, используйте анализаторы для морфологии, следите за актуальностью данных.

Если вы только начинаете, начните с простого поиска, а затем добавляйте фильтры и подсветку. Elasticsearch гибок и масштабируем, так что ваш поиск будет летать.

Студия WNDER