Настройка Elasticsearch для сайта: быстрый поиск в 2026

Elasticsearch стал стандартом де-факто для быстрого полнотекстового поиска в 2026 году. Я внедрял его на десятках проектов — от небольших интернет-магазинов до крупных корпоративных порталов, и результаты всегда впечатляющие: поиск работает за 10-50мс против 1-3 секунд на обычных SQL-запросах.

Зачем нужен Elasticsearch и когда пора его внедрять

Честно говоря, стандартный поиск по базе через LIKE в MySQL начинает «задыхаться» уже на 10-20 тысячах записей. У меня был клиент с каталогом на 50 тысяч товаров — поиск по названию занимал 2-4 секунды, а по описанию вообще до 8 секунд. Пользователи просто уходили со страницы.

После внедрения Elasticsearch 8.11 тот же поиск стал работать за 15-30 миллисекунд. И это не просто скорость — появились возможности нечёткого поиска (опечатки исправляются автоматически), поиск по синонимам, автодополнение в реальном времени.

Elasticsearch критически важен когда:

На практике я рекомендую внедрять Elasticsearch на всех проектах с каталогами от 5000+ товаров. Да, это усложняет архитектуру, но пользовательский опыт становится на порядок лучше.

💡
Совет: Не пытайтесь оптимизировать MySQL-поиск до бесконечности. Если запросы с FULLTEXT индексами всё равно работают дольше 200мс — время переходить на Elasticsearch.

Установка и базовая настройка Elasticsearch 8.x

Я всегда использую официальные репозитории Elastic — они самые стабильные. На Ubuntu 22.04 процесс выглядит так:

# Добавляем GPG ключ
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg

# Добавляем репозиторий
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list

# Устанавливаем
sudo apt update && sudo apt install elasticsearch

# Запускаем и добавляем в автозагрузку
sudo systemctl enable elasticsearch
sudo systemctl start elasticsearch

После установки нужно обязательно настроить конфигурацию в `/etc/elasticsearch/elasticsearch.yml`. Вот мой стандартный конфиг для продакшена:

# Базовые настройки кластера
cluster.name: production-search
node.name: search-node-1

# Пути к данным и логам
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch

# Сетевые настройки
network.host: 127.0.0.1
http.port: 9200

# Безопасность
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true

# Память (не больше 50% от RAM)
bootstrap.memory_lock: true

# Настройки индексов по умолчанию
action.auto_create_index: false

Критически важно настроить память правильно. В файле `/etc/elasticsearch/jvm.options` я всегда указываю heap size:

# Для сервера с 8GB RAM
-Xms2g
-Xmx2g

# Для сервера с 16GB RAM  
-Xms4g
-Xmx4g

Правило простое: heap не должен превышать 50% от доступной RAM, и никогда не больше 32GB (из-за особенностей JVM).

⚠️
Важно: В Elasticsearch 8.x безопасность включена по умолчанию. Обязательно сохраните пароль для пользователя elastic, который показывается при первом запуске.

Создание индекса и настройка маппинга

Маппинг в Elasticsearch — это схема данных, которая определяет как именно индексировать поля. Я всегда создаю маппинг вручную, а не полагаюсь на автоматическое определение типов.

Вот пример маппинга для каталога товаров, который я использую в большинстве проектов:

PUT /products
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0,
    "analysis": {
      "analyzer": {
        "russian_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "russian_morphology",
            "english_morphology",
            "russian_stop"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "integer"
      },
      "title": {
        "type": "text",
        "analyzer": "russian_analyzer",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      },
      "description": {
        "type": "text",
        "analyzer": "russian_analyzer"
      },
      "price": {
        "type": "float"
      },
      "category_id": {
        "type": "integer"
      },
      "category_name": {
        "type": "keyword"
      },
      "brand": {
        "type": "keyword"
      },
      "in_stock": {
        "type": "boolean"
      },
      "created_at": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      },
      "tags": {
        "type": "keyword"
      }
    }
  }
}

Здесь несколько важных моментов из моей практики:

Анализатор для русского языка — обязательно нужен плагин `analysis-morphology`. Без него поиск по словоформам работать не будет. Устанавливается командой:

sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-morphology

Поля типа text vs keyword — text индексируется для полнотекстового поиска, keyword для точного совпадения. Для названий товаров я делаю оба типа через multi-field.

Количество шардов — для большинства проектов достаточно 1 шарда. Больше имеет смысл только при объёме данных 50GB+ или при распределённой установке.

У меня был проект с миллионом товаров — там я использовал 3 шарда и производительность была отличная. Но на проектах до 100 тысяч записей множественные шарды только замедляют работу.

Индексирование данных из базы

Самая частая задача — перенести данные из MySQL в Elasticsearch. Я делаю это через PHP-скрипты с использованием официального клиента. Вот рабочий пример:

setHosts(['localhost:9200'])
    ->setBasicAuthentication('elastic', 'your_password')
    ->build();

// Подключение к MySQL
$pdo = new PDO('mysql:host=localhost;dbname=shop;charset=utf8mb4', $user, $pass);

// Получаем данные порциями по 1000 записей
$limit = 1000;
$offset = 0;

while (true) {
    $stmt = $pdo->prepare("
        SELECT 
            p.id,
            p.title,
            p.description,
            p.price,
            p.category_id,
            c.name as category_name,
            p.brand,
            p.in_stock,
            p.created_at
        FROM products p
        LEFT JOIN categories c ON p.category_id = c.id
        WHERE p.active = 1
        LIMIT :limit OFFSET :offset
    ");
    
    $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
    $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
    $stmt->execute();
    
    $products = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
    if (empty($products)) {
        break;
    }
    
    // Формируем bulk-запрос
    $bulkParams = ['body' => []];
    
    foreach ($products as $product) {
        // Индекс документа
        $bulkParams['body'][] = [
            'index' => [
                '_index' => 'products',
                '_id' => $product['id']
            ]
        ];
        
        // Данные документа
        $bulkParams['body'][] = [
            'id' => (int)$product['id'],
            'title' => $product['title'],
            'description' => $product['description'],
            'price' => (float)$product['price'],
            'category_id' => (int)$product['category_id'],
            'category_name' => $product['category_name'],
            'brand' => $product['brand'],
            'in_stock' => (bool)$product['in_stock'],
            'created_at' => $product['created_at']
        ];
    }
    
    // Отправляем данные
    try {
        $response = $client->bulk($bulkParams);
        
        if ($response['errors']) {
            echo "Ошибки при индексации:\n";
            foreach ($response['items'] as $item) {
                if (isset($item['index']['error'])) {
                    print_r($item['index']['error']);
                }
            }
        } else {
            echo "Проиндексировано " . count($products) . " товаров\n";
        }
        
    } catch (Exception $e) {
        echo "Ошибка: " . $e->getMessage() . "\n";
        break;
    }
    
    $offset += $limit;
    
    // Небольшая пауза чтобы не нагружать сервер
    usleep(100000); // 0.1 секунды
}

echo "Индексация завершена\n";

На практике я всегда использую bulk API — он в разы быстрее одиночных запросов. При индексации 100 тысяч товаров разница составляет 5 минут против часа.

Для автоматического обновления индекса при изменении данных в MySQL я настраиваю триггеры или использую очереди. Вот пример через Redis Queue:

prepare("UPDATE products SET title = ?, price = ? WHERE id = ?");
    $stmt->execute([$data['title'], $data['price'], $productId]);
    
    // Добавляем задачу в очередь на обновление в Elasticsearch
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $redis->lPush('elasticsearch_updates', json_encode([
        'action' => 'update',
        'index' => 'products',
        'id' => $productId,
        'data' => $data
    ]));
}
ℹ️
Производительность: На современном SSD индексация работает со скоростью 5000-10000 документов в секунду. Если медленнее — проверьте настройки refresh_interval и количество реплик.

Настройка поисковых запросов

Правильно настроенный поисковый запрос — это 80% успеха всей системы. Я использую несколько типов запросов в зависимости от задач.

Простой поиск по одному полю:

GET /products/_search
{
  "query": {
    "match": {
      "title": {
        "query": "телефон samsung",
        "operator": "and"
      }
    }
  }
}

Мультиполевый поиск с разными весами:

GET /products/_search
{
  "query": {
    "multi_match": {
      "query": "телефон samsung",
      "fields": [
        "title^3",
        "description^1",
        "brand^2"
      ],
      "type": "best_fields",
      "operator": "and"
    }
  }
}

Здесь title имеет вес 3, brand — вес 2, а description — вес 1. Это значит, что совпадения в названии будут ранжироваться выше.

Продвинутый поиск с фильтрами и сортировкой:

GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "телефон",
            "fields": ["title^3", "description"]
          }
        }
      ],
      "filter": [
        {
          "range": {
            "price": {
              "gte": 10000,
              "lte": 50000
            }
          }
        },
        {
          "term": {
            "in_stock": true
          }
        },
        {
          "terms": {
            "brand": ["Samsung", "Apple", "Xiaomi"]
          }
        }
      ]
    }
  },
  "sort": [
    {"_score": {"order": "desc"}},
    {"price": {"order": "asc"}}
  ],
  "from": 0,
  "size": 20
}

В разделе `must` указываются условия, которые влияют на релевантность. В `filter` — условия, которые просто фильтруют результаты без влияния на score.

Для автодополнения я использую completion suggester. Сначала нужно добавить поле в маппинг:

"suggest": {
  "type": "completion",
  "analyzer": "russian_analyzer"
}

А при индексации заполнять его вариантами:

{
  "title": "Samsung Galaxy S24",
  "suggest": [
    "Samsung Galaxy S24",
    "Galaxy S24",
    "Samsung S24",
    "S24"
  ]
}

Тогда автодополнение работает молниеносно:

GET /products/_search
{
  "suggest": {
    "product_suggest": {
      "prefix": "sam",
      "completion": {
        "field": "suggest",
        "size": 10
      }
    }
  }
}

Оптимизация производительности поиска

На одном из проектов у меня поиск стал работать медленно после достижения 500 тысяч товаров. Пришлось серьёзно оптимизировать — в итоге скорость выросла в 3 раза.

Настройки индекса для производительности:

PUT /products/_settings
{
  "index": {
    "refresh_interval": "30s",
    "number_of_replicas": 0,
    "translog.durability": "async",
    "translog.sync_interval": "5s"
  }
}

`refresh_interval` по умолчанию 1 секунда — это означает, что новые документы становятся доступными для поиска каждую секунду. Для большинства сайтов достаточно 30 секунд.

Кеширование на уровне приложения:

 $query,
            'filters' => $filters,
            'page' => $page,
            'size' => $size
        ]));
        
        // Проверяем кеш
        $cached = $this->redis->get($cacheKey);
        if ($cached) {
            return json_decode($cached, true);
        }
        
        // Выполняем поиск
        $searchParams = [
            'index' => 'products',
            'body' => $this->buildQuery($query, $filters, $page, $size)
        ];
        
        $response = $this->client->search($searchParams);
        
        // Кешируем на 5 минут
        $this->redis->setex($cacheKey, 300, json_encode($response));
        
        return $response;
    }
    
    private function buildQuery($query, $filters, $page, $size) 
    {
        $from = ($page - 1) * $size;
        
        $body = [
            'from' => $from,
            'size' => $size,
            'query' => [
                'bool' => [
                    'must' => [
                        [
                            'multi_match' => [
                                'query' => $query,
                                'fields' => ['title^3', 'description', 'brand^2']
                            ]
                        ]
                    ]
                ]
            ]
        ];
        
        // Добавляем фильтры
        if (!empty($filters['price_from'])) {
            $body['query']['bool']['filter'][] = [
                'range' => [
                    'price' => ['gte' => $filters['price_from']]
                ]
            ];
        }
        
        if (!empty($filters['category_id'])) {
            $body['query']['bool']['filter'][] = [
                'term' => [
                    'category_id' => $filters['category_id']
                ]
            ];
        }
        
        return $body;
    }
}

Кеширование критически важно для популярных запросов. На том проекте топ-100 запросов составляли 80% от общего трафика поиска.

Использование routing для больших индексов:

Если у вас мультитенантное приложение или данные логически разделены (например, по регионам), используйте routing:

 'products',
    'id' => $productId,
    'routing' => $categoryId, // Документы одной категории попадут в один шард
    'body' => $document
];

// При поиске
$params = [
    'index' => 'products',
    'routing' => $categoryId, // Поиск только в нужном шарде
    'body' => $query
];

Это может ускорить поиск в разы, особенно при большом количестве шардов.

💡
Мониторинг: Обязательно настройте мониторинг через Elasticsearch APIs. Запрос `GET /_cat/indices?v` покажет размер и здоровье индексов, а `GET /_nodes/stats` — загрузку узлов.

Настройка безопасности

В Elasticsearch 8.x безопасность включена по умолчанию, но её нужно правильно настроить. Я никогда не оставляю кластер с дефолтными настройками в продакшене.

Создание пользователя для приложения:

# Создаём роль с ограниченными правами
curl -X POST "localhost:9200/_security/role/search_app_role" \
-H 'Content-Type: application/json' \
-u elastic:your_password \
-d '{
  "cluster": ["monitor"],
  "indices": [
    {
      "names": ["products", "categories"],
      "privileges": ["read", "write", "create_index", "delete_index"]
    }
  ]
}'

# Создаём пользователя
curl -X POST "localhost:9200/_security/user/search_app" \
-H 'Content-Type: application/json' \
-u elastic:your_password \
-d '{
  "password": "strong_app_password",
  "roles": ["search_app_role"],
  "full_name": "Search Application User"
}'

Настройка nginx для проксирования:

Никогда не открывайте Elasticsearch напрямую в интернет. Всегда используйте nginx как прокси:

server {
    listen 443 ssl http2;
    server_name search.yourdomain.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    # Ограничиваем доступ только к поисковым эндпоинтам
    location ~ ^/(products|categories)/_search$ {
        proxy_pass http://127.0.0.1:9200;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        
        # Ограничиваем размер запроса
        client_max_body_size 1M;
        
        # Только GET и POST
        limit_except GET POST {
            deny all;
        }
        
        # Базовая аутентификация
        auth_basic "Search API";
        auth_basic_user_file /etc/nginx/.htpasswd;
    }
    
    # Блокируем все остальные запросы
    location / {
        return 403;
    }
}

Настройка файрвола:

# Разрешаем подключения только с локального интерфейса
sudo ufw allow from 127.0.0.1 to any port 9200
sudo ufw allow from 10.0.0.0/8 to any port 9200  # Если есть внутренняя сеть

# Блокируем внешние подключения
sudo ufw deny 9200

У меня был случай, когда клиент оставил Elasticsearch открытым в интернете без аутентификации. За ночь злоумышленники удалили все данные и потребовали выкуп. С тех пор безопасность — первое, что я настраиваю.

Мониторинг и диагностика проблем

Elasticsearch требует постоянного мониторинга. Я использую комбинацию встроенных API и внешних инструментов.

Основные метрики для мониторинга:

# Здоровье кластера
curl -X GET "localhost:9200/_cluster/health?pretty"

# Статистика по индексам
curl -X GET "localhost:9200/_cat/indices?v&s=store.size:desc"

# Использование памяти узлами
curl -X GET "localhost:9200/_cat/nodes?v&h=name,heap.percent,ram.percent,cpu,load_1m"

# Медленные запросы
curl -X GET "localhost:9200/_cat/pending_tasks?v"

Скрипт для автоматической проверки:

#!/bin/bash

# Проверка здоровья кластера
HEALTH=$(curl -s "localhost:9200/_cluster/health" | jq -r '.status')

if [ "$HEALTH" != "green" ]; then
    echo "ALERT: Elasticsearch cluster status is $HEALTH" | mail -s "ES Alert" admin@yourdomain.com
fi

# Проверка использования диска
DISK_USAGE=$(df /var/lib/elasticsearch | awk 'NR==2 {print $5}' | sed 's/%//')

if [ "$DISK_USAGE" -gt 85 ]; then
    echo "ALERT: Elasticsearch disk usage is ${DISK_USAGE}%" | mail -s "ES Disk Alert" admin@yourdomain.com
fi

# Проверка памяти
HEAP_USAGE=$(curl -s "localhost:9200/_cat/nodes?h=heap.percent" | head -1)

if [ "$HEAP_USAGE" -gt 85 ]; then
    echo "ALERT: Elasticsearch heap usage is ${HEAP_USAGE}%" | mail -s "ES Memory Alert" admin@yourdomain.com
fi

Настройка логирования для диагностики:

В файле `/etc/elasticsearch/log4j2.properties` я увеличиваю детализацию для медленных запросов:

# Логируем медленные запросы поиска (больше 1 секунды)
logger.index_search_slowlog_index.name = index.search.slowlog
logger.index_search_slowlog_index.level = info
logger.index_search_slowlog_index.appenderRef.index_search_slowlog_rolling.ref = index_search_slowlog_rolling
logger.index_search_slowlog_index.additivity = false

# Логируем медленные запросы индексации (больше 10 секунд)  
logger.index_indexing_slowlog.name = index.indexing.slowlog.index
logger.index_indexing_slowlog.level = info
logger.index_indexing_slowlog.appenderRef.index_indexing_slowlog_rolling.ref = index_indexing_slowlog_rolling
logger.index_indexing_slowlog.additivity = false

А в настройках индекса устанавливаю пороги:

PUT /products/_settings
{
  "index.search.slowlog.threshold.query.warn": "2s",
  "index.search.slowlog.threshold.query.info": "1s",
  "index.search.slowlog.threshold.fetch.warn": "1s",
  "index.indexing.slowlog.threshold.index.warn": "10s"
}

На практике это помогает быстро выявлять проблемные запросы. Недавно у клиента поиск начал тормозить — в логах нашёл запросы с wildcard по всему тексту, которые разработчики добавили "для удобства".

Интеграция с популярными CMS

Большинство моих проектов работают на WordPress или Битрикс, поэтому интеграция с этими CMS — частая задача.

Интеграция с WordPress:

Для WordPress я использую плагин ElasticPress или пишу кастомную интеграцию. Вот базовый класс для работы с Elasticsearch в WordPress:

client = \Elasticsearch\ClientBuilder::create()
            ->setHosts([get_option('elasticsearch_host', 'localhost:9200')])
            ->setBasicAuthentication(
                get_option('elasticsearch_user', 'elastic'),
                get_option('elasticsearch_pass', '')
            )
            ->build();
            
        // Хуки WordPress
        add_action('save_post', [$this, 'index_post']);
        add_action('delete_post', [$this, 'delete_post']);
        add_filter('posts_search', [$this, 'elasticsearch_search'], 10, 2);
    }
    
    public function index_post($post_id) 
    {
        $post = get_post($post_id);
        
        if (!$post || $post->post_status !== 'publish') {
            return;
        }
        
        $document = [
            'id' => $post->ID,
            'title' => $post->post_title,
            'content' => wp_strip_all_tags($post->post_content),
            'excerpt' => $post->post_excerpt,
            'post_type' => $post->post_type,
            'post_date' => $post->post_date,
            'categories' => wp_get_post_categories($post->ID),
            'tags' => wp_get_post_tags($post->ID, ['fields' => 'names'])
        ];
        
        $this->client->index([
            'index' => 'wordpress_posts',
            'id' => $post_id,
            'body' => $document
        ]);
    }
    
    public function elasticsearch_search($search, $wp_query) 
    {
        global $wpdb;
        
        if (empty($wp_query->query_vars['s'])) {
            return $search;
        }
        
        $query = $wp_query->query_vars['s'];
        
        try {
            $response = $this->client->search([
                'index' => 'wordpress_posts',
                'body' => [
                    'query' => [
                        'multi_match' => [
                            'query' => $query,
                            'fields' => ['title^3', 'content', 'excerpt^2']
                        ]
                    ],
                    'size' => get_option('posts_per_page', 10)
                ]
            ]);
            
            $post_ids = array_map(function($hit) {
                return $hit['_source']['id'];
            }, $response['hits']['hits']);
            
            if (empty($post_ids)) {
                return " AND 1=0 "; // Ничего не найдено
            }
            
            return " AND {$wpdb->posts}.ID IN (" . implode(',', $post_ids) . ") ";
            
        } catch (Exception $e) {
            error_log('Elasticsearch error: ' . $e->getMessage());
            return $search; // Возвращаем стандартный поиск при ошибке
        }
    }
}

Интеграция с Битрикс:

В Битрикс я обычно создаю компонент для поиска и настраиваю обработчики событий:

addEventHandler('iblock', 'OnAfterIBlockElementAdd', 'indexProductToElasticsearch');
$eventManager->addEventHandler('iblock', 'OnAfterIBlockElementUpdate', 'indexProductToElasticsearch');
$eventManager->addEventHandler('iblock', 'OnAfterIBlockElementDelete', 'deleteProductFromElasticsearch');

function indexProductToElasticsearch(&$arFields) 
{
    if ($arFields['IBLOCK_ID'] != 1) { // ID каталога товаров
        return;
    }
    
    $element = CIBlockElement::GetByID($arFields['ID'])->Fetch();
    $properties = CIBlockElement::GetProperty($arFields['IBLOCK_ID'], $arFields['ID']);
    
    $document = [
        'id' => $element['ID'],
        'title' => $element['NAME'],
        'description' => strip_tags($element['DETAIL_TEXT']),
        'price' => 0,
        'category_id' => $element['IBLOCK_SECTION_ID'],
        'active' => $element['ACTIVE'] === 'Y'
    ];
    
    // Добавляем свойства
    while ($prop = $properties->Fetch()) {
        if ($prop['CODE'] === 'PRICE') {
            $document['price'] = (float)$prop['VALUE'];
        }
        if ($prop['CODE'] === 'BRAND') {
            $document['brand'] = $prop['VALUE'];
        }
    }
    
    // Отправляем в Elasticsearch
    $elasticsearch = new ElasticsearchService();
    $elasticsearch->indexDocument('products', $element['ID'], $document);
}

Честно говоря, интеграция с Битрикс всегда сложнее из-за особенностей архитектуры, но результат стоит того. На одном проекте каталог на 80 тысяч товаров стал работать в разы быстрее.

Для интеграции с другими CMS процесс похожий — главное правильно отловить события создания/изменения/удаления контента и синхронизировать с Elasticsearch. Более подробно о различных интеграциях я писал в статье о настройке API интеграций.

⚠️
Важно: При большом объёме изменений используйте очереди для индексации. Прямая синхронная индексация может замедлить работу сайта.

Частые проблемы и их решения

За годы работы с Elasticsearch я сталкивался с десятками проблем. Вот самые частые и способы их решения.

Проблема: OutOfMemoryError

Симптомы: кластер падает с ошибкой памяти, поиск не работает.

Решение: увеличить heap size или оптимизировать запросы. Проверьте настройки в `/etc/elasticsearch/jvm.options`:

# Посмотреть текущее использование памяти
curl -X GET "localhost:9200/_cat/nodes?v&h=name,heap.percent,heap.current,heap.max"

# Очистить кеши
curl -X POST "localhost:9200/_cache/clear"

# Принудительная сборка мусора
curl -X POST "localhost:9200/_nodes/_local/jvm"

Проблема: Медленные запросы

Часто виноваты wildcard-запросы или отсутствие фильтров. Я всегда проверяю explain API:

GET /products/_search
{
  "explain": true,
  "query": {
    "match": {
      "title": "медленный запрос"
    }
  }
}

Или использую профайлер:

GET /products/_search
{
  "profile": true,
  "query": {
    "match": {
      "title": "телефон"
    }
  }
}

Проблема: Индексы становятся read-only

Elasticsearch автоматически блокирует запись при нехватке места на диске. Решение:

# Проверить статус индексов
curl -X GET "localhost:9200/_cat/indices?v"

# Разблокировать индексы
curl -X PUT "localhost:9200/_all/_settings" -H 'Content-Type: application/json' -d '{
  "index.blocks.read_only_allow_delete": null
}'

# Освободить место и изменить настройки watermark
curl -X PUT "localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d '{
  "transient": {
    "cluster.routing.allocation.disk.watermark.low": "85%",
    "cluster.routing.allocation.disk.watermark.high": "90%"
  }
}'

Проблема: Неточные результаты поиска

Обычно проблема в неправильном анализаторе или маппинге. Тестирую анализатор:

GET /products/_analyze
{
  "analyzer": "russian_analyzer",
  "text": "телефоны Samsung"
}

И проверяю, как индексируется конкретное поле:

GET /products/_analyze
{
  "field": "title",
  "text": "Samsung Galaxy S24"
}

На одном проекте поиск по слову "телефон" не находил "телефоны" — оказалось, забыли подключить морфологический анализатор.

Проблема: Высокая нагрузка на CPU

Часто причина в слишком частом refresh индекса или большом количестве мелких сегментов:

# Принудительное слияние сегментов
curl -X POST "localhost:9200/products/_forcemerge?max_num_segments=1"

# Изменение частоты refresh
curl -X PUT "localhost:9200/products/_settings" -H 'Content-Type: application/json' -d '{
  "index": {
    "refresh_interval": "30s"
  }
}'

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

Если у вас возникают сложности с настройкой поиска или нужна помощь с оптимизацией производительности сайта, обращайтесь за поддержкой Битрикс или доработкой сайта — поможем настроить всё правильно с первого раза.

Нужна помощь с настройкой поиска на сайте?

Наши эксперты помогут внедрить и настроить Elasticsearch для максимальной скорости поиска на вашем проекте.

П
Павел
Веб-разработчик · 10+ лет опыта · Bitrix, WordPress, Laravel

Читайте также

Как перенести сайт на другую CMS Настройка логов сайта: мониторинг и анализ ошибок в 2026 Автоматическое обновление контента: настройка и инструменты