Web Worker로 오디오 파형 렌더링 성능 최적화하기
타임라인뷰에서 오디오 파형을 렌더링할 때 발생하는 성능 문제를 Web Worker를 활용해 개선했다
작성일 2025.05.25
페이지가 생성된 시간 2026.02.03 00:47:07

0

Web Worker란 무엇인가?

기본 개념

Web Worker는 JavaScript가 단일 스레드로 동작하는 특성을 극복하기 위해 도입된 웹 표준 API다. 기본적으로 자바스크립트는 메인 스레드(UI 스레드)에서 모든 코드를 실행하므로, 무거운 계산 작업이 진행되면 화면 렌더링이 멈추거나 사용자 인터랙션이 느려지는 문제가 발생한다.

Web Worker는 별도의 백그라운드 스레드에서 스크립트를 실행할 수 있게 해준다.

  • 메인 스레드와 병렬로 코드 실행 가능
  • UI 렌더링 차단 없이 무거운 작업 처리
  • 브라우저 멀티코어 활용

Web Worker 동작 원리

Web Worker 사용 시나리오

Web Worker는 다음과 같은 작업에 적합하다.

  • 대규모 데이터 처리 및 분석
  • 오디오/비디오 처리
  • 이미지/캔버스 조작
  • 암호화/복호화 연산
  • 복잡한 수학 계산

내가 겪은 문제: 오디오 파형 렌더링의 성능 병목

audiopart1.png

AS-IS: 메인 스레드에서 모든 계산 수행

기존 구현에서는 오디오 파형을 렌더링하기 위해 메인 스레드에서 다음과 같은 계산을 수행했다.

// 기존 AudioWavePart.tsx
const WaveData = ({ audioData, width, durationSec, offset }) => {
  const { sampleRate, channelData } = audioData;
  const slicedRawData = subChannelData(channelData, ...);

  // 메인 스레드에서 이중 루프 실행
  for (let i = 0; i < sampleCount; i++) {
    // 샘플링 및 평균 계산
  }

  // 정규화 계산도 메인 스레드에서
  return normalize(filteredData, 0.1, 0.8);
};

문제점

  1. UI 차단: 긴 오디오 파일(초당 44,100개 이상의 샘플)의 파형을 계산하는 동안 메인 스레드가 차단됐다
  2. 렉 발생: 타임라인 스크롤이나 오디오 드래그 시 프리징이 발생했다
  3. 컴포넌트 복잡도: 계산 로직과 렌더링 로직이 분리되지 않아 유지보수가 어려웠다

해결책: Web Worker로 계산 분리

아키텍처 변경

변경 전

  • AudioWavePart에서 계산과 렌더링을 동시에 수행

변경 후

  • TimeLineAudioWave: Audio 데이터 관리, Worker 생성 및 메시지 전송
  • useAudioWaveBarMap: Worker 결과 수신, Pre-computed Bar Map 생성
  • AudioWavePart: 사전 계산된 Bar Map 활용, 순수 렌더링만 담당
  • AudioWave.worker.ts: Block 단위 데이터 샘플링, 절대값 평균 계산, 정규화, 시간별 매핑

구현

// AudioWave.worker.ts
self.onmessage = (e) => {
  // 정규화 함수 (메인 스레드에서 분리)
  function normalize(array: number[], min = 0, max = 1): number[] {
    const peak = array.reduce((max, value) => Math.max(max, value), 0);
    const multiplier = Math.pow(peak, -(max - min));
    return array.map((n) => n * multiplier + min);
  }

  const { sampleCount, blockSize, slicedRawData, offset, samplesPerSec } = e.data;

  // 백그라운드 스레드에서 샘플링
  for (let i = 0; i < sampleCount; i++) {
    const blockSum = // 블록 단위 절대값 평균 계산
    filteredData.push(blockSum / blockSize);
  }

  // 정규화 후 결과 전송
  self.postMessage(
    normalize(filteredData, 0.1, 0.8).map((value, idx) => ({
      sec: (offset + idx / samplesPerSec).toFixed(1),
      value
    }))
  );
};

주요 개선 사항

성능 최적화

항목변경 전변경 후
계산 위치메인 스레드Web Worker 스레드
UI 차단발생 (긴 오디오 시)발생하지 않음
렌더링 성능계산 + 렌더링 동시에렌더링만 수행
Worker 라이프사이클없음생성 → 작업 → 종료

코드 구조 개선

  • 관심사 분리: 계산 로직과 렌더링 로직 완전 분리
  • 재사용성: useAudioWaveBarMap 훅으로 Worker 로직 재사용 가능
  • 타입 안전성: WaveBarMap 타입으로 명확한 인터페이스 정의

결과

사용자 경험 개선

  • 스크롤 부드러움: 타임라인 스크롤 시 더 이상 렉이 발생하지 않았다
  • 반응성 향상: 오디오 드래그 및 리사이즈 시 UI가 즉시 반응했다
  • 더 부드러운 렌더링: 오디오 파형이 안정적으로 렌더링됐다

기술적 배운 점

Web Worker 활용 전략

  1. 언제 사용할까?

    • 50ms 이상 소요되는 계산 작업
    • UI 반응성이 중요한 페이지
    • 대용량 데이터 처리
  2. 주의사항

    • Worker는 DOM에 접근 불가
    • 메인 스레드와 데이터 복사 비용 고려 (Transferable Objects 사용 권장)
    • Worker 생성 비용이 있으므로 필요할 때만 생성
  3. Worker 종료 전략

    • 작업 완료 후 즉시 종료하여 리소스 해제
    • Cleanup 함수에서 worker.terminate() 호출

TypeScript + Web Worker

// Vite 플러그인을 사용한 Worker import
import AudioWaveWorker from 'web-worker:./AudioWave.worker.ts';

// 타입 안전한 Worker 사용
const worker = new AudioWaveWorker();
worker.postMessage({ /* ... */ });
worker.onmessage = (e) => { /* ... */ };

정리

Web Worker를 활용하여 오디오 파형 렌더링 성능을 개선한 결과다.

  • 메인 스레드 차단 해결: UI 렌더링에 영향 없이 복잡한 계산 수행
  • 사용자 경험 개선: 부드러운 스크롤과 반응성
  • 코드 구조 개선: 관심사 분리로 유지보수성 향상
  • 메모리 효율: 재사용 가능한 컴포넌트와 Map 활용

Web Worker는 브라우저 환경에서 병렬 처리를 구현하는 강력한 도구다. 특히 오디오/비디오 처리와 같이 많은 계산이 필요한 작업에서 사용자 경험을 크게 개선할 수 있다.


참고자료

©2024 dlwl98
github
PostsAbout