Почему «удалёнка» съедает ROI компании: расчёты потерь
В агентстве «Найт Стрит» считают, что затраты на удалёнку обычно считают по очевидной экономии: не нужен большой офис, падают расходы на командировки, нанимать можно по всей стране. Но у распределённой работы есть вторая колонка расходов, которая почти не попадает в расчёты бухгалтера. Сюда идёт лишний созвон там, где хватило бы сообщения, согласование на два дня вместо десяти минут общения в переговорке, новичок, который вникает в проект месяц, а не положенные две недели.
По отдельности это мелочи, но в команде из сотни дорогих специалистов все эти мелочи складываются в миллионы потерянной выручки за год. Цифры в статье модельные: смысл не в том, чтобы поверить процентам, а в том, чтобы подставить в формулы свои данные.
Читать далее
В агентстве «Найт Стрит» считают, что затраты на удалёнку обычно считают по очевидной экономии: не нужен большой офис, падают расходы на командировки, нанимать можно по всей стране. Но у распределённой работы есть вторая колонка расходов, которая почти не попадает в расчёты бухгалтера. Сюда идёт лишний созвон там, где хватило бы сообщения, согласование на два дня вместо десяти минут общения в переговорке, новичок, который вникает в проект месяц, а не положенные две недели.
По отдельности это мелочи, но в команде из сотни дорогих специалистов все эти мелочи складываются в миллионы потерянной выручки за год. Цифры в статье модельные: смысл не в том, чтобы поверить процентам, а в том, чтобы подставить в формулы свои данные.
Читать далее
👎10😁5
WeakRef и циклы: тихая утечка памяти в долгоживущих процессах
WeakRef в Node.js — инструмент для умных кэшей и подписок, но в долгоживущих серверных процессах он может стать причиной неочевидной утечки памяти. Самая распространенная ошибка — циклическая структура, где WeakRef замыкается с сильными ссылками, и GC молча перестает собирать объекты.
Как возникает утечка
В серверных приложениях часто используется дерево компонентов. Если дочерний элемент хранит WeakRef на родителя, а родитель — сильную ссылку на дочерний, цикл замкнут. GC видит, что объект родителя доступен через WeakRef, но сам WeakRef жив, и сборка блокируется. Результат — плавный рост heap, незаметный неделями.
Диагностика в production
- Снимайте heap snapshots через
- Включайте
- Мониторинг
Практические решения
Самый надежный способ — разорвать цикл. Используйте
Ошибка, которую все допускают
WeakRef внутри замыкания на
Вывод: WeakRef — не панацея: в долгоживущих процессах всегда проверяйте цепочки замыканий и снимайте heap snapshots, иначе тихая утечка памяти станет причиной падения сервера.
WeakRef в Node.js — инструмент для умных кэшей и подписок, но в долгоживущих серверных процессах он может стать причиной неочевидной утечки памяти. Самая распространенная ошибка — циклическая структура, где WeakRef замыкается с сильными ссылками, и GC молча перестает собирать объекты.
Как возникает утечка
В серверных приложениях часто используется дерево компонентов. Если дочерний элемент хранит WeakRef на родителя, а родитель — сильную ссылку на дочерний, цикл замкнут. GC видит, что объект родителя доступен через WeakRef, но сам WeakRef жив, и сборка блокируется. Результат — плавный рост heap, незаметный неделями.
Диагностика в production
- Снимайте heap snapshots через
heapdump или Chrome DevTools. Ищите раздел System / WeakRef — там лежат недоступные объекты.- Включайте
--trace-gc и --trace-gc-verbose. Они покажут, какие объекты GC не может собрать из-за слабых ссылок.- Мониторинг
process.memoryUsage() и v8.getHeapStatistics() — кривая роста без видимых причин говорит о проблеме.Практические решения
Самый надежный способ — разорвать цикл. Используйте
FinalizationRegistry для явного освобождения ресурсов:const registry = new FinalizationRegistry((held) => {
console.log('cleaned', held);
});
class Child {
constructor(parent) {
this.parentWeakRef = new WeakRef(parent);
registry.register(this, 'child');
}
getParent() {
return this.parentWeakRef.deref();
}
}Ошибка, которую все допускают
WeakRef внутри замыкания на
this — классика. Например, в WebSocket подписках: () => this.parent.someMethod сохраняет контекст с сильной ссылкой на весь граф. Всегда пишите явную проверку: this.parentWeakRef?.deref()?.someMethod. Иначе цикл восстановится.Вывод: WeakRef — не панацея: в долгоживущих процессах всегда проверяйте цепочки замыканий и снимайте heap snapshots, иначе тихая утечка памяти станет причиной падения сервера.
❤1
Диагностика и устранение indefinite suspension при мониторинге HTTP-запросов через async-hooks
Ситуация знакомая: используешь async-hooks для отслеживания длинных HTTP-запросов, и вдруг приложение начинает подвисать. Чаще всего это indefinite suspension из-за unhandled rejection.
В чём соль. async-hooks создаёт контекст для каждой асинхронной операции. Если внутри этого контекста Promise упал (rejection), а обработчика нет, хук может зависнуть в ожидании. Сборщик мусора не срабатывает, ресурсы текут — и приложение виснет.
Как ловить
Первое — глобальный перехват unhandledRejection:
Второе — запускай с флагом
Что делать
Вариант 1. Просто и надёжно — добавить
Вариант 2. Явное управление контекстами через destroy. Заводишь Map, чистишь её при завершении:
Вариант 3. Принудительный таймаут. Запускаешь интервал, проверяешь застрявшие контексты (скажем, те, что висят дольше 30 секунд) и прибиваешь их.
На заметку. Если Node.js 14+, бери
Вывод: Indefinite suspension лечится связкой глобального обработчика ошибок и чёткого контроля жизненного цикла контекстов.
Ситуация знакомая: используешь async-hooks для отслеживания длинных HTTP-запросов, и вдруг приложение начинает подвисать. Чаще всего это indefinite suspension из-за unhandled rejection.
В чём соль. async-hooks создаёт контекст для каждой асинхронной операции. Если внутри этого контекста Promise упал (rejection), а обработчика нет, хук может зависнуть в ожидании. Сборщик мусора не срабатывает, ресурсы текут — и приложение виснет.
Как ловить
Первое — глобальный перехват unhandledRejection:
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});Второе — запускай с флагом
--trace-warnings, чтобы видеть стек.Что делать
Вариант 1. Просто и надёжно — добавить
.catch() в каждый Promise внутри хука:const hook = createHook({
init(asyncId, type) {
if (type === 'HTTPINCOMINGMESSAGE') {
const requestPromise = someAsyncOperation();
requestPromise.catch(err => console.error(Request ${asyncId} failed:, err));
}
}
});
hook.enable();Вариант 2. Явное управление контекстами через destroy. Заводишь Map, чистишь её при завершении:
const contexts = new Map();
hook.init(asyncId, type) {
if (type === 'HTTPINCOMINGMESSAGE') {
contexts.set(asyncId, Date.now());
}
}
hook.destroy(asyncId) {
contexts.delete(asyncId);
}
Вариант 3. Принудительный таймаут. Запускаешь интервал, проверяешь застрявшие контексты (скажем, те, что висят дольше 30 секунд) и прибиваешь их.
На заметку. Если Node.js 14+, бери
AsyncLocalStorage — он сам управляет контекстом. И никогда не создавай Promise внутри init без .catch(). Тестируй с --async-stack-traces.Вывод: Indefinite suspension лечится связкой глобального обработчика ошибок и чёткого контроля жизненного цикла контекстов.
❤1
Код я написал за две недели. Продавать его боюсь до сих пор
Три недели назад расширение прошло ревью и появилось в Chrome Web Store. С тех пор ни анонса, ни поста, ни сообщения людям, которые его ждали. Тишина. Это уже не первый раз: в мае автор точно так же на месяц пропал в код, лишь бы не заниматься продвижением. В статье разбирается, почему разработчик прячется от собственного продукта, и приводятся замеры, что происходит с цифрами, когда прятаться перестаёшь.
Читать далее →
Три недели назад расширение прошло ревью и появилось в Chrome Web Store. С тех пор ни анонса, ни поста, ни сообщения людям, которые его ждали. Тишина. Это уже не первый раз: в мае автор точно так же на месяц пропал в код, лишь бы не заниматься продвижением. В статье разбирается, почему разработчик прячется от собственного продукта, и приводятся замеры, что происходит с цифрами, когда прятаться перестаёшь.
Читать далее →
❤3
Диагностика и устранение гонок данных при конкурентном доступе к
В production сценариях с worker_threads и in-memory SQLite в WAL режиме разработчики часто забывают синхронизировать доступ. Итог —
Проблема: гонка без синхронизации
Несколько воркеров читают и пишут в одну in-memory базу без внешней координации. WAL помогает с параллельным чтением, но не спасает от гонок записи. Типичные проявления: ошибки
Решение: один писатель с явными блокировками
Выделите один воркер для критических записей (например, инкременты). Координируйте через
Production-oriented пример с
Начиная с Node.js 23 используйте
Предупреждение: in-memory WAL не для продакшена
In-memory база теряет данные при краше. Для реальных проектов используйте файловую базу с WAL — это гарантирует персистентность.
Вывод: гонки данных в SQLite лечатся одним писателем и явными блокировками через
node:sqlite из worker_threads через in-memory WAL-режимВ production сценариях с worker_threads и in-memory SQLite в WAL режиме разработчики часто забывают синхронизировать доступ. Итог —
SQLITE_BUSY, рассинхрон данных и дубли в уникальных полях.Проблема: гонка без синхронизации
Несколько воркеров читают и пишут в одну in-memory базу без внешней координации. WAL помогает с параллельным чтением, но не спасает от гонок записи. Типичные проявления: ошибки
SQLITE_BUSY, частичные обновления и потеря данных.Решение: один писатель с явными блокировками
Выделите один воркер для критических записей (например, инкременты). Координируйте через
SharedArrayBuffer и Atomics — это дешевле мьютексов. Воркеры-читатели используют отдельные соединения в WAL.Production-oriented пример с
BEGIN IMMEDIATEНачиная с Node.js 23 используйте
DatabaseSync. Обязательно PRAGMA journal_mode=WAL; и PRAGMA synchronous=NORMAL;:import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync(':memory:', { readwrite: true, create: true });
db.exec('PRAGMA journal_mode=WAL;');
db.exec('PRAGMA synchronous=NORMAL;');
function safeUpdate(sql) {
db.exec('BEGIN IMMEDIATE;');
try {
db.exec(sql);
db.exec('COMMIT;');
} catch {
db.exec('ROLLBACK;');
throw new Error('Write failed');
}
}BEGIN IMMEDIATE блокирует запись сразу, избегая гонок. Без него два воркера могут начать транзакции одновременно — ловите SQLITE_BUSY.Предупреждение: in-memory WAL не для продакшена
In-memory база теряет данные при краше. Для реальных проектов используйте файловую базу с WAL — это гарантирует персистентность.
Вывод: гонки данных в SQLite лечатся одним писателем и явными блокировками через
BEGIN IMMEDIATE, давая скорость WAL без потери консистентности.👍1
Скрытый убийца производительности: синхронный 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