structuredClone и Error с глубоким стек-трейсом: неявный убийца производительности в worker_threads
Передаёте Error через worker.postMessage()? Если стек-трейс глубокий — после цепочки асинхронных вызовов — V8 может незаметно блокировать поток на сериализации. Производительность падает линейно, а вы ищете причину в другом месте.
Почему это происходит
При вызове structuredClone V8 сериализует все перечислимые свойства объекта, включая .stack. Для Error с сотнями фреймов (длинные URL, аргументы функций) V8 парсит стековую строку целиком. Каждый фрейм проверяется, разбирается — и это происходит синхронно в вызывающем потоке. Пример: 5000 повторений .stack через repeat дают задержку ~100ms на postMessage.
Как диагностировать
Используйте --trace-event-categories или clinic flame. Ищите пики CPU в structuredClone. Сравните время postMessage с простым объектом против Error — разница в 10-50 раз при глубоком стеке. Типичная ошибка: разработчики грешат на дорогие вычисления, а на самом деле V8 тупо сериализует строчку.
Решение: не передавайте полный Error
Клонируйте только критичные поля: message, code, line, fileName. Для огромных стеков передавайте идентификатор ошибки, а полный стек собирайте локально. Можно упаковать Error в простой объект:
Production-кес: в нашем сервисе 20% CPU уходило на structuredClone ошибки с 1500+ фреймами. После замены на { msg: err.message } блокировка исчезла полностью.
Вывод: Не передавайте полный Error между воркерами при глубоких стеках — сериализация через structuredClone может незаметно съедать 10-20% CPU и блокировать поток.
Передаёте Error через worker.postMessage()? Если стек-трейс глубокий — после цепочки асинхронных вызовов — V8 может незаметно блокировать поток на сериализации. Производительность падает линейно, а вы ищете причину в другом месте.
Почему это происходит
При вызове structuredClone V8 сериализует все перечислимые свойства объекта, включая .stack. Для Error с сотнями фреймов (длинные URL, аргументы функций) V8 парсит стековую строку целиком. Каждый фрейм проверяется, разбирается — и это происходит синхронно в вызывающем потоке. Пример: 5000 повторений .stack через repeat дают задержку ~100ms на postMessage.
Как диагностировать
Используйте --trace-event-categories или clinic flame. Ищите пики CPU в structuredClone. Сравните время postMessage с простым объектом против Error — разница в 10-50 раз при глубоком стеке. Типичная ошибка: разработчики грешат на дорогие вычисления, а на самом деле V8 тупо сериализует строчку.
Решение: не передавайте полный Error
Клонируйте только критичные поля: message, code, line, fileName. Для огромных стеков передавайте идентификатор ошибки, а полный стек собирайте локально. Можно упаковать Error в простой объект:
const safeError = {
message: err.message,
code: err.code,
line: err.stack?.split('\n')[1]
};
worker.postMessage(safeError);Production-кес: в нашем сервисе 20% CPU уходило на structuredClone ошибки с 1500+ фреймами. После замены на { msg: err.message } блокировка исчезла полностью.
Вывод: Не передавайте полный Error между воркерами при глубоких стеках — сериализация через structuredClone может незаметно съедать 10-20% CPU и блокировать поток.
👍1
Reverse Engineering ChatGPT Web — чувак, который когда-то разбирал Linear по косточкам, решил не останавливаться и вдумчиво покопался в исходниках ChatGPT. Он прошерстил бандлы, запросы и нарыл там React Router 7, TanStack Query, Radix и ProseMirror. Фишка в том, что весь ненужный хлам не грузится, пока ты не начнешь стучать по клавиатуре.
Base UI теперь рулит в shadcn/ui — новые проекты на shadcn/ui едут на Base UI, а не на Radix. Радикс не умер, компоненты доступны для обеих платформ, но для свежих проектов советуют именно Base UI.
TypeScript 7.0 — выкатили компилятор на Go, который шпарит в 10 раз быстрее. Правда, без нормального API от версии 6.0 пока что его рекомендуют кучке фронтенд-фреймворков.
Vercel пригрела команду Better Auth — той самой популярной либы для аутентификации.
React Navigation 8.0 — ребята делятся успехами, в том числе навигацией с поддержкой Suspense.
Next.js — опубликовали официальный RFC для WebSocket в Route Handlers.
npm v12 — теперь жизненные циклы скриптов и сборки node-gyp по умолчанию не запускаются.
Подробнее — в выпуске React Status #482.
👉 Node.JS [ru]
Base UI теперь рулит в shadcn/ui — новые проекты на shadcn/ui едут на Base UI, а не на Radix. Радикс не умер, компоненты доступны для обеих платформ, но для свежих проектов советуют именно Base UI.
TypeScript 7.0 — выкатили компилятор на Go, который шпарит в 10 раз быстрее. Правда, без нормального API от версии 6.0 пока что его рекомендуют кучке фронтенд-фреймворков.
Vercel пригрела команду Better Auth — той самой популярной либы для аутентификации.
React Navigation 8.0 — ребята делятся успехами, в том числе навигацией с поддержкой Suspense.
Next.js — опубликовали официальный RFC для WebSocket в Route Handlers.
npm v12 — теперь жизненные циклы скриптов и сборки node-gyp по умолчанию не запускаются.
Подробнее — в выпуске React Status #482.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
TypeScript 7 переписали на Go: разбираемся, правда ли он в десять раз шустрее
📣 Главная цифра из блога TypeScript: проверка типов в проекте VS Code (примерно 1,5 млн строк) упала с 77,8 с до 7,5 с. Отсюда и заголовок — «примерно в 10 раз быстрее».
Это не обзор релиза, а попытка повторить один график на монорепе, специально склеенном для теста. Два компилятора на одной тачке, все цифры на руках, плюс репозиторий, где любой результат можно дёрнуть командой
Суть вот в чём: «10×» — это про проверку типов на большом проекте, а не про всё подряд. TS7 реально ускоряет валидацию и жрёт меньше памяти, бенчи это подтверждают. Но в 7.0 нет стабильного программного API — из сборки вылетели кастомные трансформеры, плагины к tsserver и типизированный линтинг. Так что вопрос миграции не «быстрее или нет», а «живёте ли вы без этого API».
🔗 Читать далее
👉 Node.JS [ru]
Это не обзор релиза, а попытка повторить один график на монорепе, специально склеенном для теста. Два компилятора на одной тачке, все цифры на руках, плюс репозиторий, где любой результат можно дёрнуть командой
pnpm bench. Спойлер: TS7 реально быстрее, и отрыв чувствуется. Но «в десять раз» — это для одного конкретного кейса, в других сценариях магия улетучивается.Суть вот в чём: «10×» — это про проверку типов на большом проекте, а не про всё подряд. TS7 реально ускоряет валидацию и жрёт меньше памяти, бенчи это подтверждают. Но в 7.0 нет стабильного программного API — из сборки вылетели кастомные трансформеры, плагины к tsserver и типизированный линтинг. Так что вопрос миграции не «быстрее или нет», а «живёте ли вы без этого API».
🔗 Читать далее
Please open Telegram to view this post
VIEW IN TELEGRAM
Диагностика и устранение неявного замедления TLS-рукопожатий из-за скрытой конкуренции за entropy pool в кластеризованных HTTPS-серверах
Если вы держите Node.js в кластере — через PM2 или встроенный cluster, — могли замечать странные задержки при HTTPS-соединениях. Вроде всё настроено, а рукопожатия то 5 мс, то 500+. Одна из неочевидных причин — драка за entropy pool.
Причина
Внутри каждого TLS-рукопожатия Node.js дёргает случайные числа для ключей. В Linux это
Симптомы: время рукопожатия плавает хаотично, иногда
Диагностика
Берите
Решения
* Отдельный seed для каждого воркера. При старте слать его через IPC из master:
* Для операций после старта — только асинхронный
* На bare-metal поставьте
Типичная ошибка
Не используйте
Вывод: Диагностика через strace и управление seed-ами — эффективный способ ускорить TLS без переписывания стека, но критично проверить нагрузку на entropy pool в кластере с 10+ воркерами.
Если вы держите Node.js в кластере — через PM2 или встроенный cluster, — могли замечать странные задержки при HTTPS-соединениях. Вроде всё настроено, а рукопожатия то 5 мс, то 500+. Одна из неочевидных причин — драка за entropy pool.
Причина
Внутри каждого TLS-рукопожатия Node.js дёргает случайные числа для ключей. В Linux это
/dev/urandom или getrandom(). Когда несколько воркеров одновременно лезут в один пул энтропии, начинаются блокировки. Особенно в контейнерах без аппаратного RNG — там стекло быстро заканчивается.Симптомы: время рукопожатия плавает хаотично, иногда
blocked for more than 120 seconds. strace покажет, что провал в crypto.randomFill().Диагностика
Берите
strace -e trace=getrandom -p <PID> -o entropy.log. Если вызовы висят дольше 1 мс — проблема. Проверьте скорость энтропии: dd if=/dev/urandom bs=1024 count=100 | pv.Решения
* Отдельный seed для каждого воркера. При старте слать его через IPC из master:
process.on('message', (msg) => {
if (msg.type === 'seed') {
global._seed = msg.seed;
}
});* Для операций после старта — только асинхронный
crypto.randomFill с callback. Синхронный вызов дёргает блокировку пула.* На bare-metal поставьте
haveged или rng-tools, в облаке — virtio-rng.Типичная ошибка
Не используйте
Math.random() для криптографии — это антипаттерн, который не решает проблему, а только создаёт уязвимости.Вывод: Диагностика через strace и управление seed-ами — эффективный способ ускорить TLS без переписывания стека, но критично проверить нагрузку на entropy pool в кластере с 10+ воркерами.
👾 Тамагочи из 90-х: собираем на коленке на JavaScript
Автор честно признаётся: раньше он вообще не втыкал, что это за зверь. Ну, видел в американском кино, типа в «Менталисте», где герой чуть ли не со слезой вспоминает виртуального питомца и даже толкает его коллеге. А потом до него дошло: если эта игрушка пережила три десятилетия и кучу раз её перепридумывали — значит, там что-то серьёзное, а не просто мусор.
Так он решил врубиться, что это за хрень и как она работает, и начал с истории. Без неё объяснять старые штуки — пустая трата времени.
Дальше на Habr
👉 Node.JS [ru]
Автор честно признаётся: раньше он вообще не втыкал, что это за зверь. Ну, видел в американском кино, типа в «Менталисте», где герой чуть ли не со слезой вспоминает виртуального питомца и даже толкает его коллеге. А потом до него дошло: если эта игрушка пережила три десятилетия и кучу раз её перепридумывали — значит, там что-то серьёзное, а не просто мусор.
Так он решил врубиться, что это за хрень и как она работает, и начал с истории. Без неё объяснять старые штуки — пустая трата времени.
Дальше на Habr
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2🐳2
Скрытая утечка памяти: когда WeakRef и Symbol.dispose убивают DI-контейнер
В long-lived DI-контейнерах циклические зависимости в сочетании с WeakRef и Symbol.dispose создают неочевидную проблему: коллбэки очистки замыкают целые графы объектов, превращая WeakRef в бессмысленный инструмент. Разработчики часто доверяют GC, не замечая, что память не освобождается годами.
Как возникает теневая связность
Когда вы регистрируете коллбэк в WeakMap на основе WeakRef, замыкание захватывает dispose-функцию, которая тянет весь service-граф. Даже если WeakRef.deref() возвращает undefined, ссылка в замыкании удерживает объекты. Они становятся "полуживыми" - не имеют сильных ссылок, но GC их не собирает из-за скрытой цепочки через dispose.
Диагностика в production
Запуск с
Решение 1: точечное замыкание
Вместо передачи всего service в коллбэк берите только метод через bind:
Это разрывает неявную связь с полным графом.
Решение 2: FinalizationRegistry
Он не удерживает ссылку на объект. Регистрируете коллбэк с heldValue, который не ссылается на service:
Решение 3: отказ от WeakRef при циклах
Для циклических зависимостей используйте reference counting в пуле синглтонов. Надёжнее, хоть и менее элегантно:
Вывод: WeakRef не гарантирует безопасной очистки при наличии замыканий в коллбэках, поэтому всегда профилируйте память в циклах зависимости и используйте FinalizationRegistry или reference counting для long-lived контейнеров.
В long-lived DI-контейнерах циклические зависимости в сочетании с WeakRef и Symbol.dispose создают неочевидную проблему: коллбэки очистки замыкают целые графы объектов, превращая WeakRef в бессмысленный инструмент. Разработчики часто доверяют GC, не замечая, что память не освобождается годами.
Как возникает теневая связность
Когда вы регистрируете коллбэк в WeakMap на основе WeakRef, замыкание захватывает dispose-функцию, которая тянет весь service-граф. Даже если WeakRef.deref() возвращает undefined, ссылка в замыкании удерживает объекты. Они становятся "полуживыми" - не имеют сильных ссылок, но GC их не собирает из-за скрытой цепочки через dispose.
Диагностика в production
Запуск с
--expose-gc и global.gc() не покажет освобождения. Heap snapshot в Chrome DevTools обнаружит объекты без явных strong references, но присутствующие в WeakMap. Это сигнал, что проблема в замыканиях коллбэков. Типичная ошибка: проверка только первого уровня графа.Решение 1: точечное замыкание
Вместо передачи всего service в коллбэк берите только метод через bind:
const handler = service[Symbol.dispose]?.bind(service);
Это разрывает неявную связь с полным графом.
Решение 2: FinalizationRegistry
Он не удерживает ссылку на объект. Регистрируете коллбэк с heldValue, который не ссылается на service:
const registry = new FinalizationRegistry((heldValue) => {});
registry.register(service, cleanupToken, token);Решение 3: отказ от WeakRef при циклах
Для циклических зависимостей используйте reference counting в пуле синглтонов. Надёжнее, хоть и менее элегантно:
class RefCountPool {
private refs = new Map();
acquire(key) { this.refs.set(key, (this.refs.get(key) || 0) + 1); }
release(key) { if (this.refs.get(key) === 1) this.refs.delete(key); }
}Вывод: WeakRef не гарантирует безопасной очистки при наличии замыканий в коллбэках, поэтому всегда профилируйте память в циклах зависимости и используйте FinalizationRegistry или reference counting для long-lived контейнеров.
Redux с React так и не подружился
Без обид, но тупо взять Redux и воткнуть его в React не выйдет. Придётся тащить за собой ещё и «react-redux». Тогда зачем эти сказки, что «Redux works with any UI layer»?
Разбираемся: https://habr.com/ru/articles/1058110/
👉 Node.JS [ru]
Без обид, но тупо взять Redux и воткнуть его в React не выйдет. Придётся тащить за собой ещё и «react-redux». Тогда зачем эти сказки, что «Redux works with any UI layer»?
Разбираемся: https://habr.com/ru/articles/1058110/
Please open Telegram to view this post
VIEW IN TELEGRAM
😁2
Неявный рассинхрон состояния при AsyncLocalSnapshot, EventEmitter и Symbol.asyncDispose
Работаете с распределёнными очередями задач? Тогда наверняка сталкивались с
Почему контекст утекает
Production-пример ошибки
Типичный сценарий: воркер запускает задачу, подписывается на события прогресса через
Типичная ошибка
Забывают реализовать
Практический совет
Для долгоживущих слушателей не используйте
Вывод: Управляйте временем жизни подписок явно —
Работаете с распределёнными очередями задач? Тогда наверняка сталкивались с
AsyncLocalStorage для проброса контекста. Но связка AsyncLocalSnapshot с EventEmitter и забытый Symbol.asyncDispose приводят к багу, который воспроизводится раз на 50 запусков — контекст устаревает, состояние утекает.Почему контекст утекает
AsyncLocalSnapshot замораживает контекст при создании, но колбэк от EventEmitter может жить дольше задачи. Если слушатель не удалён, он выполняется с устаревшим состоянием. В production это даёт stale data — задача обрабатывается с контекстом предыдущей или undefined.Production-пример ошибки
Типичный сценарий: воркер запускает задачу, подписывается на события прогресса через
emitter.on, но не чистит слушатель. В итоге в следующей задаче AsyncLocalSnapshot возвращает старый контекст. Код:const snapshot = new AsyncLocalSnapshot(als);
emitter.on('event', snapshot.run(data => {
console.log(als.getStore()); // устаревший контекст
}));
// Слушатель не удалён — баг
Типичная ошибка
Забывают реализовать
Symbol.asyncDispose для временных слушателей. Или создают снапшот вне обработчика, вместо того чтобы делать это внутри. Совет: всегда используйте try/finally для очистки подписок.Практический совет
Для долгоживущих слушателей не используйте
AsyncLocalSnapshot — сохраняйте контекст в замыкании. Если нужно, чистите через finally или Symbol.asyncDispose:class Worker {
async [Symbol.asyncDispose]() {
emitter.removeAllListeners('progress');
}
}Вывод: Управляйте временем жизни подписок явно —
AsyncLocalSnapshot это моментальный снимок, а не долгоживущий контекст.This media is not supported in your browser
VIEW IN TELEGRAM
Виртуальные Scroll-контейнеры
Йо, народ. Сегодня заценим инструмент ng-virtual-scroll-view. В прошлый раз я раскатывал про универсальные виртуализированные списки, которые внутри юзали одноосевой виртуальный scroll-контейнер. Решил из проекта ng-virtual-list выковать еще одну штуку — двухосевой виртуальный scroll-контейнер ng-virtual-scroll-view. Поддерживает Angular с 14 по 22 версии. API и примеры использования — в документации. Читай дальше
👉 Node.JS [ru]
Йо, народ. Сегодня заценим инструмент ng-virtual-scroll-view. В прошлый раз я раскатывал про универсальные виртуализированные списки, которые внутри юзали одноосевой виртуальный scroll-контейнер. Решил из проекта ng-virtual-list выковать еще одну штуку — двухосевой виртуальный scroll-контейнер ng-virtual-scroll-view. Поддерживает Angular с 14 по 22 версии. API и примеры использования — в документации. Читай дальше
Please open Telegram to view this post
VIEW IN TELEGRAM
npm 12, TypeScript 7.0 и Bun на Rust
Выкатили npm 12 — install-скрипты теперь выключены по дефолту. Сломов дофига, так что сперва лучше проапгрейдиться до 11.18.0. GitHub
Финальный TypeScript 7.0 — на Go и «в 10 раз быстрее». Но полный API пока не докрутили, поэтому многие советуют пока не слезать с 6.0.
Bun переехал с Zig на Rust — автор выкатил полный репортаж о том, как портировал рантайм. Кучу инстансов Claude Code припахали, суммарно сожгли ~$165k по API-ценам. Rust-версия станет базой для Bun 1.4, его ждут с часу на час. Jarred Sumner
Node.js 26.5.0 — заимпортили текстовые файлы в ES-модулях через import attributes и blob.textStream().
👉 Node.JS [ru]
Выкатили npm 12 — install-скрипты теперь выключены по дефолту. Сломов дофига, так что сперва лучше проапгрейдиться до 11.18.0. GitHub
Финальный TypeScript 7.0 — на Go и «в 10 раз быстрее». Но полный API пока не докрутили, поэтому многие советуют пока не слезать с 6.0.
Bun переехал с Zig на Rust — автор выкатил полный репортаж о том, как портировал рантайм. Кучу инстансов Claude Code припахали, суммарно сожгли ~$165k по API-ценам. Rust-версия станет базой для Bun 1.4, его ждут с часу на час. Jarred Sumner
Node.js 26.5.0 — заимпортили текстовые файлы в ES-модулях через import attributes и blob.textStream().
Please open Telegram to view this post
VIEW IN TELEGRAM
😁3👍2
Неявное зависание Promise.all при отмене AsyncIterator через AbortController
Вы используете
Как возникает утечка
Когда
Диагностика на практике
Добавьте в конец
Совет по исправлению
Всегда оборачивайте тело асинхронного генератора в
Вывод: Каждый
Вы используете
for await...of с асинхронным генератором и AbortController для отмены. Кажется, всё чисто, но Promise.all зависает намертво. Проблема в том, что вызов abort() вызывает return() генератора, но если внутри остался незавершённый промис (таймер, подписка, поток), он просто висит, и Promise.all никогда не завершится.Как возникает утечка
Когда
abort() срабатывает внутри for await...of, итератор завершается. Но если в генераторе есть запущенный, но не завершённый setInterval, await на потоке данных или подписка на событие, этот промис остаётся висеть в памяти. Например, генератор каждую секунду отправляет данные, а при abort обработчик завершает итерацию, но не очищает таймер. Promise.all ждёт этот таймер вечно.Диагностика на практике
Добавьте в конец
return() генератора console.log('return called'). Если return вызывается, а Promise.all не завершается — ищите, какой ресурс утекает. Используйте обёртку с таймаутом: Promise.race([Promise.all(...), AbortSignal.timeout(5000)]). В продакшне это спасёт от вечного ожидания, но не решит корень проблемы.Совет по исправлению
Всегда оборачивайте тело асинхронного генератора в
try/finally. В finally явно очищайте все ресурсы: удаляйте слушатели, отменяйте таймеры, разрешайте ожидающие промисы. Например, если внутри генератора есть setInterval:async function* gen(signal) {
const timer = setInterval(() => {}, 1000);
try {
while (!signal.aborted) {
await new Promise(r => setTimeout(r, 100));
yield data;
}
} finally {
clearInterval(timer);
}
}Вывод: Каждый
yield и await в асинхронном генераторе должен быть гарантированно завершён до конца итерации, иначе return() не прервёт внутренние промисы, что приведёт к невосстанавливаемому зависанию.❤1👍1
MinIO, MongoDB, PostgreSQL для хранения 25 лет истории стоимости акций
💾 Когда ты строишь эмулятор для проверки торговой стратегии — 20 акций, 25 лет поминутных данных — выбор хранилища превращается в головную боль архитектурного уровня.
Автор раскурил, почему MinIO тут не взлетел, где MongoDB уперлась в потолок, и как PostgreSQL с Pgpool-II и read-репликами срезал время чтения свечи с 40мс до 10мс.
Читать далее: https://habr.com/ru/articles/1059062/
👉 Node.JS [ru]
Автор раскурил, почему MinIO тут не взлетел, где MongoDB уперлась в потолок, и как PostgreSQL с Pgpool-II и read-репликами срезал время чтения свечи с 40мс до 10мс.
Читать далее: https://habr.com/ru/articles/1059062/
Please open Telegram to view this post
VIEW IN TELEGRAM
Как получить оффер в Яндекс, Сбер, МТС и другие крутые компании?
На фото ребята из моего комьюнити, где уже больше 220 frontend-разработчиков. Все они успешно устроились в крутые компании и получили офферы от 300 тыс рублей 🫂
👋 Привет, меня зовут Тихон, я действующий Frontend-разработчик и ментор. За руку довожу до оффера в Big Tech и остаюсь рядом на испытательном сроке.
И у тебя тоже получится 😎
У меня есть канал, где я:
👉 Показываю, как пройти HR-фильтр и превратить отклики в реальные приглашения
👉 Помогаю найти мотивацию и разбираюсь с убеждениями, которые мешают расти
👉 На примерах показываю, как проходить собеседования, включая техничку
👉 Разбираю резюме и делюсь лайфхаками — например, как аккуратно «пинговать» рекрутеров
И регулярно выкладываю полезное:
▪️ Задачи, на которых валятся кандидаты
▪️ Подборка из 100+ каналов с вакансиями для разработчиков
▪️ 100 вопросов, которые помогут тебе на собеседовании
▪️ Чек-лист для проверки своего резюме
Подписывайся: https://t.me/+KcX6eD0fuRY3M2Uy
Реклама, erid: 2W5zFJKcvZP ИП Галактионов Тихон Витальевич, ИНН 771618975809
На фото ребята из моего комьюнити, где уже больше 220 frontend-разработчиков. Все они успешно устроились в крутые компании и получили офферы от 300 тыс рублей 🫂
👋 Привет, меня зовут Тихон, я действующий Frontend-разработчик и ментор. За руку довожу до оффера в Big Tech и остаюсь рядом на испытательном сроке.
В их жизни больше нет бесконечных скучных созвонов, безуспешных откликов на вакансии, странноватых собесов с вопросами словно ты на олимпиаде.
И у тебя тоже получится 😎
У меня есть канал, где я:
👉 Показываю, как пройти HR-фильтр и превратить отклики в реальные приглашения
👉 Помогаю найти мотивацию и разбираюсь с убеждениями, которые мешают расти
👉 На примерах показываю, как проходить собеседования, включая техничку
👉 Разбираю резюме и делюсь лайфхаками — например, как аккуратно «пинговать» рекрутеров
И регулярно выкладываю полезное:
▪️ Задачи, на которых валятся кандидаты
▪️ Подборка из 100+ каналов с вакансиями для разработчиков
▪️ 100 вопросов, которые помогут тебе на собеседовании
▪️ Чек-лист для проверки своего резюме
Подписывайся: https://t.me/+KcX6eD0fuRY3M2Uy
Реклама, erid: 2W5zFJKcvZP ИП Галактионов Тихон Витальевич, ИНН 771618975809
Point0 — очередной фулстек-фреймворк, который уже успели обозвать конкурентом tRPC. Судя по описанию, DX тут реально похож: те же квери и мутации. Но Point0 идет дальше — позволяет объявлять запросы прямо в клиентских файлах вместе с серверной логикой, сам шарит за гидрацию и дегидрацию при SSR, умеет швырять файлы через FormData, выдает стабильные урлы для эндпоинтов и может отдавать серверные компоненты или интерактивные острова.
Плюс фреймворк не кладет редактор мордой в стол, когда эндпоинтов становится дофига, и нормально рулит состояниями загрузки. Автор предполагает, что ты уже шаришь за tRPC — так что в статье накидали примеров на нем для сравнения.
Читать далее
👉 Node.JS [ru]
Плюс фреймворк не кладет редактор мордой в стол, когда эндпоинтов становится дофига, и нормально рулит состояниями загрузки. Автор предполагает, что ты уже шаришь за tRPC — так что в статье накидали примеров на нем для сравнения.
Читать далее
Please open Telegram to view this post
VIEW IN TELEGRAM
Диагностика неявного deadlock в async_hooks при IPC в Node.js кластерах
Вы сталкивались с ситуацией, когда кластеризованное Node.js приложение потребляет 100% CPU без видимых ошибок, а event loop замирает? Часто причина — неявный deadlock в
Гонка за ресурсами IPC
Production-пример: кластер с обработкой запросов, где каждый воркер отправляет результат через send и закрывает порт. После пары тысяч запросов — deadlock, все воркеры зависают.
Диагностика
Чтобы отловить проблему, включите логирование
-
-
- No errors in logs
Практический совет: исправление
Замените синхронный
Хороший паттерн:
Используйте
Профилактика
- Ставьте timeout на все IPC-операции
- Мониторьте
- Гоняйте кластер под нагрузкой с логированием ресурсов
Вывод: Синхронный IPC-вызов с последующим закрытием порта создает неявный deadlock в
Вы сталкивались с ситуацией, когда кластеризованное Node.js приложение потребляет 100% CPU без видимых ошибок, а event loop замирает? Часто причина — неявный deadlock в
async_hooks, возникающий при смешивании синхронных IPC-вызовов через child_process.send и MessagePort.close в воркерах.Гонка за ресурсами IPC
send() в child_process синхронно блокируется до отправки данных. Если следом вызвать port.close(), возникает замыкание: send() удерживает IPC-ресурс, close() ждет его освобождения, а async_hooks зависает в состоянии "ожидания завершения". Результат — ресурсы не освобождаются, ошибок нет, CPU занят вхолостую.Production-пример: кластер с обработкой запросов, где каждый воркер отправляет результат через send и закрывает порт. После пары тысяч запросов — deadlock, все воркеры зависают.
Диагностика
Чтобы отловить проблему, включите логирование
async_hooks с таймстампами:-
init для IPC-каналов появляется-
destroy приходит с задержкой или отсутствует вовсе- No errors in logs
Практический совет: исправление
Замените синхронный
send на асинхронный. Плохой паттерн:send({ data }, (err) => { port.close(); });Хороший паттерн:
await new Promise(resolve => process.send({ data }, resolve));
port.close();Используйте
MessageChannel вместо child_process.send — он избегает блокировки. Если deadlock уже произошел, убивайте воркер через worker.kill(), а не process.exit().Профилактика
- Ставьте timeout на все IPC-операции
- Мониторьте
async_hooks метрики через performance hooks- Гоняйте кластер под нагрузкой с логированием ресурсов
Вывод: Синхронный IPC-вызов с последующим закрытием порта создает неявный deadlock в
async_hooks, который проявляется только как 100% CPU и зависший event loop — переходите на асинхронные паттерны для всех межпроцессных коммуникаций.Please open Telegram to view this post
VIEW IN TELEGRAM
Зачем долбить бинарный поиск, если платят за кнопки
Слушай, на Хабре уже лет сто ломают копья: алгоритмы в реальной работе — это маст-хэв или просто пыль для собесных задачек? Вот очередной смельчак решил влезть в эту свару. Когда он только вкатывался во фронтенд, его мучили те же вопросы: «Нахрена меня на собесе гоняют по обходу графа, если по факту я только правлю отступы в CSS?» или «Какая мне разница до O(N), если верстка все равно разваливается в Safari?» А с приходом AI эти вопросы зазвучали еще громче: «Зачем мне вообще парить мозг, если нейронка склепает цикл за пару секунд?»
Но чем глубже автор закапывался в алгоритмические задачи, тем чаще замечал, что они всплывают в реальных проектах, когда задача оказывается сложнее, чем «зацентрировать див». Он научился собирать бизнес-фичи компактнее, быстрее выискивать места, которые конкретно тормозят прод, и — самое банальное — перестал сомневаться в своих действиях.
Дальше
👉 Node.JS [ru]
Слушай, на Хабре уже лет сто ломают копья: алгоритмы в реальной работе — это маст-хэв или просто пыль для собесных задачек? Вот очередной смельчак решил влезть в эту свару. Когда он только вкатывался во фронтенд, его мучили те же вопросы: «Нахрена меня на собесе гоняют по обходу графа, если по факту я только правлю отступы в CSS?» или «Какая мне разница до O(N), если верстка все равно разваливается в Safari?» А с приходом AI эти вопросы зазвучали еще громче: «Зачем мне вообще парить мозг, если нейронка склепает цикл за пару секунд?»
Но чем глубже автор закапывался в алгоритмические задачи, тем чаще замечал, что они всплывают в реальных проектах, когда задача оказывается сложнее, чем «зацентрировать див». Он научился собирать бизнес-фичи компактнее, быстрее выискивать места, которые конкретно тормозят прод, и — самое банальное — перестал сомневаться в своих действиях.
Дальше
Please open Telegram to view this post
VIEW IN TELEGRAM
Зависание HTTP/2 прокси: как кастомные priority-фреймы убивают flow control
Когда параллельные стримы с разными приоритетами перестают отвечать, сессия жива, но данные не идут - проблема не в сети, а в управлении потоком. Разработчики часто путают висящие стримы с сетевыми таймаутами, теряя часы на отладку.
Две ловушки flow control
HTTP/2 имеет два окна управления потоком: на сессию (65535 байт по умолчанию) и на каждый стрим. При кастомных приоритетах (например, weight=256 у одного стрима и weight=1 у остальных) прокси начинает насыщать только приоритетный стрим. Остальные ждут, окно соединения исчерпывается - дедлок неизбежен.
Как диагностировать залипание
Чтобы поймать проблему:
- Смотри WINDOW_UPDATE фреймы. Если их число падает на уровне сессии, а на стримах нет - сигнал тревоги.
- Включи логирование priority. Когда более 80% веса приходится на один стрим, жди беды.
- Попробуй увеличить initial window size до мегабайта. Если зависание исчезает, копай в flow control.
Простой пример на Node.js
Вот как управлять окном приоритетов:
Типичная ошибка и её решение
При кастомных приоритетах убедись, что сумма весов не даёт перекоса под 80% на один стрим. Следи за stream.state: если localWindowSize почти ноль, а remoteWindowSize нет - это залипание. На прокси-сервере проверяй, не блокирует ли nghttp2 флаг NGHTTP2_FLAG_PRIORITY_OPEN.
Вывод: Контроль баланса весов приоритетов и мониторинг WINDOW_UPDATE фреймов на уровне сессии критичен для предотвращения дедлоков в параллельных стримах HTTP/2.
Когда параллельные стримы с разными приоритетами перестают отвечать, сессия жива, но данные не идут - проблема не в сети, а в управлении потоком. Разработчики часто путают висящие стримы с сетевыми таймаутами, теряя часы на отладку.
Две ловушки flow control
HTTP/2 имеет два окна управления потоком: на сессию (65535 байт по умолчанию) и на каждый стрим. При кастомных приоритетах (например, weight=256 у одного стрима и weight=1 у остальных) прокси начинает насыщать только приоритетный стрим. Остальные ждут, окно соединения исчерпывается - дедлок неизбежен.
Как диагностировать залипание
Чтобы поймать проблему:
- Смотри WINDOW_UPDATE фреймы. Если их число падает на уровне сессии, а на стримах нет - сигнал тревоги.
- Включи логирование priority. Когда более 80% веса приходится на один стрим, жди беды.
- Попробуй увеличить initial window size до мегабайта. Если зависание исчезает, копай в flow control.
Простой пример на Node.js
Вот как управлять окном приоритетов:
const http2 = require('http2');
const { HTTP2_HEADER_PATH, HTTP2_HEADER_METHOD } = http2.constants;
const client = http2.connect('https://example.com');
client.setLocalWindowSize(1048576);
async function sendRequest(path) {
return new Promise((resolve, reject) => {
const stream = client.request({
[HTTP2_HEADER_PATH]: path,
[HTTP2_HEADER_METHOD]: 'GET',
});
let data = '';
stream.on('data', chunk => data += chunk);
stream.on('end', () => resolve(data));
stream.on('error', reject);
});
}
Promise.all([
sendRequest('/api/1'),
sendRequest('/api/2'),
sendRequest('/api/3')
]);Типичная ошибка и её решение
При кастомных приоритетах убедись, что сумма весов не даёт перекоса под 80% на один стрим. Следи за stream.state: если localWindowSize почти ноль, а remoteWindowSize нет - это залипание. На прокси-сервере проверяй, не блокирует ли nghttp2 флаг NGHTTP2_FLAG_PRIORITY_OPEN.
Вывод: Контроль баланса весов приоритетов и мониторинг WINDOW_UPDATE фреймов на уровне сессии критичен для предотвращения дедлоков в параллельных стримах HTTP/2.
В Strapi есть забавная особенность: формат ответа напрямую зависит от того, что ты ГОВОРИШЬ ему в запросе. Попросил статью без
Авторы разбирают, как выкрутиться из этой задницы через кодогенерацию: вытягиваешь схему с живого Strapi, по ней генеряешь TypeScript-клиент и заставляешь систему типов на этапе компиляции вычислять тип ответа прямо из параметра
Читать статью
👉 Node.JS [ru]
populate — на тебе голые скалярные поля. Сунул populate: { category: true } — и уже подтянулась вложенная категория. А если еще и fields: ['name'] добавил внутрь — она придет обнуленной. Получается, один и тот же эндпоинт возвращает кучу разных форм, и стандартный TypeScript-интерфейс нормально не опишешь: он либо обманывает, заявляя поля, которых в ответе на самом деле нету, либо просто бесполезен, превращаясь в то самое any.Авторы разбирают, как выкрутиться из этой задницы через кодогенерацию: вытягиваешь схему с живого Strapi, по ней генеряешь TypeScript-клиент и заставляешь систему типов на этапе компиляции вычислять тип ответа прямо из параметра
populate. Принцип — как у Prisma: что передал в вызов, то тебе типы и вернут. Код на TypeScript, сам клиент опенсорсный под лицензией MIT.Читать статью
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1