TGViewer
Daria’s room Daria’s room @dariasroom · 1.2K subscribers
Post #108 1.25K
"Explain" plan для FTS

Заметила, что по мере усложнения поискового пайплайна метрика вроде 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
  • ❤ 8
  • 👍 8
  • ⚡ 3
  • 🏆 2
More from @dariasroom
  1. Sep 15, 2026Автоматическое сжатие на клиенте vs ручное на сервере Стандартный клиент http.Transport са…
  2. Sep 5, 2026Причина перекосов в уровне балансировки L4-балансировщик выбирает серверную ноду при созда…
  3. Aug 28, 2026Итераторы… TL;DR: в iter.Seq итератор сам передаёт следующие элементы в код внутри range,…
  4. Aug 17, 2026Что на самом деле нужно сохранять при сериализации сложной структуры? TL;DR: Важно отделит…
  5. Aug 13, 2026Привет! Вас стало больше, так что пора наконец представиться 🙂 Я Даша, давно пишу на Go,…
  6. Aug 12, 2026HNSW: как устроен графовый индекс для векторного поиска One million years later, я наконец…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →