Как устроен движок
Страница для тех, кто хочет заглянуть под капот. Что считается,
где и с какой скоростью; где границы и что за ними. Без маркетинга — цифры замерены,
а не прикинуты.
Модель вычисления
Движок держит только листья — то, что реально ввёл человек. Все свёртки
(итоги по иерархии, суммы по измерениям) считаются на лету из определений, а не
хранятся. Число ячеек у строки — это произведение мощностей её измерений:
строка на осях «время × ЦФО × номенклатура» с 12×40×2000 членами — это 960 000
комбинаций, и все они выводимы из одного определения формулы на строке.
- Пересчёт — по срезу. Правка задевает не куб, а только зависимые от неё
ячейки: топологический проход по DAG определений строк, а не по данным. Одна
правка → одна ячейка → микросекунды, независимо от размера модели.
- Происхождение у каждого значения. Ячейка помнит, откуда она: пусто,
ввод, расчёт, раскладка вниз по долям, ручной override или загрузка из источника.
Производное никогда не путается с введённым — и восстановимо, если определение
меняется.
- Ввод в узел раскладывается вниз. Число, поставленное на свёрнутом уровне,
расходится по листьям по их долям — а не ломает согласованность итогов.
Скорость и память (замерено)
~134 млн
ячеек/сек — компилированные плоские циклы (интерпретатор ~1–2 млн/с)
238 мс
полный пересчёт 18 млн ячеек; буферы 154 МБ (8 Б/значение + 1 Б происхождение)
~5 мкс
одна правка — независимо от размера модели (пересчёт срезом)
Семантика формул — компактный интерпретатор по AST; JS-компиляция поверх него
(генерация плоского цикла) — снимаемое ускорение, а интерпретатор остаётся эталоном.
Оба пути сверяются автотестами на приколоченных числах, чтобы ускорение никогда не
меняло результат молча.
Лестница масштабирования
Принцип простой: не переходить на следующий уровень, пока не измерили, что
текущего не хватает. Мерить есть чем — вот потолки по уровням.
Любая казахстанская компания помещается в один сервер на 16 ГБ
уже на текущем JS. Триггер уровня 4 — не страна, а число торговых точек: у крупнейшего
ритейлера КЗ их 200–400, у X5 — около 30 000. Сто крат. До этого нативное ядро — выигрыш,
который не нужен там, где нужно.
Архитектура
- Ноль зависимостей, без сборки. Ядро — обычный JavaScript, который открывается
файлом. Никаких npm, фреймворков, шага компиляции.
- Ядро не знает ни про DOM, ни про сеть, ни про глобальное состояние. Только
массивы на входе и выходе. Это жёсткая граница, а не пожелание.
- Буферы уже нативно-готовы. Значения —
Float64Array, происхождение —
Uint8Array: плоская непрерывная память, ложащаяся 1-в-1 на Rust
Vec<f64>/Vec<u8> и на линейную память WASM без
конвертации и боксинга. Порт ядра на нативный код — это перенос за той же границей,
а не переписывание продукта.
- Коннектор источник-агностичен. Чтение и запись — пара драйверов с одной
сигнатурой (сейчас ClickHouse и PostgreSQL, новый источник — ещё одна пара). Сеть
отделена от логики; сборка запросов — чистая и тестируемая. Проверено на живом
PostgreSQL (SCRAM-SHA-256): план утверждённой версии уходит обратно в источник и
читается оттуда.
Где происходят вычисления
Движок — это обычный JavaScript, и работает он в браузере. Когда вы вводите
число, гоняете симуляцию или разворачиваете дерево — вся математика идёт на вашем
железе, мгновенно, без похода на сервер. Отсюда и масштабируемость показа: сколько
людей открыло, столько браузеров и считает.
- Браузер — интерактивный пересчёт: ввод, what-if, развороты. Отзывчивость
не зависит от нагрузки на сервер.
- Сервер тонкий — держит авторитетную копию модели, проверяет права и
пересчитывает в момент записи (правку участника нельзя подделать из браузера),
хранит план и ходит в источник данных. Доступ проверяется у данных, а не тем, что
интерфейс чего-то не показал.
Кстати, эту страницу и демо считает ваш браузер — стенд лишь
отдаёт файлы. Планировочную математику демо-посетителей сервер не крутит вовсе.
А браузер потянет?
Браузеры делают настоящие вычисления: движок (V8, JavaScriptCore) компилирует
JavaScript в машинный код. Figma, онлайн-таблицы, редакторы видео считают в браузере
куда тяжелее — у нас числовые циклы по типизированным массивам, для JIT это удобный
случай. Ограничения есть, но для планирования не жмут:
- Один поток. JS по умолчанию считает в том же потоке, что рисует интерфейс —
долгий расчёт подвесил бы страницу. Поэтому мы не пересчитываем куб на каждое
нажатие: правка задевает только зависимые ячейки (срез) → микросекунды, UI не
замечает. Тяжёлое при необходимости уходит в Web Worker (фоновый поток).
- Память вкладки. Практический потолок — ~200 млн ячеек (18 млн ≈ 154 МБ).
Но участник держит свой срез, а не всю модель, поэтому в лимит почти не упираешься.
- Скорость привязана к устройству. Старый телефон слабее станции, но
планировочная модель мала по меркам того, что и так крутится в браузере.
Запасной выход. Если модель когда-нибудь перерастёт браузер —
тот же движок (он ни от чего в браузере не зависит) запускается на сервере в Node,
это уровень 2 лестницы. «Слишком большое для браузера» — не тупик, а переключатель:
считаем на сервере, браузер только показывает.
Где это ставится
Продукт — тот же движок плюс тонкий сервер, упакованные в контейнер (Node, ноль
зависимостей, Docker). Разворачивается двумя способами, и для финансов почти всегда
первый:
- В периметре клиента (on-prem). Контейнер ставится на сервер внутри сети
компании, рядом с её учётной системой (ClickHouse / 1С / PostgreSQL). Коннектор
берёт доступы из окружения — данные не покидают периметр. Сотрудники
открывают внутренний адрес, считают у себя, сервер хранит план и стережёт доступ.
- Управляемое облако (SaaS). Хостим мы, клиент подключается. Проще в
эксплуатации, но финансовые данные живут на внешней инфраструктуре — для многих
финансовых служб это стоп-фактор, потому и не по умолчанию.
Честные пределы
Порядок, в котором всё упрётся на самом деле — а не приятный:
- Отрисовка сетки затыкается раньше любого движка. Решено — виртуализация:
рисуется только видимое окно строк.
- Интерпретатор — на ~2 млн ячеек. Снято компиляцией (уровень 1).
- Память вкладки — на ~200 млн ячеек. Но участник держит свой срез, а не весь куб,
поэтому лимит браузера почти никогда не связывает.
- Хранение и многопользовательский доступ — самое сложное, и это не математика.
Доступ проверяется у данных на сервере, а не тем, что интерфейс чего-то не показал.
© 2026 The Ants · All rights reserved