Головна / Статті / Чому запит у Weaviate гальмує: профілювання за етапами

Чому запит у Weaviate гальмує: профілювання за етапами

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

1126 слів

Фраза «запит повільний» майже нічого не каже інженерові. Це скарга на симптом. Повільний порівняно з чим і на якому кроці? Фільтр за метаданими, векторний індекс, читання з диска об'єктів результату чи оцінювання за ключовими словами? Доки час не прив'язано до одного з цих етапів, будь-яка зміна — постріл навмання: додати пам'яті, підкрутити ef, зменшити limit. Іноді щось допомагає, зазвичай нічого, а справжня причина так і лишається невідомою.

Тепер у Weaviate з'явилася функція, націлена саме на цю сліпу пляму. Query Profiling показує за етапами, на що конкретний повільний запит витратив час.

Чому журналу повільних запитів замало

Логування повільних запитів у Weaviate існує давно. Задаєте дві змінні оточення, перезапускаєте вузол, і кожен запит, що перевищив поріг за часом, потрапляє в логи з подробицями про таймінги. Для одного завдання це добре підходить: непомітно відстежувати регресії на всьому парку серверів протягом тижнів і місяців.

Але для частішої ситуації, коли конкретний запит гальмує просто зараз і причина потрібна негайно, інструмент поганий. Вирізняються чотири вади:

  • Потрібен перезапуск, а перезапуск вузла очищає сторінковий кеш операційної системи. Перші запити після нього читають холодні дані з диска, тож вимір відображає перезапуск, а не сам запит.
  • Він працює заднім числом. Запис з'являється лише тоді, коли запит уже виявився повільним, тому не можна націлитися на той запит, що зараз перед вами. Лишається тільки чекати, поки він знову загальмує.
  • Логування відбувається на рівні вузлів. Один запит розходиться по шардах на кількох машинах, і кожна записує лише свою частину, тож повну картину доводиться збирати вручну.
  • Це тимчасовий перемикач, про який треба пам'ятати й який треба повернути назад. Оскільки він живе поза конфігурацією розгортання, він легко розсинхронізовується.

Профілювання на рівні запиту

Профілювання запитів перевертає цю модель. Замість глобального налаштування, яке чекає на проблеми, профілювання вмикається для окремого запиту, а розбивка за часом повертається просто у відповіді. Жодних змінних оточення, перезапуску, порогів і прибирання після.

У Python для цього потрібен один імпорт класу метаданих.

from weaviate.classes.query import MetadataQuery

І прапорець у самому запиті.

response = collection.query.near_vector(
    near_vector=[0.1, 0.2, 0.3],
    limit=10,
    return_metadata=MetadataQuery(query_profile=True),
)for shard in response.query_profile.shards:
    print(shard.name, shard.node, shard.searches)

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

Підтримуються офіційні клієнти для Python, JavaScript і TypeScript, Java (v6) та C#, а також чистий gRPC. Функція стала загальнодоступною у Weaviate v1.38. З вимкненим прапорцем накладні витрати — одна перевірка булевого значення; з увімкненим рушій додає читання таймерів вартістю в мікросекунди. Це інструмент для налагодження, тож тримайте його подалі від гарячого шляху в продакшені.

Як читати профіль

Результати згруповано за шардами, а етапи, які не виконувалися, пропускаються. Тому звичайний векторний пошук дає інший профіль, ніж пошук BM25, а гібридні запити мають розділи для обох. Ось поля, які варто знати:

  • total_took — реальний час пошуку на цьому шарді, а всі інші цифри пояснюють його частини.
  • objects_took вимірює читання підсумкових об'єктів з диска, цей крок називають гідратацією. Високе значення свідчить, що запит упирається в диск: подивіться на сторінковий кеш, limit і розмір корисного навантаження.
  • filters_build_allow_list_took разом із filters_ids_matched показують, скільки часу пішло на перетворення умови where на список відповідних ID, і розмір цього списку. Величезний список означає, що фільтр надто широкий; короткий список, на який усе одно пішло багато часу, натякає на повільне читання інвертованого індексу з диска.
  • vector_search_took і knn_search_layer_0_took описують обхід HNSW. Граф живе в оперативній пам'яті, тому велике значення тут означає навантаження на CPU, а не на диск. Налаштуйте ef, розмірність векторів і стратегію фільтрації, перш ніж замовляти нове залізо.
  • knn_search_rescore_took з'являється, лише коли ввімкнено стиснення. Цей етап читає вектори повної точності, щоб переоцінити стиснених кандидатів, і це читання може дійти до диска.
  • kwd_3_term_time і kwd_4_bmw_time — два таймери пошуку за ключовими словами, які варто запам'ятати. Перший охоплює читання списків входжень для кожного терміна й залежить від кількості термінів і швидкості диска. Другий — оцінювання BlockMax WAND, яке зростає на довгих списках входжень і за слабкої вибірковості.

Три профілі, три різні рішення

Перший випадок: шард, де загальний час трохи менший за 50 мс.

total_took: 48.2ms
vector_search_took: 8.4ms
filters_build_allow_list_took: 2.1ms
objects_took: 36.8ms

Фільтрація й пошук тут дешеві. Близько трьох чвертей часу йде на завантаження об'єктів з диска. Варто подивитися на сторінковий кеш і сховище на цьому вузлі, а менший limit чи легше корисне навантаження можуть допомогти.

Другий випадок: запит із важким фільтром.

total_took: 61.5ms
filters_build_allow_list_took: 41.2ms
filters_ids_matched: 12,840,000
objects_took: 3.9ms

Дванадцять мільйонів збіглих ID. Це проблема не диска й не заліза. Фільтр просто надто широкий. Звузьте його або застосовуйте широку умову інакше, а не як попередній фільтр по всьому набору.

Третій випадок: стиснений векторний індекс.

vector_search_took: 35.1ms
knn_search_layer_0_took: 9.2ms
knn_search_rescore_took: 25.4ms

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

У трьох рішень немає нічого спільного. Без розбивки всі три надійшли б як та сама скарга: запит повільний.

Як закрити прогалину у видимості

Налаштування продуктивності векторних баз давно має проблему з видимістю. Рушій завжди знав, куди йде час; складно було дістати це знання. Профілювання запитів закриває цю прогалину мінімальною зміною: один прапорець, один запит, одна відповідь. Кажуть, команда solution engineering у Weaviate насамперед береться саме за нього, коли клієнт скаржиться на повільність: воно перетворює розмиту скаргу на конкретний етап із конкретним рішенням.

Командам, які використовують Weaviate у продакшені, варто додати query_profile=True до свого набору інструментів для налагодження. Наступного разу, коли хтось скаже, що запит повільний, відповіддю буде виміряне число, а не здогад. Подробиці є в повному анонсі під назвою Query Profiling: See Where a Slow Query Spends Its Time і на сторінці про профілювання запитів в офіційній документації.