Elasticsearch краще ніж PostgreSQL для повнотекстового пошуку: він швидший у 10–20 разів і має потужні можливості фасетів та автодоповнення. Ми маємо 5+ років досвіду та 30+ успішних проєктів з налаштування Elasticsearch. Elasticsearch лучше PostgreSQL в 10–20 разів за швидкістю пошуку.
Уявіть: ваш інтернет-магазин виріс до 500 000 товарів, і пошук за назвою через ILIKE % в PostgreSQL гальмує на кожній літері. Клієнти йдуть, конверсія падає — кожна секунда затримки знижує її на 7%. Ми з такою ситуацією стикалися не раз. В одному проєкті пошук по 1 млн товарів займав 8 секунд, після міграції на Elasticsearch — 80 мс. Конверсія зросла на 15%. За час роботи ми налаштували Elasticsearch для 30+ проєктів — від каталогів до маркетплейсів. Наш досвід показує: правильне налаштування індексів та аналізаторів скорочує час пошуку в 10 разів.
Чому Elasticsearch, а не повнотекстовий пошук у PostgreSQL?
PostgreSQL вміє повнотекстовий пошук через tsvector, але він не справляється з високими навантаженнями, складною морфологією та фасетами. Індексування даних у Elasticsearch виконується через мапінги, що задають структуру. Пошуковий двигун Elasticsearch забезпечує швидкий повнотекстовий пошук. Фасетний пошук дозволяє фільтрувати результати за категоріями, цінами тощо. Синхронізація Elasticsearch з PostgreSQL реалізується через логічну реплікацію. Порівняємо:
| Критерій | 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–2 дні).
- Проєктування — розробляємо мапінг, аналізатори, схему синхронізації (1–2 дні).
- Реалізація — встановлюємо Elasticsearch, налаштовуємо індекс, пишемо інтеграцію (3–5 днів).
- Тестування — перевіряємо релевантність пошуку, фасети, швидкість (1–2 дні).
- Деплой — розгортаємо на 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 під ключ — отримайте консультацію безкоштовно. Не відкладайте: кожна секунда затримки коштує вам клієнтів. Замовте аудит сьогодні.







