Заметила, что по мере усложнения поискового пайплайна метрика вроде
p95 latency стала недостаточной. На одном из последних бенчей время ответа выросло на 30%, но по одной этой цифре невозможно понять, где именно возникла задержка, так как она может проявится как в разборе запроса, отборе кандидатов или ранжировании так и в попытке попасть в fast path.Снаружи поиск выглядит как один вызов, но внутри мы выбираем разные стратегии в зависимости от семантики запроса. Поэтому даже похожие запросы могут выполняться по разному:
+postgres +wal
"postgres wal"
title:postgres
Они по-разному парсятся в AST и дальше попадают в разные стратегии выполнения.
Поиск по одному слову обычно прост: достаточно нормализовать токен, найти его в индексе и прочитать список документов. Стоимость здесь в основном зависит от количества кандидатов (
postings list).Поиск фразы дороже: нужно проверить не только наличие слов, но и их позиции, поэтому нужен
positional index. Например, для "hotel barge" важно убедиться, что barge действительно идет после hotel и в нужном порядке.Префиксный поиск, например
bar*, требует, чтобы индекс умел определять слова с нужным началом, и простого поиска по ключу здесь недостаточно.В
boolean запросах, например +postgres +wal, самая дорогая часть здесь скорее в выборе стратегии: нужно искать пересечения между списками, хранением списка кандидатов, определит, использовать ли WAND или уйти в на фоллбек ветку.Из-за этого метрика вида latency не отвечает, что именно стало дороже. Поэтому я добавила сбор диагностики, которую можно явно включить через контекст:
ctx := fts.WithDiagnostics(context.Background())
res, _ := engine.SearchDocuments(ctx, "postgres wal checkpoint", 10)
Диагностика показывает тип запроса, выбранную стратегию выполнения, причину пропуска fast path, число обращений к индексу, число прочитанных доков и время по этапам.
То есть получилось что-то вроде упрощенного
execution plan для поиска:
strategy: bool_fallback
skip reason: non-term boolean clause
posting entries read: 180_000
candidate docs: 24_000
returned docs: 10
И вот мы уже имеем точку для отладки. Можно быстро понять, не сработал ли fast path, не прочитал ли движок слишком большой список кандидатов и не раздулся ли он до лимита.
Например,
alpha beta gamma может выполниться через WAND, а запрос "alpha beta" gamma уже уходит в bool_fallback, потому что содержит phrase clause и не попадает в оптимизированный WAND. По диагностике видно, что меняется стратегия выполнения, растут posting entries read и candidate docs, хотя количетсво документов в результате остается тем же. Значит, причина задержки не в размере выдачи, а в том, что движок потерял WAND пропуск нерелевантных по весу доков и мы вынуждены обработать намного больше кандидатов.Про бенчи: если смотреть только на задержку, можно не заметить, что поиск стал быстрее, но хуже по качеству. Поэтому в бенчах стоит смотреть как минимум на две группы метрик.
Пр качеству:
-
nDCG показывает, насколько хорошо ранжирована верхняя часть выдачи.-
MRR показывает, насколько рано пользователь увидит первый правильный результат.-
Recall показывает, какую долю релевантных документов поиск вообще нашел.По стоимости:
-
latency-
posting entries read
- index lookups-
candidate docs
- распределение execution strategiesПоэтому если
latency p95 улучшился, но nDCG упало, можно сделать вывод, что мы размениваем время в счет качества выдачи.На мысль изначально меня натолкнула одна бизнесовая статья от VM про отдельный "observability" слой для AI агентов.
А вообще мотивация сделать что то похожее была еще в том, что такая диагностика делает разросшийся поисковый движок объяснимым: показывает не просто то, что запрос стал медленнее, а почему именно это произошло и сколько стоит каждый этап. Подробнее прилагаю пр, где можно посмотреть само решение. Также добавила слой с in memory аггрегатором статы в отдельном пакете
ftsstats, чтобы не нарушать текущую пакетную структуру проекта.#fts #projects
