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

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

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

Основные сценарии использования:

  • Поиск по каталогу товаров с фильтрами.
  • Поиск по блогу или документации.
  • Аналитика и агрегация данных (например, сколько пользователей купили товар за месяц).
Правило 1: Не пытайтесь заменить Elasticsearch базу данных. Используйте его только для поиска и аналитики. Основные данные храните в MySQL или PostgreSQL, а в Elasticsearch дублируйте только то, что нужно искать.

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

Для начала нужно установить Elasticsearch на сервер. Если вы используете Docker, добавьте в docker-compose.yml:

elasticsearch:
  image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
  environment:
    - discovery.type=single-node
  ports:
    - "9200:9200"

После запуска проверьте, что Elasticsearch работает: откройте в браузере http://localhost:9200. Вы должны увидеть JSON с информацией о версии.

Теперь установите расширение для Yii2 через Composer:

composer require yiisoft/yii2-elasticsearch

Добавьте компонент в конфигурацию приложения (config/web.php или config/console.php):

'components' => [
    'elasticsearch' => [
        'class' => 'yii\elasticsearch\Connection',
        'nodes' => [
            ['http_address' => 'http://localhost:9200'],
            // можно добавить несколько узлов для отказоустойчивости
        ],
    ],
],

Готово! Теперь Yii2 умеет общаться с Elasticsearch.

Создание модели и индексация данных

В Elasticsearch данные хранятся в индексах (аналог таблиц в MySQL). Для работы с ними создайте Active Record модель. Например, для товаров:

namespace app\models;

use yii\elasticsearch\ActiveRecord;

class Product extends ActiveRecord
{
    // Указываем, в каком индексе хранить данные
    public static function index()
    {
        return 'products';
    }

    // Маппинг полей (типы данных)
    public static function mapping()
    {
        return [
            'properties' => [
                'id' => ['type' => 'integer'],
                'name' => ['type' => 'text'],
                'price' => ['type' => 'float'],
                'description' => ['type' => 'text'],
                'created_at' => ['type' => 'date'],
            ],
        ];
    }

    // Правила валидации (опционально)
    public function rules()
    {
        return [
            [['name', 'price'], 'required'],
            [['price'], 'number'],
        ];
    }
}

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

$product = new Product();
$product->setAttributes([
    'id' => 1,
    'name' => 'iPhone 15',
    'price' => 999.99,
    'description' => 'Самый новый смартфон Apple',
    'created_at' => date('c'),
]);
$product->save(); // Автоматически индексирует документ

Для массовой индексации используйте batch-запросы:

$products = Product::find()->batch(100); // Получаем пачками из Elasticsearch
foreach ($products as $product) {
    $product->save();
}

Поиск с фильтрами и подсветкой результатов

Теперь самое интересное — поиск. Допустим, пользователь ищет «iPhone». Напишем простой поисковый запрос:

$query = Product::find()->query(['match' => ['name' => 'iPhone']]);
$results = $query->all();

Но если нужно искать по нескольким полям и с фильтрами, используйте более сложный запрос:

$searchQuery = [
    'bool' => [
        'must' => [
            ['multi_match' => [
                'query' => 'iPhone',
                'fields' => ['name^3', 'description'], // name важнее в 3 раза
            ]],
        ],
        'filter' => [
            ['range' => ['price' => ['gte' => 500, 'lte' => 1500]]],
        ],
    ],
];

$query = Product::find()->query($searchQuery);
$products = $query->all();

Чтобы выделить совпадения в тексте (подсветка), добавьте параметр highlight:

$query = Product::find()->query(['match' => ['name' => 'iPhone']])
    ->highlight(['fields' => ['name' => new \stdClass(), 'description' => new \stdClass()]]);
$results = $query->all();
foreach ($results as $product) {
    echo $product->getHighlight('name'); // Вернет массив с подсвеченными фрагментами
}
Параметр Описание Пример
query Основной поисковый запрос ['match' => ['field' => 'value']]
filter Фильтры (без влияния на релевантность) ['range' => ['price' => ['gte' => 100]]]
highlight Подсветка совпадений ['fields' => ['name' => new stdClass()]]
sort Сортировка результатов ['price' => 'asc']
Лайфхак: Для автодополнения (suggest) используйте Product::find()->suggest('term', ['term' => ['field' => 'name']]). Это отлично работает для поисковых подсказок.

Обновление и удаление данных

Обновление документа в Elasticsearch похоже на сохранение — просто измените атрибуты и вызовите save(). Но есть нюанс: если вы изменили только одно поле, Elasticsearch перезапишет весь документ. Чтобы обновить частично, используйте метод updateAttributes():

$product = Product::get(1);
$product->updateAttributes(['price' => 899.99]);

Удаление:

$product = Product::get(1);
$product->delete();

Массовое удаление по условию:

Product::deleteAll(['match' => ['status' => 'deleted']]);

Синхронизация с MySQL: как не потерять данные

Главная проблема — данные в MySQL и Elasticsearch могут рассинхронизироваться. Например, товар изменили в админке, а в поиске осталась старая версия. Решение: использовать события в модели Yii2.

Добавьте в модель Product (MySQL) обработчики:

use app\models\elastic\ProductIndex;

public function afterSave($insert, $changedAttributes)
{
    parent::afterSave($insert, $changedAttributes);
    
    $elasticProduct = ProductIndex::findOne($this->id) ?: new ProductIndex();
    $elasticProduct->setAttributes([
        'id' => $this->id,
        'name' => $this->name,
        'price' => $this->price,
        'description' => $this->description,
        'created_at' => $this->created_at,
    ]);
    $elasticProduct->save();
}

public function afterDelete()
{
    parent::afterDelete();
    
    $elasticProduct = ProductIndex::findOne($this->id);
    if ($elasticProduct) {
        $elasticProduct->delete();
    }
}

Также можно использовать очередь (например, RabbitMQ) для асинхронной синхронизации, чтобы не замедлять запросы пользователей.

Заключение

Интеграция Yii2 с Elasticsearch — это не rocket science. Вы устанавливаете расширение, создаете модель, индексируете данные и ищете. Главное — помнить о синхронизации и не хранить в Elasticsearch то, что не нужно искать. Начните с малого: добавьте поиск по товарам или статьям, и вы увидите, как скорость вырастет в разы.

Что в итоге:

  • Elasticsearch ускоряет поиск по большим объемам данных.
  • Yii2 предоставляет удобный Active Record для работы с ним.
  • Синхронизацию лучше автоматизировать через события или очереди.

Попробуйте прямо сейчас — создайте индекс и проиндексируйте пару записей. Уверен, результат вас впечатлит.

Студия WNDER