Development Study/Frontend

UI 버벅임은 이제 그만! Web Worker 를 적절히 이용하여 UX 증진시키기

TMInstaller 2023. 10. 6. 13:33
728x90

전부터 궁금한 것이 있었다

비동기 처리는 API에서만 할 수 있는 것일까? 사용자가 상호작용하는 모든 것에 API가 들어있지는 않을 텐데, 그렇다면 어떡하지?

이런 질문들이 나를 감싸고 있을 때, 갑자기 스쳐지나가는 질문 하나가 있었다

Web Worker, 이거 Promise랑 다른건가?

생각해보니 궁금해졌다. 둘 다 비동기, 병렬의 키워드를 가지고 있는 개념이 아닌가

그래서 이참에 Web Worker에 대해 알아보기로 했다


Web Worker란 무엇일까?

싱글스레드인 자바스크립트에서 많은 양의 데이터를 받아와 가공해야하는 경우 시간이 오래걸리게 되면서 병목이 발생, 따라서 다른 스레드에서 병렬적으로 처리해주는 역할을 하는 것이 web-worker다

웹 API의 일부 함수는 멀티 스레드로 처리가 되는 것도 있지만, 사용자가 직접 데이터를 처리하는 부분에 대해서는 싱글 스레드로 동작된다.
따라서 데이터 처리가 많아질수록 병목현상이 생려 렌더링 프레임에 영향을 미치게 된다

더 정확히는 스크립트 연산을 웹 어플리케이션의 주 실행 스레드와 분리된 별도의 백그라운드 스레드에서 실행할 수 있는 기술이다

자바스크립트는 브라우저에서 싱글 스레드 기반으로 동작하기에 web-worker 를 사용하지 않았을 때 UI 동작과 핸들링이 순차적인 방식으로 처리가 된다
반면, web-worker 를 사용하면 병렬 처리가 가능해진다

문제 상황 생성

직접 예시와 함께 이해해보는 것이 좋을 것이라 생각하여 위의 설명에 따라 먼저 web-worker 를 사용하지 않을 경우 버벅일만한 예제를 만들어보았다

import React from 'react'

export default function App(): JSX.Element {
    // 큰 계산 작업을 수행하는 함수
    const heavyCompute = () => {
        let sum = 0;
        for (let i = 0; i < 1e9; i++) {
            sum += i;
        }
        console.log("계산 완료", sum);
    }

    // "계산 시작" 버튼 클릭 시 heavyCompute 함수를 실행
    const onStartCompute = () => {
        console.log("계산 시작");
        heavyCompute();
        console.log("계산 종료");
    }

    // "다른 작업" 버튼 클릭 시 콘솔에 메세지 출력
    const onOtherTask = () => console.log("다른 작업 진행 중");

    return (
        <>
            <button
                type="button"
                onClick={onStartCompute}
                style={{ background: '#4789e4', marginRight: '10px'}}
            >                    
                계산 시작
            </button>

            <button
                type="button"
                onClick={onOtherTask}
                style={{ background: '#47e476' }}
            >    
                다른 작업
            </button>
        </>
    )
}

기본적으로 계산 시작 버튼을 누를 경우 계산이 오랫동안 진행될 것이고, 이로인해 다른 작업 버튼을 눌러도 계산이 완료되기 전까지는 로그가 뜨지 않을 것이다
이 상황은 다음과 같이 표현될 수 있다

 

 

그리고 우리는 이런 상황에서 web-worker를 사용한다


Web Worker 파일을 생성하여 해결하기

주로 web-worker 파일은 public 폴더에서 관리된다
public 폴더에 computeWorker.js 파일을 생성해서 아래와 같이 작성한다

// public/computeWorker.js
self.onmessage = function(event) {
    if (event.data.command === 'startCompute') {
        let sum = 0;
        for (let i = 0; i < 1e9; i++) {
            sum += i;
        }
        // 계산이 완료되면 결과를 메인 스레드에 보냅니다.
        self.postMessage({ status: 'completed', result: sum });
    }
};

그리고 App 컴포넌트에서 Web Worker를 생성하고, 메세지를 전송해서 계산을 요청하도록 처리한다

import React, { useEffect, useState } from 'react';

export default function App(): JSX.Element {
    const [worker, setWorker] = useState<Worker | null>(null);

    useEffect(() => {
        // Web Worker를 생성한다
        const newWorker = new Worker('computeWorker.js');
        setWorker(newWorker);

        // Web Worker로부터의 메세지를 처리한다
        newWorker.onmessage = (event) => {
            if (event.data.status === 'completed') {
                console.log('계산 완료:', event.data.result);
            }
        };
    }, []);

    const onStartCompute = () => {
        console.log('계산 시작');
        worker?.postMessage({ command: 'startCompute' });    
    };

    const onOtherTask = () => console.log('다른 작업 수행 중');

    return (
        <>
            <button
                type="button"
                onClick={onStartCompute}
                style={{ background: '#4789e4', marginRight: '10px' }}
            >
                계산 시작
            </button>

            <button
                type="button"
                onClick={onOtherTask}
                style={{ background: '#47e476' }}
            >
                다른 작업
            </button>
        </>
    )
}

위와 같이 작성한다면 로직의 프로세스는 다음과 같이 변화한다

이전 프로세스(좌), 이후 프로세스(우)


그럼에도 Web Worker를 잘 사용하지 않는다던데...

이런 장점을 가지고 있음에도 실제 프로젝트에서 web-worker 를 잘 사용하지 않는 것을 볼 수 있는데, 그 이유는 다음과 같다

  1. 서버에서 대부분의 데이터를 처리하기에 굳이 web-worker 를 사용하게 될 만한 일들이 없다
    • web-worker 를 사용할 만큼 부하가 걸리는 작업을 안한다고 볼 수 있다
  2. DOM 제어 및 Window 객체의 일부 함수는 메인 스레드에서만 가능하고 web-worker 에서 사용이 불가능하다
  3. 단순 계산식에서는 web-worker 를 사용하지 않는 것이 더 빠르기도 하고, 메인 스레드와 Web Worker 사이의 데이터 전송 과정에서 비용이 발생한다

그렇다면 도데체 언제 Web Worker를 사용하지?

확실히 특수성이 짙은 기능인 만큼 사용하면 좋은 조건들을 가지고 있다

  1. 화면 동작에 영향을 미치는 연산 동작, 즉, 바이너리 파일 핸들링 또는 복잡한 계산이 필요한 경우
    • 이미지나 비디오 편집 웹 애플리케이션에서 필터나 변환 작업을 할 때, 메인 스레드에서 수행하면 UI가 멈출 수 있으므로, Web Worker에서 처리하면 유용하다
  2. 백그라운드에서 지속적인 작업을 해야하거나 메인 스레드 영향을 미치지 않고 작업을 하는 경우
    • 실시간 데이터 분석 또는 모니터링 웹 앱에서 백그라운드에서 지속적으로 데이터를 체크하고 분석하는 작업을 수행할 때, Web Worker를 사용하여 메인 스레드의 부담을 줄일 수 있다
  3. 멀티 스레드로 개발했을 때 사용자 환경 개선에 도움이 되는 경우
    • 싱글 페이지 애플리케이션에서 페이지 전환 시 데이터를 미리 로딩하거나, 복잡한 알고리즘을 백그라운드에서 실행하여 사용자에게 빠른 응답성을 제공하는 경우에 유용하다

그렇다면, 이제 Web Worker를 적절하게 사용하여 더욱 쾌적한 웹 환경을 만들어보자!


References

728x90