Оптимізація продуктивності Elasticsearch (шарди, репліки, refresh_interval)

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Оптимізація продуктивності Elasticsearch (шарди, репліки, refresh_interval)
Складна
~2-3 робочих дні
Часті питання

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

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Оптимізація продуктивності Elasticsearch (шарди, реплики, refresh_interval)

Повільний Elasticsearch — майже завжди результат неправильних налаштувань, а не недостатку залізо. Зайві шарди вбивають продуктивність надійніше, ніж слабкі процесори. Занадто частий refresh робить індексацію у 3–5 разів повільнішою, ніж потрібно. Оптимізація Elasticsearch — це передусім правильне проектування, а потім уже тюнінг залізо.

Шарди: головна точка оптимізації

Кожний шард — це окремий екземпляр Lucene індексу з власними файловими дескрипторами, JVM об'єктами, overhead на heap. На кластері з 5 вузлами мати 500 маленьких індексів по 50 шардів кожен = 25 000 шардів = кластер повзе.

Правило: 1 шард = 10–50 GB даних. Менше — шарди занадто маленькі (overhead домінує над даними). Більше — складно перебалансувати при додаванні вузла.

Максимум шардів на 1 GB heap: ~20 шардів. При heap 16 GB = не більше 320 шардів на вузол.

Перевірити статистику шардів:

# Розмір шардів та їх розподіл
curl -u elastic:pw "http://localhost:9200/_cat/shards?v&h=index,shard,prirep,state,docs,store,node"

# Скільки шардів на вузол
curl -u elastic:pw "http://localhost:9200/_cat/nodes?v&h=name,shards,diskUsed,heapPercent"

Зменшення кількості шардів через shrink API:

# Спочатку відключити запис і перемістити усі шарди на один вузол
PUT /products/_settings
{
  "settings": {
    "index.routing.allocation.require._name": "es-node-01",
    "index.blocks.write": true
  }
}

# Shrink до 1 шарда
POST /products/_shrink/products_shrunk
{
  "settings": {
    "index.number_of_shards": 1,
    "index.number_of_replicas": 1,
    "index.routing.allocation.require._name": null,
    "index.blocks.write": null
  }
}

refresh_interval

Elasticsearch за умовчанням робить refresh кожну секунду — створює новий сегмент Lucene з буфера в пам'яті та робить документи доступними для пошуку. Кожний refresh — файлові операції, створення сегменту, навантаження на IO.

Для real-time пошуку (чат, сповіщення) — залишити 1s.

Для аналітики, логів, ETL — збільшити до 30s–300s:

PUT /logs-*/_settings
{
  "index.refresh_interval": "60s"
}

При масовому завантаженню даних — тимчасово відключити:

PUT /products/_settings
{
  "index.refresh_interval": "-1"
}

# Завантажуємо дані...

PUT /products/_settings
{
  "index.refresh_interval": "1s"
}

POST /products/_refresh

Прирріст швидкості індексації при refresh_interval: -1 vs 1s — 3–5x.

Merge Policy і forcemerge

Lucene періодично об'єднує малі сегменти у великі (merge). Це звільняє місце від видалених документів та прискорює пошук (менше сегментів = менше ітерацій). За умовчанням відбувається у фоні, але створює IO навантаження.

Для read-only індексів (архівні дані, завершені rolling-індекси) — форсувати merge до 1 сегменту:

POST /logs-2024.01.01/_forcemerge?max_num_segments=1

Після forcemerge пошук по індексу значно швидший, а розмір зменшується на 20–40% за рахунок видалення tombstone-записів для видалених docs.

Не запускайте forcemerge на активно індексуємих індексах — створює величезну IO навантаження.

Реплики при індексації

Реплика — синхронна копія шарда на іншому вузлі. Кожний записаний документ індексується в primary + усі replica шарди. При 2 репліках — утроєне навантаження на диск при індексації.

При масовому завантаженню даних у новий індекс:

// 1. Встановимо 0 реплік на час завантаження
PUT /products/_settings
{ "index.number_of_replicas": 0 }

// 2. Завантажуємо дані
// ...

// 3. Відновлюємо реплики
PUT /products/_settings
{ "index.number_of_replicas": 1 }

Прирістання швидкості — 2–3x при 1 реплиці, 3–4x при 2 репліках.

Bulk API

Індексувати по одному документу — антипаттерн. Розмір пакету залежить від розміру документів: ціль — пакети по 5–15 MB.

from elasticsearch import Elasticsearch
from elasticsearch.helpers import bulk, parallel_bulk

es = Elasticsearch([...])

def generate_actions(data):
    for item in data:
        yield {
            "_index": "products",
            "_id": item["id"],
            "_source": item,
        }

# Паралельний bulk з кількома потоками
success, errors = 0, 0
for ok, info in parallel_bulk(
    es,
    generate_actions(data),
    thread_count=4,
    chunk_size=500,
    max_chunk_bytes=10 * 1024 * 1024,  # 10 MB
    raise_on_error=False
):
    if ok:
        success += 1
    else:
        errors += 1
        print(f"Error: {info}")

Оптимізація запитів

Filter vs. Query: використовуйте filter скрізь, де не потрібен score. Фільтри кешуються на рівні шарда, не впливають на scoring.

// Повільно (scoring + no cache)
{
  "query": {
    "term": { "is_active": true }
  }
}

// Швидко (no scoring + cached)
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "is_active": true } }
      ]
    }
  }
}

Wildcard і regexp — дорогі операції, особливо з leading wildcard (*term). Уникайте або замініть на edge N-gram аналіз.

Deep pagination: from: 10000 — дорого. ES повинен зібрати 10 000 + size документів з кожного шарда. Використовуйте search_after для пагінації:

{
  "query": { "match_all": {} },
  "sort": [
    { "created_at": "desc" },
    { "_id": "asc" }
  ],
  "search_after": ["2024-01-15T10:00:00", "abc123"],
  "size": 20
}

Моніторинг продуктивності запитів

Profile API — детальний розбір виконання запиту:

POST /products/_search
{
  "profile": true,
  "query": {
    "match": { "title": "ноутбук" }
  }
}

У відповіді — breakdown по кожному шарду: час створення запиту, scoring, fetch. Дозволяє знайти вузьке місце.

Hot Threads API — що робить JVM:

curl -u elastic:pw "http://localhost:9200/_nodes/hot_threads"

Heap і GC

При heap > 85% включається агресивний GC, запити починають гальмувати. Ознаки: GCOverheadLimit виключення в логах, різке снження throughput.

Дивитись GC статистику:

curl -u elastic:pw "http://localhost:9200/_nodes/stats/jvm?pretty" | \
  jq '.nodes[] | {name: .name, heap_used_percent: .jvm.mem.heap_used_percent, gc_young: .jvm.gc.collectors.young.collection_time_in_millis}'

G1GC (за умовчанням в JDK 14+) — кращий вибір для ES. У jvm.options:

-XX:+UseG1GC
-XX:G1ReservePercent=25
-XX:InitiatingHeapOccupancyPercent=30

Таймлайн

Аудит конфігурації існуючого кластера з рекомендаціями — 1 робочий день. Оптимізація шардингу, refresh_interval, bulk-індексації — 2–3 дні. Глибока оптимізація запитів з profiling на реальному навантаженні — додатково 1–2 дні.