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

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка Elasticsearch для поиска веб-приложения
Сложная
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

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

Elasticsearch — распределённый поисковый движок на базе Apache Lucene. Берут его, когда стандартный ILIKE '%query%' в PostgreSQL перестаёт справляться: полнотекстовый поиск с релевантностью, фасетная фильтрация, автодополнение, географический поиск — всё это нативные возможности ES.

Установка 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 — включить

JVM Heap

# /etc/elasticsearch/jvm.options.d/heap.options
# Не более 50% RAM, не более 32GB (compressed OOP threshold)
-Xms4g
-Xmx4g

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

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

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" }]
}

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

Логическая репликация через Debezium + Kafka — промышленное решение. Для начала достаточно проще:

// 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`)
  }
}

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

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

# Статистика индекса
curl -s "http://localhost:9200/products/_stats?pretty" | jq '.indices.products.total'

# Медленные запросы
curl -s "http://localhost:9200/products/_settings" -XPUT -H 'Content-Type: application/json' -d '{
  "index.search.slowlog.threshold.query.warn": "2s",
  "index.search.slowlog.threshold.query.info": "500ms"
}'

Сроки

Настройка Elasticsearch, создание индексов с кастомными анализаторами и интеграция с приложением: 3–5 дней. Настройка автодополнения, фасетного поиска и синхронизации из PostgreSQL: ещё 3–5 дней. Кластер из трёх нод с Kibana и мониторингом: 1–2 недели.