Скрытый убийца производительности: синхронный DNS в
Высоконагруженный HTTP-клиент на Node.js внезапно теряет пропускную способность? Event loop залипает при большом количестве параллельных запросов? Чаще всего проблема не в HTTP, а в том, как система разрешает доменные имена.
В чем суть?
Как диагностировать?
1. Подключить
2. Использовать
3.
Как устранить?
Для
Типичная ошибка: использование только
Вывод: блокировка из-за
dns.lookup под нагрузкойВысоконагруженный HTTP-клиент на Node.js внезапно теряет пропускную способность? Event loop залипает при большом количестве параллельных запросов? Чаще всего проблема не в HTTP, а в том, как система разрешает доменные имена.
В чем суть?
dns.lookup (используется http.request, https, axios по умолчанию) при вызове без флагов обращается к системному getaddrinfo через libuv. Этот вызов синхронный и блокирует event loop на время резолвинга. При 1000+ RPS latency одного разрешения (например, 50 мс) превращается в 50 000 мс общего блокирования цикла.Как диагностировать?
1. Подключить
dns.promises и выполнить dns.promises.lookup при нагрузке. Если event loop задерживает микротаски, виновник найден.2. Использовать
process.hrtime.bigint() до и после dns.lookup - разница > 10 мс системного времени указывает на проблему.3.
clinic doctor - на графике async latency видны ступеньки, совпадающие с пиками DNS-запросов.Как устранить?
Для
http(s) агентов - замена на асинхронный DNS:const { createConnection } = require('net');
const dns = require('dns/promises');
const agent = new http.Agent({
createConnection: async (options, cb) => {
const { address } = await dns.lookup(options.hostname);
const socket = createConnection({ ...options, host: address });
cb(null, socket);
},
});Типичная ошибка: использование только
maxSockets или scheduling: 'lifo' снижает частоту новых соединений, но не решает первопричину при множестве уникальных хостов. Проще - библиотеки dns-cache или cacheable-lookup.Вывод: блокировка из-за
dns.lookup - классическая проблема одного потока Node.js на стыке системных вызовов, решается заменой на асинхронное разрешение через dns.promises или кастомный агент.Диагностика и устранение неявной фрагментации кучи в Node.js
Long-lived HTTP серверы с разными по размеру payload часто страдают от "дырявой" кучи, хотя утечек памяти нет. Память фрагментируется из-за чередования маленьких и больших Buffer-ов, что ведет к росту RSS и частым GC паузам.
Как проявляется фрагментация
После обработки HTTP запросов создаются Buffer-ы разного размера: маленькие для заголовков, большие для тела. GC освобождает их неупорядоченно, оставляя пустоты между выделенными блоками. Со временем это увеличивает RSS на 20-40% без утечек. Симптомы: падение производительности через 12+' часов работы, частые stop-the-world паузы GC.
Диагностика в production
Первый шаг — трассировка с
Решение: Buffer Pool для однородности
Создайте пул Buffer-ов фиксированного сегмента, например 1024 байта. Это снижает разнообразие размеров и уменьшает фрагментацию. Пример:
Практические советы и ошибки
- Для максимального снижения фрагментации используйте
- Настройте
- Типичная ошибка — использовать
- В Node 20+ можно изолировать тяжелые операции через
Вывод: Фрагментация кучи в long-lived серверах — реальная проблема, решаемая через Buffer Pool с фиксированным сегментом и контроль аллокаций, а не через поиск утечек.
Long-lived HTTP серверы с разными по размеру payload часто страдают от "дырявой" кучи, хотя утечек памяти нет. Память фрагментируется из-за чередования маленьких и больших Buffer-ов, что ведет к росту RSS и частым GC паузам.
Как проявляется фрагментация
После обработки HTTP запросов создаются Buffer-ы разного размера: маленькие для заголовков, большие для тела. GC освобождает их неупорядоченно, оставляя пустоты между выделенными блоками. Со временем это увеличивает RSS на 20-40% без утечек. Симптомы: падение производительности через 12+' часов работы, частые stop-the-world паузы GC.
Диагностика в production
Первый шаг — трассировка с
--trace-gc. Запустите: NODE_OPTIONS="--trace-gc" node app.js. В выводе ищите Mark-sweep паузы >50ms. Второй шаг — сравнение heapdump-ов через час и сутки работы в Chrome DevTools. Ищите фрагментированные объекты Buffer, разбросанные по куче. Третий шаг — проверьте флаги V8: node --v8-options | grep -i "heap.*frag".Решение: Buffer Pool для однородности
Создайте пул Buffer-ов фиксированного сегмента, например 1024 байта. Это снижает разнообразие размеров и уменьшает фрагментацию. Пример:
class BufferPool {
constructor(size = 1024) {
this.pool = [];
this.size = size;
}
alloc(size) {
if (size > this.size) return Buffer.allocUnsafe(size);
if (this.pool.length) return this.pool.pop().slice(0, size);
return Buffer.allocUnsafe(this.size);
}
free(buf) { this.pool.push(buf); }
}Практические советы и ошибки
- Для максимального снижения фрагментации используйте
Buffer.allocUnsafeSlow вместо Buffer.allocUnsafe в пуле — он избегает shared memory pool.- Настройте
--max-old-space-size и --optimize-for-size для ограничения кучи.- Типичная ошибка — использовать
Buffer.allocUnsafe для всех случаев без разбора. Это увеличивает фрагментацию из-за случайных размеров.- В Node 20+ можно изолировать тяжелые операции через
--experimental-vm-modules в отдельных контекстах.Вывод: Фрагментация кучи в long-lived серверах — реальная проблема, решаемая через Buffer Pool с фиксированным сегментом и контроль аллокаций, а не через поиск утечек.
Неявный deadlock в Node.js: AsyncLocalStorage + worker_threads + await в run()
Многие знают AsyncLocalStorage для проброса контекста, но скрещивание его с пулом worker_threads и await внутри колбэка run() создаёт неочевидный deadlock. Эта ошибка часто остаётся незамеченной до первого production-инцидента, когда пул потоков залипает при высокой нагрузке.
Как проявляется deadlock
Внутри run() вы помещаете ID запроса и вызываете await, пока воркер не закончит задачу. Если потоков в пуле ограниченное число (например, 2), а запросов много, каждый await блокирует run(), не освобождая поток из пула. Воркеры заняты, потоки ждут await — контекст AsyncLocalStorage не отпускается до полного завершения колбэка.
Пример problem:
Как обнаружить
- Залогируйте время выполнения воркеров: рост с увеличением числа запросов — тревожный сигнал.
- Используйте clinic.js или 0x: они покажут аномальные пики ожидания.
- Типичная ошибка: пул из 2 воркеров и 100 параллельных запросов — все зависнут через несколько секунд.
Практический совет
Не оставляйте await внутри run(). Вынесите ожидание наружу:
Либо передавайте контекст через
Предупреждение
AsyncLocalStorage отлично работает на веб-серверах с краткими синхронными операциями, но await внутри run() с ограниченным пулом потоков — прямой путь к deadlock. Проверяйте такие места на этапе code review.
Вывод: Избегайте await внутри колбэка AsyncLocalStorage.run() при работе с пулом worker_threads — выносите асинхронное ожидание наружу или передавайте контекст через сообщения.
Многие знают AsyncLocalStorage для проброса контекста, но скрещивание его с пулом worker_threads и await внутри колбэка run() создаёт неочевидный deadlock. Эта ошибка часто остаётся незамеченной до первого production-инцидента, когда пул потоков залипает при высокой нагрузке.
Как проявляется deadlock
Внутри run() вы помещаете ID запроса и вызываете await, пока воркер не закончит задачу. Если потоков в пуле ограниченное число (например, 2), а запросов много, каждый await блокирует run(), не освобождая поток из пула. Воркеры заняты, потоки ждут await — контекст AsyncLocalStorage не отпускается до полного завершения колбэка.
Пример problem:
const als = new AsyncLocalStorage();
async function processRequest(data) {
await als.run({ requestId: Date.now() }, async () => {
const worker = new Worker('./worker.js');
const result = await new Promise((resolve, reject) => {
worker.on('message', resolve);
worker.on('error', reject);
});
console.log(als.getStore().requestId);
});
}
Как обнаружить
- Залогируйте время выполнения воркеров: рост с увеличением числа запросов — тревожный сигнал.
- Используйте clinic.js или 0x: они покажут аномальные пики ожидания.
- Типичная ошибка: пул из 2 воркеров и 100 параллельных запросов — все зависнут через несколько секунд.
Практический совет
Не оставляйте await внутри run(). Вынесите ожидание наружу:
async function processRequest(data) {
const worker = new Worker('./worker.js');
const result = await new Promise(/*...*/);
als.run({ requestId: Date.now() }, () => {
console.log(als.getStore().requestId);
});
}Либо передавайте контекст через
worker.postMessage() — это проще и исключает deadlock.Предупреждение
AsyncLocalStorage отлично работает на веб-серверах с краткими синхронными операциями, но await внутри run() с ограниченным пулом потоков — прямой путь к deadlock. Проверяйте такие места на этапе code review.
Вывод: Избегайте await внутри колбэка AsyncLocalStorage.run() при работе с пулом worker_threads — выносите асинхронное ожидание наружу или передавайте контекст через сообщения.
Как я хакнул рынок труда: пишем свой ИИ-комбайн для автооткликов на HH.ru
Если вы хоть раз искали работу в IT за последний год, то знаете, что рынок беспощаден к новичкам. Нужно откликнуться на сотни вакансий, а в итоге получаешь отказы от роботов. Чтобы пробиться через фильтры HR, нужно под каждую вакансию писать уникальное сопроводительное письмо. В этой статье автор сделал полный разбор того, как написал собственного автономного ИИ-агента, который ищет вакансии, фильтрует мусор с помощью локальной нейросети, пишет персонализированные сопроводительные письма и отчитывается в Telegram, пока пользователь спокойно занимается своими делами. Скрипт задумывался бесплатным, автономным и не требующим настройки вокруг платных API.
Читать далее
Если вы хоть раз искали работу в IT за последний год, то знаете, что рынок беспощаден к новичкам. Нужно откликнуться на сотни вакансий, а в итоге получаешь отказы от роботов. Чтобы пробиться через фильтры HR, нужно под каждую вакансию писать уникальное сопроводительное письмо. В этой статье автор сделал полный разбор того, как написал собственного автономного ИИ-агента, который ищет вакансии, фильтрует мусор с помощью локальной нейросети, пишет персонализированные сопроводительные письма и отчитывается в Telegram, пока пользователь спокойно занимается своими делами. Скрипт задумывался бесплатным, автономным и не требующим настройки вокруг платных API.
Читать далее
Хабр
Как я хакнул рынок труда: пишем свой ИИ-комбайн для автооткликов на HH.ru
Всем привет! Если вы хоть раз искали работу в IT за последний год, то знаете, что рынок не любит новичков без опыта. Вы откликаетесь на сотни вакансий, получаете отказы от скриптов-автоответчиков, а...
👎3
Buffer.write со str.length — тихая бомба под production.
Ты пишешь в буфер, передаёшь offset через str.length, и данные записываются в неверное место. Всё молча. Никаких ошибок. Просто битая информация.
В UTF-8 один символ может занимать от 1 до 4 байт. «Привет» — это 6 символов, но 12 байт. Если offset посчитан по длине строки, ты фактически говоришь Buffer: «запиши мне данные со смещением 6 байт». А нужно было с 12. Разница в два раза.
Я такое видел в production: кусок бинарного заголовка перезаписывался, файл читался как мусор. И никто не мог понять, почему.
Диагностика
— используй
— сравнивай
— проверяй логи, где размер буфера и смещение не сходятся.
Исправление
Простое решение:
Или вообще запиши с начала:
Типичная ошибка
— расчёт offset вручную через
— молчаливое переполнение буфера без проверки остаточного места;
— путаница между символьным и байтовым смещением в одном бинарном сериализаторе.
Профилактика
— всегда считай байты через
— не вычисляй offset вручную для строк с Unicode;
— в TypeScript включай strict — он ловит часть таких косяков.
Вывод:
Silent data corruption — гадость редкая, но реальная. Проверь код: не используешь ли
Ты пишешь в буфер, передаёшь offset через str.length, и данные записываются в неверное место. Всё молча. Никаких ошибок. Просто битая информация.
В UTF-8 один символ может занимать от 1 до 4 байт. «Привет» — это 6 символов, но 12 байт. Если offset посчитан по длине строки, ты фактически говоришь Buffer: «запиши мне данные со смещением 6 байт». А нужно было с 12. Разница в два раза.
Я такое видел в production: кусок бинарного заголовка перезаписывался, файл читался как мусор. И никто не мог понять, почему.
Диагностика
— используй
Buffer.byteLength(str, 'utf8') для вычисления реального размера в байтах;— сравнивай
str.length и Buffer.byteLength(str) — если различаются, в строке есть не-ASCII символы;— проверяй логи, где размер буфера и смещение не сходятся.
Исправление
Простое решение:
const byteOffset = Buffer.byteLength(str, 'utf8');
buf.write(str, byteOffset);
Или вообще запиши с начала:
buf.write(str, 0, 'utf8').Типичная ошибка
— расчёт offset вручную через
str.length * someCoefficient — это антипаттерн;— молчаливое переполнение буфера без проверки остаточного места;
— путаница между символьным и байтовым смещением в одном бинарном сериализаторе.
Профилактика
— всегда считай байты через
Buffer.byteLength();— не вычисляй offset вручную для строк с Unicode;
— в TypeScript включай strict — он ловит часть таких косяков.
Вывод:
Silent data corruption — гадость редкая, но реальная. Проверь код: не используешь ли
str.length как offset для Buffer.write?❤1
PROvoke на Analyst Days: как мы выступали в корсете и что из этого вышло
Привет, Хабр! В первой части я рассказывал, как мы с Татьяной Маркиной готовили доклад «Провоок» на Analyst Days — про споры, студию в Краснодаре, логистический ад и идею с кнутом и пряником. А сегодня — вторая часть: что произошло на сцене, какие роли мы играли, почему корсет так и не вышел в свет, и как один разговор в кулуарах перевернул моё понимание публичных выступлений. Подробности — во второй части статьи.
Источник: Анатомия парного доклада: как мы собирали «PROvoke» на Analyst Days. Часть 2 — выступление и фидбек
Привет, Хабр! В первой части я рассказывал, как мы с Татьяной Маркиной готовили доклад «Провоок» на Analyst Days — про споры, студию в Краснодаре, логистический ад и идею с кнутом и пряником. А сегодня — вторая часть: что произошло на сцене, какие роли мы играли, почему корсет так и не вышел в свет, и как один разговор в кулуарах перевернул моё понимание публичных выступлений. Подробности — во второй части статьи.
Источник: Анатомия парного доклада: как мы собирали «PROvoke» на Analyst Days. Часть 2 — выступление и фидбек
Как один отказ бизнесу сэкономил компании 10 миллионов рублей
К концу проекта внедрения нового биллинга бизнес предложил перенести архивные продукты и исторические данные по ним. Архивные продукты использовались редко — для разбора спорных ситуаций с клиентами. Изначально было решено не избавляться от старого биллинга, а использовать его для чтения ретроданных. После оценки работ выяснилось, что типового механизма нет — нужно менять архитектуру, разрабатывать сценарии миграции, тестировать и поддерживать. Оценка составила около 10 миллионов рублей. Пересчет финансовой модели показал, что дополнительные инвестиции увеличивают срок окупаемости проекта на один год. Компания вкладывала бы деньги в функциональность без измеримой бизнес-ценности. Было принято решение отказать бизнесу и предложить оставить эти данные доступными для чтения в legacy биллинге.
Источник
К концу проекта внедрения нового биллинга бизнес предложил перенести архивные продукты и исторические данные по ним. Архивные продукты использовались редко — для разбора спорных ситуаций с клиентами. Изначально было решено не избавляться от старого биллинга, а использовать его для чтения ретроданных. После оценки работ выяснилось, что типового механизма нет — нужно менять архитектуру, разрабатывать сценарии миграции, тестировать и поддерживать. Оценка составила около 10 миллионов рублей. Пересчет финансовой модели показал, что дополнительные инвестиции увеличивают срок окупаемости проекта на один год. Компания вкладывала бы деньги в функциональность без измеримой бизнес-ценности. Было принято решение отказать бизнесу и предложить оставить эти данные доступными для чтения в legacy биллинге.
Источник
Гео-аналитическая платформа вдвоём за 2,5 месяца: история spec-driven разработки
Автор поделился опытом старта проекта с чистого листа. Перед ним стояла амбициозная задача, не было легаси и была полная свобода выбора подхода. Он решил максимально довериться AI и больше не трогать код. Изначально были сомнения, что такой подход выдержит реальный масштаб, так как опыт подсказывал, что чем больше проект, тем быстрее AI путается в контексте и упирается в лимиты.
Через 2,5 месяца работы вдвоём удалось запустить гео-аналитическую платформу. Автор отмечает, что в до-AI эпоху такой проект строили бы годами. Этот опыт изменил его представление о разработке.
Читать далее
Автор поделился опытом старта проекта с чистого листа. Перед ним стояла амбициозная задача, не было легаси и была полная свобода выбора подхода. Он решил максимально довериться AI и больше не трогать код. Изначально были сомнения, что такой подход выдержит реальный масштаб, так как опыт подсказывал, что чем больше проект, тем быстрее AI путается в контексте и упирается в лимиты.
Через 2,5 месяца работы вдвоём удалось запустить гео-аналитическую платформу. Автор отмечает, что в до-AI эпоху такой проект строили бы годами. Этот опыт изменил его представление о разработке.
Читать далее
Маск внезапно выкатил Grok 4.5 — первую версию, заточенную под код и агентов 😨
Обучали её в связке с Cursor, и по бенчмаркам она уже перепрыгивает GPT-5.5, заодно держится на уровне Opus 4.8. Terminal-Bench: у неё 83%, а у Opus 4.8 — 78,9%.
Прайсинг: $2/$6 за миллион токенов. «Opus-уровень, но шустрее, меньше жрет токенов и дешевле» — написал Маск в X. В Cursor она уже есть на всех тарифах. Завтра ждем ответочку от Альтмана😊
👉 Node.JS [ru]
Обучали её в связке с Cursor, и по бенчмаркам она уже перепрыгивает GPT-5.5, заодно держится на уровне Opus 4.8. Terminal-Bench: у неё 83%, а у Opus 4.8 — 78,9%.
Прайсинг: $2/$6 за миллион токенов. «Opus-уровень, но шустрее, меньше жрет токенов и дешевле» — написал Маск в X. В Cursor она уже есть на всех тарифах. Завтра ждем ответочку от Альтмана
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Грейсфул шатдаун: как SIGTERM и postMessage ломают порядок сигналов
Каждый middle/senior разработчик Node.js сталкивался с грейсфул шатдауном, но редкий замечает скрытую ловушку — смешивание
Проблема приоритетов событий
SIGTERM — системное событие с высоким приоритетом в цикле событий.
Production-пример и диагностика
Рассмотрим типичный код:
Здесь
* Логируйте порядок событий с таймстемпами.
* Используйте
* Проверяйте флаг
Практическое исправление
Решение — флаги состояния:
Для надёжности —
Типичная ошибка
Многие путают: проблема не в доступе к
Вывод: Используйте флаги состояния перед асинхронными операциями в обработчиках сигналов — это предотвращает потерю данных при грейсфул шатдауне.
Каждый middle/senior разработчик Node.js сталкивался с грейсфул шатдауном, но редкий замечает скрытую ловушку — смешивание
process.on('SIGTERM') и Worker.prototype.postMessage. Из-за разницы приоритетов событий и микрозадач теряются данные в production.Проблема приоритетов событий
SIGTERM — системное событие с высоким приоритетом в цикле событий.
postMessage — асинхронная операция, попадающая в очередь микрозадач. Если SIGTERM приходит до завершения отправки сообщения, оно обрабатывается уже после shutdown. Результат — потеря данных, которые должны были уйти в Worker.Production-пример и диагностика
Рассмотрим типичный код:
process.on('SIGTERM', () => {
console.log('Shutdown');
worker.terminate();
});
worker.postMessage({ type: 'data', payload: result });Здесь
postMessage может быть прерван сигналом. Как диагностировать:* Логируйте порядок событий с таймстемпами.
* Используйте
process.on('SIGTERM', { once: true }) для однократного вызова.* Проверяйте флаг
isShuttingDown перед любым postMessage.Практическое исправление
Решение — флаги состояния:
let isShuttingDown = false;
process.on('SIGTERM', () => { isShuttingDown = true; });
if (!isShuttingDown) worker.postMessage({ ... });
Для надёжности —
SharedArrayBuffer для атомарной синхронизации между основным потоком и Worker, если требуется строгий контроль.Типичная ошибка
Многие путают: проблема не в доступе к
process из Worker threads — Worker его не имеет. Корень — порядок микрозадач в основном потоке, где postMessage может быть отложен после обработки SIGTERM.Вывод: Используйте флаги состояния перед асинхронными операциями в обработчиках сигналов — это предотвращает потерю данных при грейсфул шатдауне.
👍2❤1
This media is not supported in your browser
VIEW IN TELEGRAM
Claude Code теперь стерильно для стаи
Наваливаться на одну сессию в терминале можно гуртом. Плюс теперь разрешено стыковать свои инстансы Claude, чтоб они балакали между собой.
Потыкать
👉 Node.JS [ru]
Наваливаться на одну сессию в терминале можно гуртом. Плюс теперь разрешено стыковать свои инстансы Claude, чтоб они балакали между собой.
Потыкать
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
OpenAI наконец-то почесались и закатили апдейт: GPT-5.6 Sol, Terra и Luna — уже в проде, дружок. Постепенно раздают ранний доступ по всему шарику.
Вот пруф, если глазам не веришь
👉 Node.JS [ru]
Вот пруф, если глазам не веришь
Please open Telegram to view this post
VIEW IN TELEGRAM
Anthropic раскатал схему работы Fable 5 в обнимку с Sonnet 5 — тут два варианта
Первый вариант — Fable 5 втыкается как «советник» (Advisor). Sonnet 5 тащит задачу, но периодически дёргает Fable 5 за стратегическим советом. На тесте SWE-bench Pro такая связка вывозит примерно 92% от качества Fable 5, при этом платишь всего 63% от её цены. Fable 5 дёргают примерно раз на задачу. Подробнее в документации.
Второй расклад — Fable 5 работает оркестратором. Она разруливает план и раздаёт таски исполнителям — Sonnet 5. В Benchmark-тестах BrowseComp на Claude Managed Agents такая конфигурация показала 96% производительности Fable 5, а стоила 46% от оригинала. Жирные по ресурсам задачи скидывают на Sonnet 5. Пример в кукбуке.
Claude Managed Agents переваривает оба подхода (документация). Каждый подагент держит свой кэш, так что при повторных вызовах не придётся платить за полную пережевку контекста.
👉 Node.JS [ru]
Первый вариант — Fable 5 втыкается как «советник» (Advisor). Sonnet 5 тащит задачу, но периодически дёргает Fable 5 за стратегическим советом. На тесте SWE-bench Pro такая связка вывозит примерно 92% от качества Fable 5, при этом платишь всего 63% от её цены. Fable 5 дёргают примерно раз на задачу. Подробнее в документации.
Второй расклад — Fable 5 работает оркестратором. Она разруливает план и раздаёт таски исполнителям — Sonnet 5. В Benchmark-тестах BrowseComp на Claude Managed Agents такая конфигурация показала 96% производительности Fable 5, а стоила 46% от оригинала. Жирные по ресурсам задачи скидывают на Sonnet 5. Пример в кукбуке.
Claude Managed Agents переваривает оба подхода (документация). Каждый подагент держит свой кэш, так что при повторных вызовах не придётся платить за полную пережевку контекста.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Системный аналитик — не просто почтальон между бизнесом и разрабами. Часто тащит на себе интегратора, деливери-менеджера, продакта, архитектора и даже скрам-мастера. И периодически в него летят таски, которые вообще не его профиль. Это такой челлендж: либо ты закаляешься, либо ловишь ментальный аут.
Это уже второй заход от Александра Малышева, лид-аналитика из МТС Cashback. И тут он расписывает зону ответственности SA. Всё в стихах и с юмором — прямо как народная сказка.
📖 Читать сказку
👉 Node.JS [ru]
Это уже второй заход от Александра Малышева, лид-аналитика из МТС Cashback. И тут он расписывает зону ответственности SA. Всё в стихах и с юмором — прямо как народная сказка.
📖 Читать сказку
Please open Telegram to view this post
VIEW IN TELEGRAM
😁1
Anthropic решила, что пора поживиться, и попыталась снять с парня $16,6 млн просто за красивые глаза 👻
Корейский юзер, у которого бесплатный тариф и ноль запросов к API, получил официальную бумажку с требованием оплатить $16,6 млн. А за день до этого ему же прилетел запрос на $1,6 млн — сумма за ночь выросла в 10 раз. Хорошо, что банк просто зарубил эти попытки из-за лимита карты.
Anthropic пока молчит. И это не единичный случай: на Reddit народ тоже жалуется на внезапные списания от этой конторы.
Инфа сотка 😁
👉 Node.JS [ru]
Корейский юзер, у которого бесплатный тариф и ноль запросов к API, получил официальную бумажку с требованием оплатить $16,6 млн. А за день до этого ему же прилетел запрос на $1,6 млн — сумма за ночь выросла в 10 раз. Хорошо, что банк просто зарубил эти попытки из-за лимита карты.
Anthropic пока молчит. И это не единичный случай: на Reddit народ тоже жалуется на внезапные списания от этой конторы.
Инфа сотка 😁
Please open Telegram to view this post
VIEW IN TELEGRAM
Неявная блокировка Event Loop: как MapIterator убивает производительность в hot-path
Казалось бы, обычный
Почему это происходит
V8 в оптимизированном коде (TurboFan) может не аллоцировать итератор — он сохраняется на стеке или убирается escape analysis. Но деоптимизация из-за полиморфизма типов ключей (строки и числа),
Диагностика на практике
-
-
- Сравни
Практический совет
В hot-path используй
Типичная ошибка
Не проверяешь стабильность типов ключей
Вывод: Аллокация
Казалось бы, обычный
for...of по Map — и вдруг Event Loop начинает тормозить. Проблема в скрытой аллокации MapIterator при деоптимизации V8. В hot-path это приводит к частому GC и блокировкам.Почему это происходит
V8 в оптимизированном коде (TurboFan) может не аллоцировать итератор — он сохраняется на стеке или убирается escape analysis. Но деоптимизация из-за полиморфизма типов ключей (строки и числа),
try/catch внутри цикла или превышения inline-лимитов заставляет создавать MapIterator в куче. В production это вызывает пинг-понг с GC.Диагностика на практике
-
--trace-gc — ищем аллокации MapIterator.-
--trace-deopt — смотрим причину: часто смешение типов ключей или нестабильный полиморфизм.- Сравни
for...of, map.forEach и ручной обход через Array.from(map.keys()) в бенчмарке. Разница может быть кратной.Практический совет
В hot-path используй
forEach, а не for...of. V8 может встроить его колбэк и избежать аллокации итератора. Если нужны ключи — закешируй их через Array.from и обходи обычным циклом.Типичная ошибка
Не проверяешь стабильность типов ключей
Map. Если ключи смешанные (строки и числа) — for...of гарантированно деоптимизируется. Зафиксируй типы или используй forEach.Вывод: Аллокация
MapIterator в hot-path из-за деоптимизации V8 — скрытая причина лагов Event Loop; замена for...of на map.forEach или кеширование ключей через Array.from устраняет проблему без ущерба для читаемости.❤2