0.TL;DR
- 기존의 타이머 수정방안 3가지 모두 서버 리소스와 UX측면에서 약간의 손해를 감수해야 했기에 아쉬웠음
- JavaScript의 비동기 처리 브라우저의 멀티 스레드 동작, 그리고 백그라운드 스레드에서 동작하는 Web Worker 학습
- 이후 Web Worker를 사용해서 background에서 타이머가 동작하도록 해 문제 해결
1. Why?
스타게이트에서 타이머를 동작시키는 과정에서, 백그라운드로 탭이 넘어갈 시 1000ms에 한 번 동작해야 할 타이머가 60000ms에 한 번 동작하게 되는 문제점이 있었다. 이를 해결하기 위해 Page Visibility API로 타이머가 정상적인 값을 보내주도록 처리했었다.
하지만, 사용을 고민했던 3가지 방안 모두 서버 리소스와 UX측면에서 약간의 손해를 감수해야 했기에 아쉬움이 있었다.
그래서 더 나은 방법을 찾아보던 중 Web Worker를 사용하면 탭이 백그라운드로 넘어가도 계속해서 타이머가 동작하도록 할 수 있다는 것을 알게되었다. 고로 이번 글에서는 Web Worker에 대해서 학습하고, 스타게이트에 적용시켜보고자 한다.
2. Web Worker
1) Web Worker의 필요성
- JavaScript는 싱글 스레드 언어이다. 그렇기에, 기본적으로는 순차적으로 동작한다.
- 하지만 브라우저는 싱글 스레드로 동작하지 않는다.
- Web Worker는 멀티 스레드 작업이 필요할 경우, 메인 스레드와는 별개의 백그라운드 스레드를 사용하는 것이다.
위의 이미지를 보자
- 좌측은 Web Worker가 없기에 순차적인 방식으로 UI 조작과 데이터 핸들링을 처리한다.
- 우측은 Web Worker를 사용하기에 UI 조작, 데이터 핸들링 병렬 처리가 가능하다.
- UI 조작이 즉각적으로 이뤄져야하기에 보통 메인 스레드가 UI 조작을, 워커 스레드가 다른 작업을 하게 된다.
2) Web Worker란?
이를 알기 위해서는 우선 JavaScript의 동작부터 이해해야한다.
JavaScript의 동작
- 콜 스택
- JavaScript가 실행될 때, 실행되는 함수가 쌓이는 스택이다.
- Js는 싱글 스레드 언어이기에, 한 번에 하나의 함수만 처리할 수 있다.
- 실행이 완료된 함수는 스택에서 제거된다.
- 메세지 큐(태스크 큐, 콜백 큐)
- 비동기 작업(setInterval, 네트워크 요청 등)이 실행되면, 각자의 실행환경(ex)setInterval이라면 Web API)으로 넘어간다.
- 실행이 완료되면 결과값을 가진 콜백 함수가 여기서 대기한다.
- 이벤트 루프
- 콜 스택이 비어 있을 때, 메시지 큐에서 다음 작업(콜백 함수)를 가져와서 콜 스택에 추가해준다.
이를 통해서 싱글 스레드인 JavaScript가 비동기적으로, 블로킹 없이 동작할 수 있다.
Web Worker
- 동작
- Web Worker는 자신만의 콜 스택, 이벤트 루프, 메세지 큐를 가진다.
- 메인 스레드와는 독립적인 환경에서 동작한다.
- 만약 Worker 내부에서 비동기 작업이 요청된다면? Worker의 이벤트 루프에 의해 처리된다.
- 메인 스레드와의 통신
- 메세지 기반으로 통신이 이뤄진다. 메세지를 보낼때는 postMessage, 받을 때는 onmessage를 사용한다.
- 메인 스레드와의 차이점
- 메인 스레드와는 별개의 글로벌 컨텍스트를 가진다.
- DOM에 직접 접근이 불가능하다.
3) 그래서 언제 쓰면 되나요?
이럴 때 쓰세요
- 클라이언트 단에서 처리해야하지만 복잡하고 시간이 오래 걸리는 로직이 있는 경우
- 이번처럼 비활성화시에도 계속해서 동작해야하는 경우
이럴 땐 다시 생각해보세요
- 브라우저에서 Web Worker를 지원해줘야 하기에, 지원되지 않는 구형 브라우저까지 생각해야하는 경우
- 단, IE에서도 지원하니 어지간하면 거의 모든 브라우저가 지원한다.
- 복잡하고 시간이 오래 걸리는 로직이 아닌 경우 or 서버에서 처리하는게 더 유리한 경우
- 복잡한 로직이 아니라면 굳이 Web Worker를 사용하기보다 그냥 비동기 처리가 더 나을 수 있다.
- Dom 조작이 필요한 경우
- 추가적인 비용이 발생하기에, 정말 필요한지 잘 모르는 경우
- 아래의 timerWorker는 1000ms에 한번 메시지를 전송하는 간단한 로직임에도 400kb의 메모리를 소모한다.
3. How?
timerWorker.ts
setInterval(() => {
postMessage('1000ms');
}, 1000);
- 1000ms에 한 번, 메인 스레드로 메시지를 보내는 timerWorker다.
useEffect(() => {
if (isV8 && window.Worker) {
const worker = new Worker(
new URL('./timerWorker.ts', import.meta.env.VITE_SERVER_URL)
);
worker.onmessage = () => {
// 상태 변경 코드
};
return () => worker.terminate();
} else {
// 기존 타이머 코드
}
}, [data, setData]);
- 보정이 들어가지 않는 브라우저거나, Web Worker가 지원되지 않을 경우 타이머가 정상적으로 작동하지 않을 수 있다.
- 이 경우 Web Worker를 사용하는 대신, 상대적으로 자주 데이터를 가져오는 기존의 방식을 그대로 사용하도록 했다.
주의해야할 점
- 번들링 환경에서는 ./timerWorker.ts만 적어두면 경로를 찾지 못한다.
- 그래서 import.meta.url을 사용해 절대 경로로 웹 워커를 참조하도록 경로를 설정해줘야 한다.
4. 래퍼런스
https://tech.kakao.com/2021/09/02/web-worker/
https://www.zerocho.com/category/HTML&DOM/post/5a85672158a199001b42ed9c
이벤트 루프 - JavaScript | MDN (mozilla.org)
https://ko.vitejs.dev/guide/features#web-workers
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/import.meta
'Side Project > 스타게이트' 카테고리의 다른 글
Firefox에서의 setInterval 문제 해결 (0) | 2024.02.07 |
---|---|
setInterval과 Page Visibility API로 정확한 대기시간 알려주기 (2) | 2024.01.25 |
Atomic Design Pattern 도전기 (0) | 2023.07.31 |