Рекурсия на сервере: как Интеграм обходит деревья и графы одним запросом
Большинство бизнес-данных — это связи: подразделение внутри подразделения, «похожие» записи одна за другой. Чтобы пройти такую структуру вглубь, обычно идут в код приложения — десятки запросов. Интеграм обходит дерево или граф и сразу ранжирует результат за один серверный вызов.
Большинство бизнес-данных — это не плоские таблицы, а связи: подразделение внутри подразделения, категория внутри категории, «похожие» записи, тянущиеся одна за другой. Чтобы пройти такую структуру вглубь — от корня до листьев или от записи к её соседям — нужна рекурсия. Обычно за ней идут в код приложения: выгрузил один уровень, спросил следующий, и так десятки раз. Каждый шаг — отдельное обращение к серверу.
Интеграм умеет иначе: обойти всю структуру вглубь и сразу её отсортировать — за один серверный вызов.
Откуда берётся рекурсия
Движок отчётов Интеграма опирается на нативный механизм MySQL WITH RECURSIVE — стандартный способ писать рекурсивные запросы прямо в СУБД. Поверх него работает композиция: запись вида [имя_отчёта] подставляет один сохранённый отчёт внутрь другого как подзапрос. Вы собираете сложный обход из простых кирпичиков-отчётов, а сервер разворачивает его в один SQL-запрос. Никакого отдельного «движка обхода» и никакой выгрузки промежуточных уровней в приложение.
Кейс 1. Деревья: оргструктура, каталоги, меню
Подчинённые таблицы Интеграма — это и есть деревья: запись ссылается на родителя (c.id = ref.up). Рекурсивный отчёт спускается от любого узла до самых листьев одним запросом: все подразделения внутри департамента, все подкатегории товара, всё дерево пунктов меню. Тот же механизм Интеграм использует и у себя — чтобы собрать ролевое меню по вложенной структуре.
Что это даёт на практике: «показать все заказы по всем дочерним филиалам», «свернуть и развернуть категорию со всем вложенным» — без ручных джойнов на каждый уровень и без потолка «только два-три уровня вглубь».
Кейс 2. Графы: память по смыслу
Связи бывают не древовидными, а сетевыми — «похоже на это». На этом построен наш эксперимент IME (Integram Memory Engine): дать приложению память по смыслу прямо в рабочей базе, без отдельной векторной СУБД.
Идея в трёх шагах. Текст превращается в вектор — набор чисел, у похожих по смыслу текстов наборы похожи. Близость векторов (косинус) считает обычный отчёт Интеграма. А чтобы не сравнивать запрос с миллионами записей, у каждой записи хранятся ссылки на несколько ближайших соседей — получается граф «знакомств», и поиск идёт по нему прыжками: спроси соседа, кто ещё ближе к теме.
Вот здесь рекурсия и решает. Раньше обход графа шёл из клиента: около 82 обращений к серверу, примерно 33 секунды на один поиск. Рекурсивный отчёт собирает окрестность графа (поиск вширь до заданного бюджета) и ранжирует её косинусом в одном запросе: зерно → рекурсивное расширение → косинус → top-k.
| Способ обхода | Обращений к серверу | Время |
|---|---|---|
| обход из клиента | ~82 | ~33 с |
| один рекурсивный запрос | 1 | ~0.3 с |
Это около 100× по времени. На живой базе ideav.ru/mem выигрыш индексного поиска над полным перебором растёт с объёмом: 2.1× при 60 записях → 12.7× при 5000, при точности recall@1 95–100%.
Честно про границы
Рекурсия снимает боль «обхода вглубь», но не превращает Интеграм в векторную СУБД:
- каждый шаг обхода — это сетевое обращение; для миллисекундных задержек на больших корпусах выделенный ANN-индекс (FAISS, Qdrant, pgvector) всё равно быстрее по константе;
- нативного векторного индекса нет — близость считается сканом по тексту, и на больших объёмах он медленнее;
- инкрементальное сопровождение рёбер графа при каждой вставке мы пока не доказали — в прототипе граф строился пакетно.
Прямые аналоги, которые умеют и рекурсию, и вектора, — это Postgres с pgvector и Neo4j. На больших объёмах они мощнее, но это отдельная инфраструктура. Ценность подхода Интеграма не в самом примитиве WITH RECURSIVE, а в композиции: вектора, рёбра, рекурсия и косинус живут в одной таблице рядом с бизнес-данными — без новой базы и без бюджета сверху.
Что забрать с собой
Рекурсивные отчёты плюс композиция [имя] превращают связи в обходимые структуры: деревья — от корня до листа, графы — от записи к её соседям, и всё это одним серверным вызовом. Для иерархий это убирает ручные джойны по уровням; для «похоже на это» — открывает память по смыслу прямо внутри приложения.
Подробности и цифры — в репозитории эксперимента ideav/ime: на пальцах, прототип и результаты, сравнение с векторными СУБД.