VOD 10,000개 데이터 패킷 대량 주입 파이프라인 분석

목표

  • Chrome 확장 프로그램의 5단계 레이어 통신 구조를 파악하고, Web Worker와 타임 오프셋 치환 방식을 활용해 VOD 플레이어 내부 큐에 10,000개의 가상 데이터 패킷을 효율적으로 주입하는 흐름을 정립합니다.

기억하기

  • VOD vs LIVE 모드 파라미터 차이: VOD 모드에서 rate는 초당 전송률이 아니라 총 생성 패킷 수를 의미합니다.
  • 단일 벌크 전송 (Bulk Transfer): 1초마다 tick 단위로 패킷을 발행하는 LIVE 모드와 달리, START 시점에 Web Worker에서 10,000개 패킷 전체를 단일 <root> XML 문자열로 일괄 생성해 단 1회의 postMessage로 전달합니다.
  • 타임 오프셋 플레이스홀더 패턴: 생성 시점에는 __VOD_TIME_OFFSET_{s}__ 형태로 상대 시간(초)을 마킹하고, 최종 주입 계층(page.js)에서 실제 videoEl.currentTime + 2초를 기준으로 절대 타임스탬프로 일괄 치환합니다.
  • 5단계 격리 컨텍스트 통신: Popup UI (popup.js) → Content Script (content.js) → Web Worker (worker.js) → Content Script (content.js) → Page Script (page.js) → VOD Player 순으로 데이터를 중계합니다.

내용

전체 파이프라인은 Chrome 확장 프로그램의 보안 격리 환경을 넘나드는 5단계 레이어로 구성됩니다.

[클릭] startBtn (popup.js)
   │ chrome.tabs.sendMessage → { type: 'START', rate: 10000, durationMs: 60000, mode: 'vod' }
   ▼
[content.js] → worker.postMessage('START')
   │
   ▼
[worker.js] ── vodpackets.js 10,000회 호출 ──▶ bigXml (<root>..10000개..</root>)
   │ self.postMessage({ type: 'VOD_BULK', xmlString: bigXml })
   ▼
[content.js] → window.postMessage('VOD_TESTER_PACKET')
   │
   ▼
[page.js] ── DOMParser로 XML 파싱
           ── __VOD_TIME_OFFSET_N__ → 실제 재생시간 + N으로 치환
           ── dataItem.createData() × 10,000
           ── dataItem.queue.push() × 10,000
   │
   ▼
[VOD 플레이어] 재생 시간 도달 시 데이터 자동 렌더링

1단계: Popup UI — 버튼 클릭 (popup.js)

사용자로부터 총 패킷 수(rate)와 분배 구간(durationMs)을 입력받아 content.js로 전달합니다.

// popup.js L434-442
startBtn.onclick = async () => {
  const rate = Math.max(1, Number(rateInput.value) || 1); // 예: 10000
  const durationMs = Number(durationSelect.value) || 0;   // 예: 60000 (1분)
  const r = await send({ type: 'START', rate, durationMs, mode: 'vod' });
  applyStatus(r);
};

2단계: Worker — 10,000개 XML 패킷 일괄 생성 (worker.js)

메인 스레드 부하를 방지하기 위해 Web Worker 내부에서 타임 오프셋을 균등 분배한 후 단일 <root> XML 덩어리로 합산합니다.

// worker.js L157-184
case 'START': {
  if (mode === 'vod') {
    const durationSec = Math.floor(dur / 1000);  // 예: 60초
    const totalPackets = rate;                    // 10,000개

    let bigXml = '<root>';
    for (let i = 0; i < totalPackets; i++) {
      // 0~59초 구간에 균등 분배 (i=0 → 0초, i=9999 → ~59초)
      const s = Math.floor((i / totalPackets) * durationSec);
      const k = pickKey();           // 가중치 추첨 (데이터 95%)
      let xml = entry.fn().xmlString; // vodpackets.js 에서 XML 생성
      xml = xml.replace('__VOD_TIME__', `__VOD_TIME_OFFSET_${s}__`);
      bigXml += xml;
    }
    bigXml += '</root>';

    // 단 한 번의 메시지로 전체 묶음 전송
    self.postMessage({ type: 'VOD_BULK', xmlString: bigXml, count });
  }
}

3단계: 개별 XML 패킷 생성 (vodpackets.js)

개별 패킷은 시간 치환용 플레이스홀더(__VOD_TIME__)를 포함하는 템플릿 형태로 반환됩니다.

// vodpackets.js
export function vodChat() {
  return {
    tagName: 'data',
    xmlString: '<data><p>0</p><n><![CDATA[User1]]></n><u>tester1</u><m><![CDATA[Test Message]]></m><t>__VOD_TIME__</t></data>',
  };
}

4단계: Content Script — 메인 페이지 컨텍스트 전달 (content.js)

Worker로부터 수신한 대량 XML 데이터를 메인 웹 페이지 스크립트 실행 컨텍스트로 중계합니다.

// content.js
window.postMessage({ type: 'VOD_TESTER_PACKET', xmlString: bigXml }, '*');

5단계: Page Script — 타임 치환 및 플레이어 큐 적재 (page.js)

현재 비디오 재생 위치(videoEl.currentTime + 2초)를 기준으로 오프셋을 절대 시간으로 일괄 치환한 후, 파싱한 데이터를 VOD 플레이어 내부 큐에 직접 적재합니다.

// page.js L104-160
// 현재 재생 시간 기준으로 오프셋 변환
const currentTime = videoEl.currentTime;
const time = currentTime + 2; // 현재 재생 위치 + 2초 앞

// __VOD_TIME_OFFSET_3__ → String(currentTime + 2 + 3)
replacedXmlString = d.xmlString.replace(
  /__VOD_TIME_OFFSET_(\d+)__/g,
  (_, offset) => String(time + Number(offset))
);

// XML 파싱 후 chatItem에 삽입
const xmlDoc = vodParser.parseFromString(replacedXmlString, 'text/xml');
const children = Array.from(xmlDoc.documentElement.children); // 10,000개 노드
for (const child of children) {
  const parsedData = dataItem.createData(child); // 내부 파싱 함수
  if (parsedData) dataItem.queue.push(parsedData);     // 플레이어 내부 데이터 큐에 삽입
}

모드별 아키텍처 비교

구분 LIVE 모드 VOD 모드
패킷 생성 시점 1초마다 주기적 tick 발생 START 시점 1회 일괄(Bulk) 생성
rate의 의미 초당 전송 패킷 수 총 주입 패킷 수 (예: 10,000개)
주입 방식 ctrl.receive() (소켓 실시간 소비) dataItem.queue.push() (플레이어 큐 사전 적재)
시간 처리 별도 타임스탬프 계산 불필요 현재 재생 시간 + 상대 오프셋 치환
tick 루프 역할 패킷 생성 및 지속 전송 UI 메트릭 측정 상태 유지만 수행

느낀점

  • 대용량 문자열 결합 및 10,000회 루프 처리를 Web Worker로 격리하여 팝업과 브라우저 메인 스레드의 프리징 현상을 방지한 구조가 인상적입니다.
  • 실시간 스트림과 달리 타임라인 기반으로 동작하는 VOD 특성에 맞춰, 생성 시점에는 상대 오프셋으로 기록하고 주입 시점에 실제 비디오 재생 시간과 결합하는 '오프셋 치환 패턴'을 통해 재생 오차를 효과적으로 해결했습니다.

ToDo

  • 10,000개 노드를 DOMParser로 일괄 파싱하고 순회할 때 메인 스레드 프레임 드랍(Long Task) 발생 여부 프로파일링.
  • 한 번에 10,000개를 밀어 넣는 방식 대신 requestAnimationFrame 또는 청크 단위(예: 1,000개씩 분할) 큐 삽입 적용 검토.
  • 사용자가 VOD 재생바를 탐색(Seek)하거나 되감기/빨리감기를 했을 때 기존 주입된 가상 데이터 큐의 지속/초기화 동작 검증.

결론

  • VOD 환경의 대량 부하 테스트는 실시간 소켓 주입 방식이 아닌 사전 일괄 생성 후 플레이어 내부 타임라인 큐 적재 방식을 취합니다.
  • 5단계 레이어를 거치며 연산 부하(Worker), 브릿지 중계(Content Script), 도메인 로직 주입(page.js)의 책임을 명확히 분리하여 브라우저 부담을 최소화하면서 대규모 VOD 데이터 렌더링을 구현할 수 있습니다.