Что не так и как выжать больше
Критика архитектуры на фактах из кода (7 экспертных линз) + дорожная карта perf/стабильности/безопасности/стоимости + тикет-лист.
Вердикт
Инженерно сильно (единый WebGL-canvas, on-demand рендер, инстансинг, бинарный protobuf, серверные скрипты), но есть 4 системных риска — следствие решений «ради простоты разработки», дорогих на масштабе.
Сделано правильно (не трогать)
- On-demand рендер (0 draw в покое) + async GPU-picking через
fenceSync(без синхронного readPixels-столла) - WebGL2-инстансинг свечей, бинарный protobuf-es, SharedWorker-мультиплекс стримов
- Серверное исполнение Indie (нет клиентского eval = XSS-safe), reconnect с jitter+cap, display-p3 темы
Стабильность
| Sev | Проблема | Evidence |
|---|---|---|
| КРИТ | Single canvas + один WebGL-контекст без app-восстановления Потеря контекста (TDR/смена GPU/>16 контекстов) гасит весь терминал; после restore холст пустой | Pixi-only handleContextLost, forceCanvas:!1, нет Canvas2D-fallback графика |
| КРИТ | Нет app-level heartbeat → «тихо стоящая цена» Half-open HTTP/2-стрим висит «живым» без данных до TCP-таймаута ОС (30–120с) | keepalive только опция fetch; app ping/pong = 0 |
| ВЫС | vite:preloadError → reload() без счётчикаReload-петля на протухшем деплое (старые имена чанков) | function h(n){n.preventDefault();location.reload()} |
| ВЫС | Сырой throw "LayoutManager was not loaded"Белый экран если ленивый чанк не загрузился | mount-эффект if(!fe.lm)throw… |
| ВЫС | Workspace flush только на beforeunloadПотеря разметки/рисунков при краше/OOM/iOS | saveDataTreeThrottled.flush() на beforeunload, throttle 5с |
| ВЫС | SharedWorker cross-tab blast radius Исключение/зависший reconnect в воркере роняет данные во ВСЕХ вкладках | onconnect добавляет порт каждой вкладки к одному streamer |
| СРЕД | TextureGC привязан к кадрам В простое (0 draws) текстуры не выгружаются → рост VRAM на одном контексте | maxIdle=3600 кадров, checkCountMax=600 |
Стоимость
| Sev | Проблема | Evidence |
|---|---|---|
| КРИТ | Индикаторы: server-side, per-instance, без дедупликации Compute растёт как N×M×U стримов, не как число формул; одинаковый RSI считается дважды | indicator_source_code в каждом запросе, indicator_key_salt (cache-buster), пин tpi_worker_instance_id |
| ВЫС | Нет client-side компьюта даже для SMA/EMA Тривиальная математика = постоянная серверная нагрузка + стрим | нет pyodide/JS-индикаторов/.wasm в корпусе |
| ВЫС | Свечи синтезируются на лету из тиков CPU на каждый запрос истории/экстраполяции, особенно мелкие ТФ | CandleOffsetApi/ExtrapolationApi (Unary) |
| ВЫС | Bf-reconnect индикаторов = ∞ без circuit-breakerThundering-herd добивает упавший indicator-сервис | maxAttempts:∞, 1s, exp, cap 60s |
| СРЕД | Голый график тащит индикаторный рантайм Свеча в SeriesItem.candle → даже price = индикатор identity | есть отдельный CandleStream, но chart core на нём не сидит |
| СРЕД | Settings читаются по типам, несколько round-trip на старте Лишний QPS на settings-сервис на синхронном пути init | api/settings?settingType=rendering/media/internal |
Безопасность
| Sev | Проблема | Evidence |
|---|---|---|
| КРИТ | Sentry 100% + PII + Replay без маскирования GDPR ст.44 (US-инстанс) + видео балансов/позиций/ключей | tracesSampleRate:1, sendDefaultPii:!0 ×7 воркеров; replay без maskAllText |
| КРИТ | Биржевые/AI-ключи плейнтекстом на сервере Компрометация бэкенда = доступ к торговым аккаунтам всех | StoreApiKey.credentials (T:9), POST api/ai-chat/api-keys {apiKey} |
| ВЫС | Webhook = SSRF-поверхность Бэкенд дёргает произвольный URL юзера без видимого allowlist | TestUserWebhook{webhook.url} |
| ВЫС | Права ключа не enforced (есть withdraw)Клиент не настаивает на read-only/без-вывода | PermissionInfo{name,enabled} — только репорт |
| ВЫС | accessToken в JS-памяти + Bearer XSS эксфильтрует токен (httpOnly не спасает) | setCredentials, Bearer ${accessToken} |
| ВЫС | PostHog autocapture + recording + always Утечка UX-контента финансового продукта в аналитику | person_profiles:"always", autocapture:!0 |
| СРЕД | Viewport maximum-scale=1Блокировка зума = нарушение WCAG 1.4.4 | meta maximum-scale=1,minimum-scale=1 |
| СРЕД | Доки советуют «Allow unsupervised» для MCP Приучает отключать human-in-the-loop у торгового аккаунта | MCP guide Step 4 |
Рендер
| Sev | Проблема | Evidence |
|---|---|---|
| ВЫС | Глобальный ticker.update() перерисовывает все сценыТик одного из 6 графиков = рендер всех на общем контексте | общий scenes[], нет per-scene scissor |
| ВЫС | Рендер на main-thread, OffscreenCanvas не для GL GL+picking+Canvas2D-стакан конкурируют со Svelte/protobuf | transferControlToOffscreen = 0 |
| ВЫС | Полный реаллок Float32Array вместо bufferSubDataGC-давление + полный re-upload при скролле/смене ТФ | _vertexBuffer.data=new Float32Array(...) |
| СРЕД | rAF busy-poll крутится вечно при 0 draws Лишние wakeup main-thread в простое/фоне | петля requestAnimationFrame(n) безусловная |
| СРЕД | Цвета как 4×float32 вместо packed uint32 4× overhead VRAM/полосы на инстанс | aStrokeColor 4 FLOAT, i+=16 |
| СРЕД | GPU-picking = второй render-pass, не throttled Лишний pass на каждый pointermove | isRenderInteractive, нет rAF-коалесинга |
| СРЕД | Market Depth на Canvas2D (CPU), не GPU 5 слоёв стакана растеризуются на CPU параллельно GL | getContext("2d") в TxOrderBook |
| НИЗ | Pixi v7 (WebGL2), не v8/WebGPU · powerPreference default · antialias off Нет WebGPU-throughput; чарт может уйти на iGPU; ступенчатые линии | версии 7.x, powerPreference:"default" |
Загрузка
| Sev | Проблема | Evidence |
|---|---|---|
| ВЫС | global.css 824 КБ render-blocking на каждой страницеНескипаемый ресурс даже на логине; Tailwind не пурджится | 4427 правил, 2735 --tw- |
| ВЫС | highlight.js 948 КБ / 108 языков — статически Любой роут с чатом/AI грузит все грамматики (нужно ~5) | цепочка CzHe8QoV→ByCio7PO→Eu1IcvlF (static) |
| ВЫС | protobuf+Connect дублируется в 6 воркерах 2.8 МБ, 6 копий рантайма (SharedWorker = свой bundle-root) | makeMessageType 38–130 на воркер |
| ВЫС | Pixi-монолит 3.1 МБ грузится целиком Все плагины (filters/text/a11y) даже если нужны свечи | BgH94ARf.js, 241× pixi |
| СРЕД | 352 чанка / 12 МБ — чрезмерная фрагментация Сотни round-trip + большой modulepreload-манифест | ls *.js = 352 |
Архитектура
| Sev | Проблема | Evidence |
|---|---|---|
| ВЫС | Монолит 3.1 МБ, 91 зависимый чанк Правка тултипа инвалидирует кэш всего движка у всех | grep -l BgH94ARf = 91 |
| ВЫС | Хард-форки LWC + Lumino без upstream-связи Каждый секьюрити-патч апстрима мёрджить вручную навсегда | рендер переписан на Pixi; lm-* классы |
| ВЫС | 26 schema-версий миграций (DRAWING_MANAGER:11) Накапливающийся хвост migration-функций, нельзя удалять | манифест версий в монолите |
| СРЕД | Bespoke-движки (TxOrderBook, block-editor, time-series) Свои кодовые базы без community-поддержки | build, not buy |
Данные
| Sev | Проблема | Evidence |
|---|---|---|
| ВЫС | 6 воркеров без координации → refresh/reconnect-шторм До 6 параллельных refresh; синхронная пачка реконнектов × вкладки | каждый воркер свой api/auth/token + qv() |
| СРЕД | OHLCV в тяжёлой Decimal{i64,i64}-обёртке~12 вложенных сообщений на бар (vs double в indicator.Bar) | candle.v1.Candle все поля Decimal |
| СРЕД | Полная пересортировка стакана на каждый snapshot O(n log n) ресорт обеих сторон после каждого реконнекта | Qg(e.bids,"desc")=[...t].sort() |
| СРЕД | HTTP/2 head-of-line на лоссовых сетях Потеря пакета стопорит ВСЕ стримы соединения (свечи+котировки+индикаторы) | один origin/воркер, нет QUIC/WS-изоляции |
⚡ Quick wins
Low-effort, высокий импакт — делается за день-два.
| Действие | Эффект | Усилие | Область |
|---|---|---|---|
| Sentry sampleRate 1→0.1, sendDefaultPii:false, replay maskAllText/Inputs/blockMedia | −90% PII/стоимости + нет записи балансов/ключей | low | Безоп/Стоим |
| highlight.js → core + 5 языков, остальное import() | −880 КБ на роутах с чатом/AI | med | Загрузка |
| Tailwind strict purge + critical-CSS split | 824 КБ → ~80 КБ render-blocking | med | Загрузка |
| Pinned vendor-chunk для Pixi (manualChunks) | рушит cache-bust blast radius для 91 чанка | low | Архит |
| Снять maximum-scale=1 (touch-action в canvas) | фикс WCAG 1.4.4 | low | Безоп |
| Cap reload-петли (sessionStorage≤2) + убрать throw LayoutManager | нет белых экранов/петель | low | Стабильн |
| Агрессивнее throttling_delay + filter_response (watchlist=только LAST) | меньше quote-push без новых API | low | Стоим |
🔧 Средние ставки
Заметный эффект, недели работы.
| Действие | Эффект | Усилие | Область |
|---|---|---|---|
| Heartbeat-watchdog per-stream поверх qv() | детект мёртвого соединения: десятки сек → единицы | low | Стабильн/Данные |
| Единый auth-broker + общий jitter-bucket для 6 воркеров | refresh 6→1, сглаженный реконнект-фронт | med | Данные |
| App-level webglcontextrestored-хук (re-resolve + isNeedRedraw) | устраняет single-canvas краш | med | Стабильн |
| Стандартные индикаторы (SMA/EMA/RSI/MACD/BB/VWAP) на клиент | снимает основную массу индикаторных стримов | med | Стоим |
| Расцепить свечи и indicator-рантайм (рисовать из CandleStream) | голый график не зависит от R_PYTHON/WASM | med | Стоим/Архит |
| Dirty-driven render + partial bufferSubData | 0 wakeup в простое; дешёвая подгрузка истории | med | Рендер |
| Webhook SSRF-защита (allowlist/блок private-IP/DNS-rebind) | закрывает SSRF | med | Безоп |
| Edge-кэш истории свечей (Unary → immutable бары) | нет повторного синтеза при перемотке | med | Стоим |
| Circuit-breaker для ∞-reconnect профилей | нет retry-шторма при outage | med | Стабильн/Данные |
| visibilitychange/pagehide flush + IndexedDB-бэкап workspace | окно потери данных → секунды (покрыт iOS/OOM) | med | Стабильн |
🏗 Большие ставки
Архитектурные изменения, но именно они снимают системные риски.
| Действие | Эффект | Усилие | Область |
|---|---|---|---|
| Контентно-адресуемый кэш индикаторов + warm-pool с eviction | кратное снижение compute (доминирующий драйвер) | high | Стоим |
| Расщепить 3.1 МБ монолит (pixi/engine/migrations-lazy) | blast radius 91→<10; быстрее кэш | high | Архит |
| OffscreenCanvas + рендер в воркере | снять GL/picking с main-thread (нет джанка) | high | Рендер |
| Pixi v8/WebGPU + per-scene scissor; HTTP/3 (QUIC) | throughput мультичарта; нет TCP head-of-line | high | Рендер/Данные |
| e2e/KMS-шифрование ключей + enforced read-only по умолчанию | компрометация бэкенда ≠ доступ к деньгам | high | Безоп |
Ранжирование по экономии денег
Что реально снизит серверные расходы — по убыванию. Доминирующий драйвер — серверный per-instance компьют индикаторов.
| # | Рычаг | Экономия | Почему |
|---|---|---|---|
| 1 | Контентно-адресуемый кэш индикаторов + warm-pool | ★★★★★ | Доминирующий драйвер. Дедуп популярных формул (RSI/EMA на BTC/ETH) = ×N→×1; eviction простаивающих процессов. Самая большая экономия compute/памяти. |
| 2 | Стандартные индикаторы на клиент | ★★★★ | Большинство юзеров — стандартный набор. Убирает основную массу серверных индикаторных стримов/процессов. |
| 3 | Candles-only путь (обход indicator-рантайма) | ★★★★ | Голый график — самый частый кейс — перестаёт жечь indicator-worker. |
| 4 | Edge-кэш истории свечей/экстраполяции | ★★★ | Закрытые бары immutable → CDN снимает повторный синтез при перемотке, особенно мелкие ТФ. |
| 5 | Sentry sampleRate 1→0.05 + drop PII | ★★★ | −10–20× Sentry-ingest и трафика через собственный /api/events. Нулевой продуктовый риск. |
| 6 | PostHog: autocapture off + sample recording 3% | ★★ | Кратно меньше событийного объёма и нагрузки на self-proxy a.takeprofit.com. |
| 7 | Consolidate воркеры / shared protobuf-рантайм | ★★ | Меньше дублей кода и потоков-конкурентов; меньше CPU на сессию. |
| 8 | throttling_delay/filter_response для фоновых виджетов | ★★ | Меньше quote-push на неактивные подписки — всё уже в протоколе. |
| 9 | Circuit-breaker (анти-thundering-herd) | ★ | Не постоянная экономия, но срезает пиковую стоимость инцидента (retry-шторм ×вкладки). |
Тикет-лист (готов в трекер)
28 issues, отсортированы по severity. Готовы к заведению.
| ID | Заголовок | Область | Sev | Усилие |
|---|---|---|---|---|
| TP-1 | Sentry: sampleRate→0.1, sendDefaultPii:false, mask Replay | Безоп | КРИТ | low |
| TP-2 | WebGL contextrestored: re-resolve снимка + isNeedRedraw | Стабильн | КРИТ | med |
| TP-3 | App-level heartbeat-watchdog per-stream + stale-индикация | Стабильн | КРИТ | low |
| TP-4 | Шифрование биржевых/AI ключей + enforced read-only default | Безоп | КРИТ | high |
| TP-5 | Content-addressable indicator cache + warm-pool eviction | Стоим | КРИТ | high |
| TP-6 | highlight.js → core + lazy языки | Загрузка | ВЫС | med |
| TP-7 | Tailwind purge + critical-CSS split | Загрузка | ВЫС | med |
| TP-8 | Pixi pinned vendor-chunk + расщепить монолит | Архит | ВЫС | high |
| TP-9 | Единый auth-broker + общий jitter-bucket (6 воркеров) | Данные | ВЫС | med |
| TP-10 | Стандартные индикаторы на клиент (JS/WASM) | Стоим | ВЫС | med |
| TP-11 | Candles-only путь через CandleStream | Стоим | ВЫС | med |
| TP-12 | Webhook SSRF allowlist/private-IP block | Безоп | ВЫС | med |
| TP-13 | Circuit-breaker для ∞-reconnect (Bf/fN) | Стабильн | ВЫС | med |
| TP-14 | SharedWorker health + per-tab supervisor | Стабильн | ВЫС | high |
| TP-15 | reload-петля cap + убрать throw LayoutManager | Стабильн | ВЫС | low |
| TP-16 | visibilitychange/pagehide flush + IndexedDB-бэкап | Стабильн | ВЫС | med |
| TP-17 | accessToken: убрать из логов/replay, CSP без unsafe-eval | Безоп | ВЫС | med |
| TP-18 | PostHog: identified_only + autocapture allowlist + sample recording | Безоп | ВЫС | low |
| TP-19 | OHLCV double вместо Decimal для отрисовки | Данные | СРЕД | med |
| TP-20 | Инкрементальный orderbook-снапшот (diff по version) | Данные | СРЕД | low |
| TP-21 | Dirty-driven render (убрать вечный rAF) + bufferSubData | Рендер | СРЕД | med |
| TP-22 | Per-scene scissor вместо глобального ticker.update | Рендер | ВЫС | high |
| TP-23 | OffscreenCanvas + рендер в воркере | Рендер | ВЫС | high |
| TP-24 | Throttle hit-test по rAF | Рендер | СРЕД | low |
| TP-25 | Batched settings request + immutable cache headers | Загрузка/Стоим | СРЕД | low |
| TP-26 | UPSTREAM.md baseline + cherry-pick процесс (LWC/Lumino) | Архит | ВЫС | low |
| TP-27 | Консолидировать миграции + golden-fixture тесты | Архит | ВЫС | med |
| TP-28 | Edge-cache истории свечей (CDN, immutable бары) | Стоим | ВЫС | med |
Метод
Ревью по реверс-инжинирингу клиента (352 чанка + 6 воркеров + 46 CSS + 99 доков). Каждая проблема подтверждена конкретным кодом/конфигом. Severity и усилие — экспертная оценка. Это анализ клиентской стороны; серверные числа (реальная стоимость compute) — оценочные, точные нужны от команды.