외부 원문 링크
복잡한 웹 애플리케이션을 더 빠르게 만들기
Edge 팀이 제안한 Delayed Message Timing API. 창/워커/iframe 간 postMessage 의 지연 원인을 (수신자 busy / 큐 혼잡 / 직렬화 오버헤드) 세 갈래로 측정한다.
핵심 요약
Edge 팀은 postMessage(window↔window, window↔worker, iframe, MessageChannel, BroadcastChannel) 의 지연을 진단하기 위해 Delayed Message Timing API 를 제안했다. 세 종류 지연이 구분된다. (1) 수신자 busy: 수신 컨텍스트가 긴 동기 작업 중 — blockedDuration 속성으로 큐 대기 시간을 보고. (2) 큐 혼잡: 짧은 작업이 처리 속도보다 빠르게 들어와 누적 — taskCount / scriptTaskCount 로 얼마나 많은 작업이 앞에 있었는지. (3) 직렬화 오버헤드: 큰 데이터 전송 시 구조화 복제 비용 — serialization / deserialization 에 소요 시간. PerformanceObserver 로 delayed-message 타입을 구독하면 sender / receiver 양쪽 모두에서 관찰 할 수 있어 라운드트립 단위 분석이 가능해진다. 아직 제안 단계이지만 API 가 드러낸 세 축 프레임 자체가 실전 진단에 바로 쓸 수 있다.
복잡 웹앱 성능을 '메인 스레드 한 개' 가 아니라 '여러 컨텍스트(창/워커/iframe) 의 협업 지연' 으로 재구성한다. 기존 DevTools 는 한 스레드 내부 만 보여줬다면, Delayed Message Timing API 는 컨텍스트 사이의 지연 을 처음으로 드러낸다.
React Server Components, Web Worker 오프로딩, OffscreenCanvas 등 최근 성능 기법은 다중 컨텍스트 를 전제한다. 그런데 postMessage 지연은 지금까지 측정 도구가 없었다. 면접에서 '워커 분리했더니 왜 느려졌지?' 같은 케이스에 근본 원인 세 갈래 (수신자 busy / 큐 혼잡 / 직렬화) 를 답할 수 있으면 깊이가 드러난다.
학습 포인트
면접 답변으로 연결할 학습 포인트입니다.
지연의 세 갈래
postMessage 지연의 원인 세 가지를 구분해 질문하면 진단이 빨라진다.
| 원인 | 증상 | API 속성 | 해법 방향 |
|---|---|---|---|
| 수신자 busy | long task 뒤 큐에 밀림 | blockedDuration | yielding (scheduler.yield), long task 쪼개기 |
| 큐 혼잡 | 짧은 작업이 누적 | taskCount | 스로틀링, MessageChannel 분리 |
| 직렬화 비용 | 큰 객체 전송 | serialization | Transferable 사용, 청크 단위 분할 |
같은 '느림' 이라도 조치가 정반대다.
모든 지연을 '워커가 느리다' 로 뭉뚱그리는 것. 실제로는 세 원인이 다른 해법 을 요구.
PerformanceObserver 로 양쪽 관찰
기존 Long Tasks / LoAF 는 한 컨텍스트 내부 만 측정. Delayed Message Timing 은 송신/수신 양쪽에서 관찰 가능.
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log({
traceId: entry.traceId,
blocked: entry.blockedDuration,
tasks: entry.taskCount,
serialize: entry.serialization,
});
}
});
observer.observe({ type: 'delayed-message', buffered: true });
traceId 로 같은 메시지의 송신/수신 엔트리를 correlate 하면 왕복 시간 분해 분석이 가능.
한쪽 컨텍스트만 계측해 절반의 그림만 보는 것 — sender 와 receiver 양쪽 모두 observer 등록 필요.
Transferable 과 직렬화 회피
postMessage(data, [data.buffer]) 처럼 Transferable 을 넘기면 복제 없이 소유권 이전 — serialization 비용이 0.
// 복제 — 큰 배열이면 수십 ms
worker.postMessage(largeTypedArray);
// 이전 — 비용 거의 0, 단 송신 측은 접근 불가
worker.postMessage(largeTypedArray, [largeTypedArray.buffer]);
OffscreenCanvas / ImageBitmap / ArrayBuffer 등이 Transferable. 대용량 데이터는 설계 시부터 Typed Array 로 잡아야 이 경로가 열린다.
복잡한 객체를 그대로 postMessage 해 구조화 복제 가 메인 스레드를 막는 것. 설계 시 TypedArray 친화 구조로 분리.
읽는 순서
- 1이론
WICG Delayed Message Timing README 를 읽고 세 원인 축과 속성 매핑 표를 작성.
- 2구현
메인 → 워커로 큰
Uint8Array를 (a) 구조화 복제, (b)Transferable두 방식으로 전달해postMessage소요 시간 비교. - 3실무
본인 서비스에 Workers/Iframes 가 있다면
performance.now()타임스탬프로 송수신 차이를 측정하고 Top 5 느린 메시지 유형 리스트업. - 4설명
사내에 '다중 컨텍스트 성능 3축(busy/congestion/serialization)' 체크리스트를 배포해 리뷰 기준으로 합의.
면접 연결 질문
[좋은 답변] (1) 수신자 busy — 워커가 이전 메시지 처리 중(blockedDuration), (2) 직렬화 비용 — 큰 객체를 복제(Transferable 미사용), (3) 메시지 빈도가 너무 높음(taskCount 누적). Performance 탭 + Delayed Message Timing API 로 분해. 해법: 각각 scheduler.yield / Transferable / 스로틀.
[좋은 답변] 복제: 원본 유지, 깊은 복사 (시간 O(size)). Transferable: 원본 소유권을 수신 측에 넘겨 복사 없음. 대신 송신 측에서 접근 시 TypeError. 대용량 + 단방향 전송에 Transferable, 쌍방향 공유에는 SharedArrayBuffer + Atomics.
[좋은 답변] Long Tasks / LoAF 로 한쪽의 long task 는 감지 가능. 그러나 양쪽 상관관계 는 수동 상관 필요. Delayed Message Timing 이 표준화되기 전에는 performance.now() 타임스탬프를 메시지에 박아 sender-receiver 차이로 유추 — 정밀도 제한.
자기 점검
'워커는 빠르니 무한히 보내도 된다' — 실제로는 큐 혼잡 이 체감 지연의 주범.
그냥 'postMessage 대체' 라는 오해 — 채널별 큐 격리가 핵심.