TGViewer
Channel Public Channel
SuperOleg dev notes

SuperOleg dev notes

@super_oleg_dev

Обзоры новостей и статей из мира frontend, интересные кейсы, исследования и мысли вслух

https://github.com/SuperOleg39

https://twitter.com/ODrapeza

@SuperOleg39
Subscribers
1.75K
Photos
57
Videos
3
Links
160

Showing posts older than #122 · Back to latest

Older Posts 15 shown
Post #121 1.44K
Привет!

Свежий инсайт по ускорению сборки.

`--max_semi_space_size=128` отлично работает и для CLI приложений на Node.js!

GC работает реже, в сумме тратит меньше времени, локально ускорило тестовую production сборку с 130 до 110 секунд, теперь интересно как на development повлияет.

Кстати, не знал что у Node.js есть апишка для генерации CPU profile, очень удобно, пробую сейчас решение из Next.js - https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/cpu-profile.ts

Из минусов, нет возможности перейти в исходники, интересно можно ли как-то сурсмапы подружить с этим трейсом..
GitHub next.js/packages/next/src/server/lib/cpu-profile.ts at canary · vercel/next.js The React Framework. Contribute to vercel/next.js development by creating an account on GitHub.
  • 👍 6
  • ❤ 1
Post #119 1.33K
Привет!

Сейчас у Next.js проходит эпик по ускорению development сборки - очень уж сильно жалуются пользователи на перф при использовании новой app directory.

Периодически ищу что-то интересное в issues некста, так как тоже используем webpack и swc, и в этом квартале тоже работаем над ускорением сборки.

Пример постов, где можно найти какие-то интересные PR или ссылочки:
- https://github.com/vercel/next.js/issues/48748#issue-1680013792
- https://github.com/vercel/next.js/issues/49929#issuecomment-1637185156
- из последнего issue сразу же неплохая вводная статья про GC и утечки в Node.js - https://www.dynatrace.com/news/blog/understanding-garbage-collection-and-hunting-memory-leaks-in-node-js/

Всю эту историю я начал смотреть после профилирования `tramvai start` на одном из наших приложений (одно из самых больших, очень много модулей), где повторная сборка с прогретым webpack file-system cache занимала очень много времени (до минуты на m1, полторы-две минуты на intel). Вот парочкой наших проблем и решений, где малыми усилиями получили хорошие результаты, хочу поделиться.

Профилировка показала что примерно 60-70% времени занимает работа либы `watchpack`, которая отвечает в вебпаке за отслеживание изменений файлов и папок.

Оказалось, проблема специфичная для MacOS, где проблему вызывает закрытие огромного количества watchers. Кардинальное уменьшение решает проблему и при этом я не заметил никакого замедления реакции на изменение файлов.

Как уменьшить количество watchers - env переменная `WATCHPACK_WATCHER_LIMIT=20`

Issues по теме:
- https://github.com/webpack/watchpack/issues/222
- https://github.com/vercel/next.js/pull/51826

Второй кейс - потребление памяти.

На этом же приложении development сборки занимала минимум 12 GB оперативной памяти.
При этом для production сборки коллеги не увеличивали `max_old_space_size`, то есть проблема только во время работы dev сервера.

У трамвая изначально есть такая особенность, что в development режиме отключена опция splitChunks, которая отвечает за эффективное разделение кода, и в 5 вебпаке по дефолту уже давно используется отличная стратегия granular chunking.

Выключено как я понимаю по двум причинам:
- в теории может замедлять dev сборку на время работы плагина
- появляется куча рандомных чанков что усложняет тестирование и разработку
- так сделано в Next.js, главный референс при интеграции granular chunking стратегии разделения кода

У меня уже пару недель в голове крутилась мысль, что не используя splitChunks в приложениях с огромным количеством динамических импортов (обычно на каждый роут), мы дублируем все их общие зависимости в каждом чанке страницы.

Ок, включил splitChunks, поправил упавшие тесты, проверили на приложении:
- потребление памяти снизилось с 12 gb до менее чем 3 gb 🚀
- повторная сборка с прогретыми кэшами ускорилась в несколько раз (на m1 примерно с 45 секунд до 10) - оказалось что размер FS кэшей клиентской сборки тоже уменьшился с нескольких гигабайт до сотен мегабайт
- холодный старт ускорился незначительно

То есть теория полностью подтвердилась, оптимизация радикально уменьшает потребление памяти за счет удаления огромного количества дубликатов модулей.

Кстати пытаюсь в issues некста достучаться до мейнтейнеров, что бы аналогично попробовали включить splitChunks в dev режиме - https://github.com/vercel/next.js/issues/49929#issuecomment-1647549143

Вот такие небольшие истории успеха, надеюсь будет полезно для ваших development сборок.

А в следующем посте план рассказать про многочисленные факапы за последнее время)
nextjs.org Next.js Docs: App Router The App Router is a file-system based router that uses React's latest features such as Server Components, Suspense, Server Functions, and more.
  • 🔥 20
  • 👍 1
Post #116 1.15K
Раскатили параметр 3 июля в 16 часов
Post #115 1.18K
Привет!

Получилось подвести некий итог возможных оптимизаций на стороне сервера в формате рекомендаций для tramvai приложений - https://tramvai.dev/docs/guides/server-optimization

Отличная рекомендация от Андрея Марченко и Александра Хороших в комментариях к предыдущему посту про DNS кэши - увеличить параметр ноды `--max_semi_space_size`!

Добавил график по количеству срабатываний GC, после увеличения до 64mb - minor очисток стало примерно в 10 раз меньше, но их продолжительность тоже примерно в 10 раз.
По метрикам, заметно улучшилось время ответа и пиковые значения лага эвент лупа на 99м перцентиле, на 95м перцентиле уже не так сильно но тоже видны изменения в лучшую сторону.
CPU троттлинг в пике стал ниже примерно на 2%.

В общем без сомнения пошло в дефолтные рекомендации.

По DNS кэшам. Оптимизация не дала такого заметного эффекта, как semi space size, но и никаких проблем с использованием до сих пор не увидел, сегодня буду проводить эксперимент с увеличением времени жизни кэша до 5 минут, а после до 10 минут.

Эффект заметен слабо з-за того что активно используется keepAlive, про это уже писал, по сути в рамках эксперимента виден яркий эффект на единственном запросе из тестируемого приложения, который без keepAlive.
Поэтому интересно увеличить время жизни кэша, но с текущим TTL в минуту, в приложении происходит очень мало вызовов системной функции getaddrinfo, поэтому больших ожиданий нет.

Итого, DNS кэш идет в дефолты, но при любых возможных проблемах будет не жалко отключить.

Дополнительно закину скриншоты графиков после увеличения max semi size.
tramvai.dev Server optimization | tramvai Introduction
  • 🔥 8
  • 🍾 4
Post #114
SuperOleg dev notes pinned «Привет! Ищу коллегу в нашу платформенную команду для разработки и поддержки React мета-фреймворка tramvai Мы - одна из команд в Tinkoff Coretech Frontend, которая отвечает за создание инструментов и общих практик для огромного фронтенд коммьюнити в Тинькофф.…»
Post #113 6.81K
Привет!

Ищу коллегу в нашу платформенную команду для разработки и поддержки React мета-фреймворка tramvai

Мы - одна из команд в Tinkoff Coretech Frontend, которая отвечает за создание инструментов и общих практик для огромного фронтенд коммьюнити в Тинькофф.

Почему у нас классно:
- работа в небольшой и технически прокачанной команде, без менеджмента, бюрократии и лишних встреч
- сами себе продакты - ставим задачи и выбираем приоритеты, ориентируясь на потребности наших пользователей и тренды развития JS экосистемы
- ежедневные челленджи - сложные задачи из самых разных областей: клиентская и серверная разработка, консольные утилиты, бандлеры, производительность, логи и метрики, CI/CD процессы
- самые крутые пользователи - разработчики, активный Inner Source и хорошая обратная связь
- несколько десятков tramvai приложений в продакшене - battle-tested это про нас
- заботимся о качестве продукта - пишем RFC и ADR, интеграционные тесты и документацию, проходим code review, придерживаемся Zero Bug Policy, собираем обратную связь
- регулярно контрибьютим в Open Source, также имеем большие планы на OSS для tramvai

Наши основные задачи:
- Разработка собственного SSR фреймворка
- Поддержка пользователей фреймворка - исправление багов, ответы на вопросы, помощь с ревью и миграциями
- Обеспечивать качественную работу всех tramvai приложений в Tinkoff экосистеме - интеграционные тесты, помощь в разборе инцидентов, рекомендации по улучшению
- Исследовать и исправлять проблемы производительности и на сервере, и на клиенте
- Совместная работа с другими платформенными командами - tramvai содержит много полезных интеграций для итоговых пользователей и приложений

Наш технический стек:
- монорепозиторий на 150+ пакетов и модулей - Typescript, Nx + Yarn workspaces, PVM + SemVer + Conventional Commits, Rollup, Gitlab CI
- view слой - React, CSS Modules, Tinkoff UI-kit
- серверная часть - Node.js и Fastify
- тестирование - Playwright, Jest, React Testing Library
- сборка приложения - Webpack, Module Federation, Babel / SWC, Postcss

Идеальный кандидат подходит под любое описание из списка:
- знает и любит React, изучал новую Suspense SSR архитектуру и React Server Components
- имеет широкий кругозор, понимает принципы работы других фреймворков, интересуется развитием таких мета-фреймворков как Next.js, Remix, Nuxt.js, SvelteKit, Astro, Qwik City
- разрабатывал и поддерживал Node.js приложения в продакшене, знает на какие метрики стоит обращать внимание, исправлял высокий event loop lag или исследовал утечки памяти
- имеет опыт работы в платформенной команде - поддержка продукта для других разработчиков, обратная совместимость и long-term миграции, написание технической документации
- хорошо знаком с любым популярным бандлером и транспайлером, писал под них плагины и обходил AST, не боится изучать исходных код инструментов и контрибьютить в них
- умеет профилировать и оптимизировать web приложения, знает как улучшить waterfall загрузки ресурсов страницы или медленные анимации, использовать такие инструменты как WebPageTest, Lighthouse и Performance Insights, знает про Web Vitals и Chrome UX Report, читает CPU Flamegraph как открытую книгу
- есть опыт организации JS библиотек - организации репозиториев, сборки, тестирования, линтера, полного релизного цикла, настройки CI/CD, работы с транзитивными зависимостями и дубликатами, понимание плюсов и минусов чистых ESM пакетов
- знаком с различными стратегиями деплоя серверных и SPA приложений, есть опыт работы с Docker и k8s
- интересуется архитектурными подходами, знаком с IoC и Dependency Injection в частности, Clean Architecture, Feature-Sliced Design
- вдохновлен развитием сетевых протоколов, знает как устроены DNS, TCP, HTTP и QUIC, пользовался такими инструментами как Wireshark
- знаком с мониторингом - собирает, рисует и использует метрики и логи, участвует в разборе инцидентов

Пишите ваши вопросы или присылайте резюме в личку!
  • 🔥 20
  • ❤ 1
  • 😢 1
  • 💩 1
Post #112 1.25K
В комментарии к посту закинули очень крутой кейс, запуск ноды с увеличенным параметром max-semi-space-size!

Грубо говоря, реже делаем очистки памяти и соответственно реже нагружаем приложение этой работой, за счет большего потребления памяти.

Больше инфы по теме:
- https://www.alibabacloud.com/blog/better-node-application-performance-through-gc-optimization_595119
- https://github.com/nodejs/node/issues/42511

При чем недавно пол-дня гуглил, какие есть возможности управлять GC, как делать очистку пореже, и этот флаг не нашел)

Отдельно отмечу работу ChatGPT, который придумал специально для меня целый набор флагов и опций, как менять стратегии сборщика мусора v8 :)
  • 🔥 8
  • 💯 1
Post #110 1.07K
Пример ускорения запроса без агента с keepAlive: true
Post #105 1.02K
Привет!

Сделали очередной заход на добавление кэша DNS lookup.

Теория - резолв DNS синхронный, и треды libuv занимает, и негативно влияет на производительность приложений, лишняя нагрузка на event loop.

Использовал либу https://github.com/szmarczak/cacheable-lookup

TTL кэша высокий ставить кажется опасным, начали с 15 секунд, оставил 1 минуту, и хочется попробовать еще повысить - риск тут вижу, что можем ловить совершенно не нужные ошибки запросов к сторонним сервисам в момент смены этими сервисами IP адреса, что по идее может происходить не редко в k8s кластерах.

На этой неделе раскатили и понаблюдали на одном приложении, какой эффект получили по HTTP запросам:
- заметно ускорились запросы, которые идут в обход трамвайных HTTP клиентов
- незначительно ускорились запросы через наши базовые HTTP клиенты - тут большой буст к перформансу уже дает активная опция keepAlive для http/https агентов ноды, установка соединений происходит гораздо реже. Плюс lru-кэши и дедупликация для запросов.
- соответственно на наших метриках активных соединений почти пропали GetAddrInfoReqWrap

По производительности, сначала показалось что эффекта вообще нет - приложение рестартует, отъедает побольше памяти, и снова повышается лаг эвент лупа - кажется по большей части из-за роста времени работы Garbage Collector.

Но к концу недели все-таки стал заметен эффект, пиковые значения лага эвент лупа, времени сбора мусора, потребление CPU и CPU троттлинг - все стало пониже, пример графиков закину в канал отдельным сообщением (там где перцентиль не указан вроде бы 95).

Я думаю, этот эффект был бы менее заметен если бы мы выделяли подам больше 1000 mCPU в k8s, так как и треды libuv и сборка мусора выполнялись бы по настоящему параллельно, и не было бы такого высокого CPU троттлинга.

Хорошая статья, где предлагается оптимальным использовать 1150 mCPU, или 1.15 ядра на под, и объясняются все механизмы из предыдущего абзаца - https://medium.com/pipedrive-engineering/how-we-choked-our-kubernetes-nodejs-services-932acc8cc2be

По итогу, кажется хорошая и почти бесплатная оптимизация, посмотрим, выдержит ли испытание временем.

Обязательно делитесь вашим опытом аналогичных оптимизаций, и в особенности не удачных!
GitHub GitHub - szmarczak/cacheable-lookup: A cacheable dns.lookup(…) that respects TTL :tada: A cacheable dns.lookup(…) that respects TTL :tada: - szmarczak/cacheable-lookup
  • 🔥 4
  • ❤ 3
  • 👍 2
Post #104 1.26K
Почему я вообще поднял тему lazy hydration.

Сейчас исследую возможность оптимизировать тяжелый футер tinkoff.ru не только на клиенте, но и на сервере.

В теории, его можно кэшировать, и не рендерить для каждой страницы заново - а время его рендеринга занимает 20-30% от общего времени рендера большинства страниц, то есть оптимизация напрямую повлияет на максимальные нагрузки, которые могут обрабатывать приложения.

Отдельная история - это персонализация и динамика, которая пролезла в футер, и от которой надо избавиться перед кэшированием, но тут нас больше интересует техническая часть.

Если искать информацию по кэшированию React компонентов, можно найти несколько устаревших форков рендерера `ReactDOMServer`, например https://github.com/rookLab/react-component-caching

Этот подход сложный в реализации и поддержке, и не удивительно что актуальных решений кажется не существует.

Но оказалось что можно сделать очень простой локальный механизм, который отлично ложится на наш `LazyRender` компонент, настолько простой что до сих пор ищу подвох.

Сразу покажу обновленный код:

```
const LazyRender = ({ children, cacheEnabled, cacheKey, serverCache }) => {
const containerRef = useRef(null);
const isVisible = useObserver(containerRef);

if (cacheEnabled && typeof window === 'undefined') {

let html: string;

if (serverCache.has(cacheKey)) {
html = serverCache.get(cacheKey);
} else {
const reactDomServer = require('react-dom/server');

html = reactDomServer.renderToString(children);

serverCache.set(cacheKey, html);
}

return React.createElement('div', {
dangerouslySetInnerHTML: { __html: html },
});
}

if (isVisible) {

return React.createElement('div', {}, children);
}


return React.createElement('div', {
ref: containerRef,

suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: '' },
});
};

```

Где `cacheKey` - уникальный ключ для кэширования конкретной вариации children компонента, он должен иметь низкую кардинальность для эффективной работы кэша.

А `serverCache` - любой LRU кэш со стандартными методами has / get / set, и настроенным не слишком высоким временем жизни кэша.

Для этого кэша на сервере важно быть синглтоном, в принципе как и для любого другого.

В `LazyRender`, если кэш включен, мы вручную рендерим children в HTML строку, и дальше сохраняем результат в кэши и используем при рендере других страниц.

Основной минус подхода - React Context приложения будет недоступен в children компоненте. Мы планируем использовать это для микрофронта (а футер как раз такой микрофронт), где это кажется не проблема, и даже хорошая практика.

По итогу, механизм кэширования это самая простая часть, а больше времени заняла интеграция в модуль отвечающем за загрузку и рендер микрофронтов, и в модуль который добавляет микрофронт футер в лэйаут приложения.
И конечно же доработки самого футера.

Из интересного, закрыл включение кэширования за фича-тогглом - сам футер одновременно можно раскатить на все приложения, а код с кэшированием требует обновления фреймворка на приложениях, и этот рубильник даст возможность в будущем контроллируемо проверить работоспособность механизма.

Также, добавил серверную метрику - счетчик с лейблами hit / miss и уникальным ключем кэша, для возможности чекать hit rate и в целом эффективность кэширования.

Обязательно поделюсь результатами, как дело дойдет до интеграции и подключения на продакшене!
GitHub GitHub - rookLab/react-component-caching: Speedier server-side rendering with component caching in React 16 Speedier server-side rendering with component caching in React 16 - rookLab/react-component-caching
  • 🔥 17
  • 👍 4
Post #103 947
Теперь хочется обсудить современные возможности React, и как они могут помочь ускорить гидрацию и уменьшить количество клиентского кода, и нужен ли еще этот хак.

В первую очередь это конечно React Server Components - подход Next.js с серверными компонентами по умолчанию в теории может кардинально уменьшить количество клиентского кода.

А стриминг разметки и данных с сервера на клиент вместе с механизмом selective hydration делает процесс гидрации неблокирующим и постепенным, также это все идет в паре с вложенным роутингом и гранулярным разделением кода на чанки.

Концепция RSC очень крутая, и это полноценный конкурент таким подходам как у Resumability у Qwik или Islands Architecture.

На практике у RSC + Next.js не все идеально, есть вопросы и к производительности на сервере, и к DX и сложным концепциям, вендор лок на некст, но тем не менее RSC это скорее всего грядущее будущее экосистемы реакта, в том или ином виде.

Окей, у нас не Next.js, что есть еще интересного и более доступного?

Это как раз механизм selective hydration - неблокирующей гидрации, почитать про это и вообще целевую SSR архитектуру можно тут - https://github.com/reactwg/react-18/discussions/37

Даже без RSC, в React 18 есть возможность стримить разметку с сервера, и гидрировать ее на клиенте постепенно, небольшими неблокирующими задачами - то есть напрямую улучшить время до интерактивности важного контента на странице.

Про стриминг React приложения на сервере и его проблемы уже писал ранее - https://t.me/super_oleg_dev/49

По умолчанию, Suspense + стриминг + selective гидрация не решают проблему больших клиентских бандлов - но это уже задача `@loadable` или `React.lazy` для выделения кода компонентов в отдельные чанки с помощью динамического импорта.

И в целом, большая часть проблем решается - ваши тяжелые компоненты вынесены в lazy чанки, обернуты в Suspense, приложение гидрируется через hydrateRoot, время до интерактивности улучшилось, а кода стало меньше.

Но к сожалению тут есть и минусы:
- с SSR, мы автоматически предзагружаем скрипты lazy чанков которые были отрендерены на странице на сервере. Это расходы на сеть, и возможно на компиляцию кода, даже если он выполняется не сразу
- если отдельные блоки не обернуты в Suspense, при действиях пользователя, например клике по кнопке в процессе гидрации - React запустит принудительную блокирующую гидрацию у всего поддерева компонентов до ближайших Suspense границ
- ошибки гидрации не редкость, они также приводят к принудительной блокирующей гидрации или даже рендеру

В итоге, кажется подход с lazy гидрацией остается актуальным, и позволяет делать настоящие независимые Islands - отдельные островки из которых может состоять приложение.
GitHub New Suspense SSR Architecture in React 18 · reactwg react-18 · Discussion #37 Overview React 18 will include architectural improvements to React server-side rendering (SSR) performance. These improvements are substantial and are the culmination of several years of work. Most...
  • 🔥 17
Post #102 960
Привет!

Одна из первый идей, которую я реализовывал в Тинькофф - ленивая гидрация для React компонентов.

Ленивая гидрация позволяет не выполнять реакту код компонента на клиенте сразу при гидрации всего приложения, и отлично комбинируется с IntersectionObserver - можно выполнять код только при попадании компонента в область видимости.

Также, можно вообще не выполнять код на клиенте пометив блок как статичный, и каким-либо образом убирать его из клиентского бандла.

На tinkoff.ru очень тяжелый футер, и lazy hydration просто отлично зашла для его оптимизации.

Механизм достаточно простой, это по сути легализованный хак в React, детали есть в этом issue - https://github.com/facebook/react/issues/10923#issuecomment-338715787

Код для lazy обертки может базово выглядеть так:

```
const LazyRender = ({ children }) => {
const containerRef = useRef(null);
const isVisible = useObserver(containerRef);

if (isVisible) {
return React.createElement('div', {}, children);
}

return React.createElement('div', {
ref: containerRef,
suppressHydrationWarning: true,
dangerouslySetInnerHTML: { __html: '' },
});
};
```

Абстрактный хук `useObserver` должен вернуть `true` всегда на сервере, либо при попадании компонента в область видимости на клиенте - то есть мы сознательно создаем ошибку гидрации, и гасим ее через пропс `suppressHydrationWarning: true`.

Вся магия, почему разметка на клиенте не ломается, а переиспользуется, как раз заключается в хаке с `dangerouslySetInnerHTML: { __html: '' }`.

У решения конечно же есть минусы:
- приходится создавать лишний тег - враппер
- по умолчанию код все-равно остается в клиентском бандле
GitHub How to prevent React from modifying elements manipulated by external code? · Issue #10923 · facebook/react The New York Times is rebuilding its website using React. Currently, it’s an isomorphic app that has both server- and client-side renders. Our question: What's the best way to include a non-Rea...
  • 👍 21
Older posts →
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 →