Браузер и фронтенд-рантайм
Structured clone и transferables
Вы переносите задачу обработки изображения на 40 МБ в воркер — и главный поток всё равно замирает на 40 мс, прежде чем воркер даже начнёт. Работа переехала, но данные — нет.
Алгоритм structured clone
postMessage(data) не делит data — он делает его глубокую копию через алгоритм structured clone. Structured clone обрабатывает объекты, массивы, Map, Set, Date, типизированные массивы, Blob и ArrayBuffer. Он не обрабатывает:
- Функции (молча отбрасываются)
- DOM-узлы (бросает исключение)
- Прототипы классов — методы теряются, выживают только свойства данных
Копия синхронна на отправляющем потоке и масштабируется с размером payload-а: примерно 1 мс на МБ для плоских объектов, больше для глубоко вложенных структур. Отправьте объект на 50 МБ — и вы заблокировали отправляющий поток на ~50 мс — вы переместили работу, но заплатили за это налог на клонирование.
- Скорость клона плоского объекта
- ~1 мс / МБ
- Клон объекта на 30 МБ
- ~30 мс блокировки отправителя
- Transfer (ArrayBuffer)
- O(1), микросекунды
- Transferable: ArrayBuffer
- Отсоединяет отправителя (byteLength → 0)
- Transferable: ImageBitmap
- Передаётся, источник становится закрытым
- Transferable: OffscreenCanvas
- Эксклюзивное владение переходит
Transferables: передача владения за O(1)
Побег от стоимости клона — механизм transferable. Передайте второй аргумент в postMessage — список transferable-объектов:
postMessage({ pixels, width, height }, [pixels.buffer]);Вместо копирования байтов браузер отдаёт нижележащую память принимающему потоку и отсоединяет её от отправителя. После передачи:
pixels.buffer.byteLengthравен0у отправителя- Получатель имеет новый view с полными данными
Стоимость падает с O(size) до O(1). Transfer — правильный ход для больших бинарных payload-ов: bitmap-ы изображений, аудиобуферы, содержимое файлов.
Типы transferable: ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas, ReadableStream, WritableStream, TransformStream.
Что теряется при structured clone
При postMessage объекта с методами:
class Point {
constructor(x, y) { this.x = x; this.y = y; }
distanceTo(other) { ... }
}
const p = new Point(1, 2);
worker.postMessage(p);
// Воркер получает: { x: 1, y: 2 } — метода distanceTo нетStructured clone копирует свойства данных, но не имеет представления для функций или прототипных цепочек. По дизайну: функции ссылаются на замыкания, которые могут держать DOM-узлы или другое состояние только главного потока. Правило: сериализуйте данные, не поведение. Пересоздайте класс на принимающей стороне при необходимости.
Вы `postMessage`-ите Float32Array на 40 МБ воркеру, и отправляющий поток замирает на ~40 мс. Каков фикс?
`onmessage` воркера получает объект, у которого были методы до отправки. Методы пропали. Почему?
Когда вы postMessage-ите большой ArrayBuffer, по умолчанию делается глубокая копия (налог structured clone). Альтернатива перемещает владение буфером к принимающему потоку за константное время, оставляя его непригодным у отправителя. Как называется эта альтернатива?
Structured clone плоского объекта стоит примерно 1 мс на МБ. Воркеру шлётся объект на 30 МБ по значению. Примерно сколько миллисекунд отправляющий поток блокируется на клоне?
Передать буфер воркеру вместо клонирования
1/3Какая спецификация определяет алгоритм structured clone, используемый postMessage, IndexedDB и Cache API?
Почему это работает
Почему structured clone синхронен и почему он блокирует отправителя? Потому что спецификация JavaScript гарантирует: вызов postMessage — единственная, атомарная операция: данные полностью сериализуются до возврата функции. Если бы сериализация была асинхронной, вы могли бы мутировать данные между вызовом postMessage и моментом, когда воркер их читает, нарушая изоляцию. Синхронная стоимость — цена гарантии изоляции. Механизм transfer обходит это, перемещая указатель памяти вместо байтов — ОС может атомарно передать владение регионом памяти без копирования.
- 01Что structured clone обрабатывает и что отбрасывает?
- 02Как передать ArrayBuffer вместо клонирования, и что происходит со ссылкой отправителя?
- 03Когда следует использовать SharedArrayBuffer вместо transfer?
postMessage делает глубокую копию данных через алгоритм structured clone со скоростью ~1 мс на МБ — объект на 30 МБ блокирует отправляющий поток на ~30 мс, сводя на нет пользу от переноса работы вне главного потока. Фикс — механизм transferable: поместите ArrayBuffer во второй аргумент postMessage, и браузер отдаёт владение получателю за O(1), отсоединяя ссылку отправителя. Функции и методы прототипов не могут пересекать границу — structured clone это сериализатор данных, не копировщик объектов. Для случаев, когда оба потока нуждаются в параллельном доступе к одной памяти, ответ — SharedArrayBuffer, но он требует cross-origin isolation.
встречается в41
- Federation и lookahead: батчинг за пределами DataLoadermiddle
- Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемостьsenior
- Лок и single-flight: ограничение параллельных rebuildmiddle
- Stale-while-revalidate и CDN request coalescingmiddle
- Детектирование stampede и дизайн TTL для продакшенаmiddle
- Метастабильный сбой, fencing-токены и production-постмортемыsenior
- Что такое отношение: таблицы, строки, ключи и ограниченияjunior
- Ограничения, ключи и типы данных Postgresmiddle
- JSONB, массивы и когда side table побеждаетmiddle
- Целостность схемы: deferral, версионирование и сбои в продакшнеsenior
- Где происходит data fetching — и почему это решает LCPjunior
- React Server Components и Suspense streamingmiddle
- Senior internals: RSC payload, слои кэша и production паденияsenior
- Конверт IPjunior
- Читаем IP-заголовокmiddle
- Что делает TLS и зачем он нуженjunior
- Расписание ключей, SNI, ALPN и расширенияsenior
- Защита 0-RTT, ECH, гибридный PQ и продакшн TLSsenior
- Двенадцать слоёв: один URL, семь действующих лицjunior
- Устойчивость: каскадные повторы, circuit breakers и error budgetsenior
- Что такое OpenTelemetry: API, SDK, Collector, OTLPjunior
- Сигналы OTel, Semantic Conventions и проводной формат OTLPmiddle
- Collector OTel: receivers, processors, exporters и паттерны развёртыванияmiddle
- Vendor-нейтральность, eBPF-инструментирование, Operator и OTel в браузере и serverlesssenior
- Эксплуатация OTel Collector: надёжность, version skew, режимы отказа и управлениеsenior
- Что такое trace propagation и почему сломанная propagation хуже отсутствия трейсовjunior
- traceparent и tracestate: полный формат W3C-заголовкаmiddle
- Baggage и async-границы: перенос контекста через очереди и callback''''иmiddle
- Async context на разных языках, service mesh, миграция B3 и безопасностьsenior
- Production-сбои propagation, span links и платформенный дизайнsenior
- Debugging-воронка: SLO → RED → trace → profilejunior
- Архитектура OTel: один SDK, четыре сигнала, один wire-форматmiddle
- Петля инцидента: от пейджера до постмортема до предотвращенияmiddle
- Масштаб, безопасность и ROI наблюдаемых системsenior
- At-most-once, at-least-once, exactly-once: три контракта доставкиjunior
- Consumer-side dedup: самый дешёвый путь к exactly-once processingmiddle
- Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инцидентыsenior
- Что такое OAuth и почему пароли — не ответjunior
- Authorization code flow с PKCEmiddle
- Sender-constrained токены: DPoP и mTLSsenior
- OAuth в production: audience атаки, observability и реальные провалыsenior