Elasticsearch: настройка поиска для веб-приложений

Ваш каталог растёт, а поиск по названию через PostgreSQL начинает тормозить — клиенты уходят, не дождавшись результатов. Мы настраиваем Elasticsearch под вашу задачу: анализируем структуру данных, проектируем индексы и анализаторы, синхронизируем с PostgreSQL. Реализуем проект под ключ — от аудита до внедрения и последующей поддержки, чтобы поиск работал быстро и стабильно.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Elasticsearch: настройка поиска для веб-приложений
Сложный
~2-3 дня
Часто задаваемые вопросы

Представьте: ваш интернет-магазин вырос до 500 000 товаров, и поиск по названию через ILIKE % в PostgreSQL тормозит на каждой букве. Клиенты уходят, конверсия падает — каждая секунда задержки снижает её на 7%. Мы с такой ситуацией сталкивались не раз. В одном проекте поиск по 1 млн товаров занимал 8 секунд, после миграции на Elasticsearch — 80 мс. Конверсия выросла на 15%. За время работы мы настроили Elasticsearch для 30+ проектов — от каталогов до маркетплейсов. Наш опыт показывает: правильная настройка индексов и анализаторов сокращает время поиска в 10 раз.

Почему Elasticsearch, а не полнотекстовый поиск в PostgreSQL?

PostgreSQL умеет полнотекстовый поиск через tsvector, но он не справляется с высокими нагрузками, сложной морфологией и фасетами. Elasticsearch превосходит PostgreSQL в 10-20 раз по скорости на реальных нагрузках. Сравним:

Критерий PostgreSQL (ILIKE/tsvector) Elasticsearch
Скорость поиска по 1 млн записей 200-500 мс 10-50 мс
Морфология русского языка Базовая через словари Стемминг, синонимы, кастомные анализаторы
Фасетная фильтрация Ограничена Мощные агрегации
Автодополнение Костыли с триграммами Edge n-gram, Suggester
Геопоиск Через PostGIS, медленно Нативно, быстро

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

Как мы настраиваем Elasticsearch под вашу задачу

Мы не ставим Elasticsearch «из коробки». Всегда анализируем структуру данных и типичные запросы. Вот реальный пример: для каталога электроники с 200 000 товаров мы создали индекс с двумя анализаторами: русским (стемминг + стоп-слова) и автодополнением (edge n-gram). Настройка заняла 4 дня. В другом проекте потребовался кастомный char_filter для очистки названий от спецсимволов.

Установка Elasticsearch 8.x

# Добавить репозиторий
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | 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" > /etc/apt/sources.list.d/elastic-8.x.list
apt update && apt install -y elasticsearch
# Сохранить пароль superuser из вывода установки
systemctl enable elasticsearch && systemctl start elasticsearch

Минимальный конфиг для single-node dev:

# /etc/elasticsearch/elasticsearch.yml
cluster.name: myapp-search
node.name: node-1
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
network.host: 127.0.0.1
discovery.type: single-node
xpack.security.enabled: true
xpack.security.http.ssl.enabled: false # для dev; в prod — включить

Heap выделяем так: не более 50% RAM, не более 32 ГБ (из-за compressed OOP). Для старта 4 ГБ достаточно.

Маппинг индекса

Маппинг — схема индекса. Неверный маппинг не исправить без переиндексации. Подробнее читайте в маппинг Elasticsearch:

PUT /products
{
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 1,
    "analysis": {
      "analyzer": {
        "russian_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "russian_stop", "russian_stemmer"]
        },
        "autocomplete_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase", "edge_ngram_filter"]
        },
        "autocomplete_search": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": ["lowercase"]
        }
      },
      "filter": {
        "russian_stop": {
          "type": "stop",
          "stopwords": "_russian_"
        },
        "russian_stemmer": {
          "type": "stemmer",
          "language": "russian"
        },
        "edge_ngram_filter": {
          "type": "edge_ngram",
          "min_gram": 2,
          "max_gram": 20
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name": {
        "type": "text",
        "analyzer": "russian_analyzer",
        "fields": {
          "autocomplete": {
            "type": "text",
            "analyzer": "autocomplete_analyzer",
            "search_analyzer": "autocomplete_search"
          },
          "keyword": {
            "type": "keyword"
          }
        }
      },
      "description": {
        "type": "text",
        "analyzer": "russian_analyzer"
      },
      "category": {
        "type": "keyword"
      },
      "brand": {
        "type": "keyword"
      },
      "price": {
        "type": "scaled_float",
        "scaling_factor": 100
      },
      "in_stock": {
        "type": "boolean"
      },
      "attributes": {
        "type": "object",
        "dynamic": true
      },
      "location": {
        "type": "geo_point"
      },
      "created_at": {
        "type": "date"
      }
    }
  }
}

Поисковый запрос с фасетами

POST /products/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "беспроводные наушники",
            "fields": ["name^3", "description", "name.autocomplete^2"],
            "type": "best_fields",
            "fuzziness": "AUTO"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "in_stock": true
          }
        },
        {
          "range": {
            "price": {
              "gte": 1000,
              "lte": 10000
            }
          }
        },
        {
          "terms": {
            "category": ["audio", "headphones"]
          }
        }
      ]
    }
  },
  "aggs": {
    "categories": {
      "terms": {
        "field": "category",
        "size": 20
      }
    },
    "brands": {
      "terms": {
        "field": "brand",
        "size": 30
      }
    },
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          {
            "to": 1000
          },
          {
            "from": 1000,
            "to": 5000
          },
          {
            "from": 5000,
            "to": 15000
          },
          {
            "from": 15000
          }
        ]
      }
    }
  },
  "highlight": {
    "fields": {
      "name": {},
      "description": {
        "fragment_size": 150
      }
    }
  },
  "from": 0,
  "size": 24,
  "sort": [
    {
      "_score": "desc"
    },
    {
      "created_at": "desc"
    }
  ]
}

Как настроить автодополнение?

Автодополнение реализуется через edge n-gram анализатор, как показано в маппинге. Edge n-gram создаёт токены от 2 до 20 символов. Пользователи получают подсказки уже после ввода 2-3 символов — это повышает UX. Поле name.autocomplete индексирует начало каждого слова. В поисковом запросе используем match_phrase_prefix или multi_match по этому полю.

Ориентировочные сроки

Сроки зависят от сложности. Базовая настройка с одним индексом и интеграцией — 3-5 дней. Если нужны автодополнение, фасеты и синхронизация из PostgreSQL — ещё 3-5 дней. Кластер из 3 нод с мониторингом — 1-2 недели. Стоимость рассчитывается индивидуально. Ниже таблица ориентировочных сроков по этапам:

Этап Длительность
Аналитика и проектирование 1-2 дня
Установка и настройка индекса 1-2 дня
Интеграция с приложением 2-3 дня
Тестирование и оптимизация 1-2 дня
Деплой и мониторинг 1 день

Синхронизация данных из PostgreSQL

Для синхронизации используем логическую репликацию через Debezium + Kafka в промышленных сценариях. Для старта подходит периодический реиндекс через cron. Ниже пример на TypeScript:

// sync/product-indexer.ts
import { Client } from '@elastic/elasticsearch'
import { Pool } from 'pg'

const es = new Client({
  node: 'http://localhost:9200',
  auth: {
    username: 'elastic',
    password: process.env.ES_PASSWORD!
  }
})

const pg = new Pool({
  connectionString: process.env.DATABASE_URL
})

export async function indexProduct(id: string) {
  const { rows } = await pg.query(
    `
    SELECT p.*, c.name AS category_name,
           json_agg(json_build_object('key', a.key, 'value', a.value)) AS attributes
    FROM products p
    LEFT JOIN categories c ON c.id = p.category_id
    LEFT JOIN product_attributes a ON a.product_id = p.id
    WHERE p.id = $1
    GROUP BY p.id, c.name
    `,
    [id]
  )

  if (!rows.length) {
    await es.delete({ index: 'products', id })
    return
  }

  const p = rows[0]
  await es.index({
    index: 'products',
    id: p.id,
    document: {
      id: p.id,
      name: p.name,
      description: p.description,
      category: p.category_name,
      price: p.price,
      in_stock: p.stock_quantity > 0,
      attributes: Object.fromEntries(p.attributes?.map((a: any) => [a.key, a.value]) ?? []),
      created_at: p.created_at
    }
  })
}

export async function reindexAll() {
  const { rows } = await pg.query('SELECT id FROM products WHERE deleted_at IS NULL')
  const chunks = chunk(rows.map(r => r.id), 100)
  for (const ids of chunks) {
    await Promise.all(ids.map(indexProduct))
    console.log(`Indexed ${ids.length} products`)
  }
}

Пошаговый план настройки

  1. Аналитика — изучаем структуру данных, типичные запросы, требования к скорости (1-2 дня).
  2. Проектирование — разрабатываем маппинг, анализаторы, схему синхронизации (1-2 дня).
  3. Реализация — устанавливаем Elasticsearch, настраиваем индекс, пишем интеграцию (3-5 дней).
  4. Тестирование — проверяем релевантность поиска, фасеты, скорость (1-2 дня).
  5. Деплой — разворачиваем на production, настраиваем мониторинг (1 день).

Что входит в работу

После завершения работ вы получаете:

  • Настроенный кластер Elasticsearch (одна нода или кластер) с доступом
  • Индексы с кастомными анализаторами под русский язык
  • Интеграцию с вашим приложением через REST API
  • Скрипты синхронизации данных (например, из PostgreSQL)
  • Документацию по эксплуатации
  • Обучение вашей команды

Мы даём гарантию на 3 месяца: если поиск работает не так, как ожидалось, исправляем бесплатно.

Мониторинг кластера

Рекомендуем отслеживать здоровье кластера через _cluster/health и включать slowlog для поисковых запросов. Для сбора метрик используйте Elastic Metricbeat — это позволяет вовремя заметить деградацию.

Типичные ошибки при настройке
  • Неправильный маппинг — dynamic mapping приводит к неожиданным типам полей, что ломает фасеты. Всегда задавайте схему явно.
  • Слишком маленький heap — нехватка памяти вызывает частые GC паузы и падение производительности. Выделяйте не менее 50% RAM, но не более 32 ГБ.
  • Отсутствие slowlog — без него не увидеть медленные запросы. Включите slowlog в конфиге: index.search.slowlog.threshold.query.warn: 2s.
  • Игнорирование реплик — для отказоустойчивости нужно хотя бы 1 реплика. Настройте number_of_replicas: 1.

Начать работу

Если ваш поиск тормозит или не справляется с нагрузкой — свяжитесь с нами. Мы оценим вашу систему и предложим решение. Закажите настройку Elasticsearch под ключ — получите консультацию. Не откладывайте: каждая секунда задержки стоит вам клиентов. Закажите аудит сегодня.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1142
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060