본문 바로가기
FE/browser

[Browser] Web Worker 와 Service Worker

by livemehere 2023. 9. 20.

서론

Worker 는 웹사이트의 성능을 향상시키기 위해 등장했습니다.

안드로이드나 IOS 의 경우 일반적으로 앱의 메인 스레드는 유저 이벤트를 언제든지 반응할 수 있도록 놔두고, 다른 쓰레드로 복잡한 작업을 수행합니다. 링크 를 참조하시면 실제로 안드로이드의 경우 메인 스레드를 오랫동안 blocking 하면 앱이 출돌하는 이슈가 있던 것을 확인할 수 있습니다. 이런 이유로 모바일 기기에서는 멀티쓰레딩이 아주 일반적인 패턴으로 자리 잡았죠.

 

하지만 javascript 의 경우 싱글 스레드로 동작하고, 위와 같은 멀티쓰레딩을 구현하기엔 부족함이 많았습니다.

하지만 worker 의 등장으로 백그라운드의 다른 스레드에서 script 를 실행할 수 있게 되었어요.

워커는 메인스레드를 간섭하지 않고 어떤 메모리도 공유하지 않는 완전히 독립된 스레드에서 실행되기 때문이죠.

 

이런 worker 의 종류에는 두가지 가있습니다. web workers 와 service workers.

이 두가지는 공통점과 차이점이 있고, 프로덕션에서 어떻게 활용할 수 있는지 알아보도록 하겠습니다.

 

공통점

- 메인스레드와 완전히 독립된 스레드에서 script 를 실행한다.

- 메인스레드의 Window, Document 객체에 접근이 불가하며, 제한된 브라우저 APIs 사용.

차이점

둘의 경우 같은 worker 라는 점에서, 아무거나 사용해도 되겠지만 핵심적인 차이가 있습니다.

- web worker 와 다르게 service worker 는 네트워크 요청을 간섭할 수 있고, push API 를 사용할 수 있습니다.

- web workers 는 여러개 등록가능 하지만 service worker 는 도메인당 하나만 등록할 수 있고 대신 모든 활성화된 탭에 영향을 줍니다.

- web worker 는 탭의 수명주기에 따라 함께 종료되지만, service worker 는 탭의 동작과 별개로 동작합니다. 즉, 모든탭이 닫혀있어도       백그라운드에서 동작할 수 있습니다. 그렇기 때문에 push 알림도 가능한 것이에요!

 

단, service worker 의 경우 백그라운드에서 실행 될 수 있지만, 너무 오래걸리는 작업은 강제로 종료될 수 있습니다. 그렇지 않으면 사용자의 보안, 배터리에 악영향을 줄 수 있기 때문이에요. Background Fetch 와 같은 API 를 통해서 워커의 종료를 방지할 수 도 있습니다.

 

Web Workers

web worker 는 단순히 오래걸리는 작업을 수행할 때 사용할 수 있습니다. AI, 이미지 인코딩, 압축 등의 작업이 대표적이에요.

간단한 예시 코드로 바로 이해할 수 있을겁니다.

 

new Worker() 로 새로운 워커를 생성할 수 있습니다.

그리고 생성한 파일과 호출된 워커는 부모-자식 관계를 형성하게되고, postMessage 와 onmessage 로 데이터를 교환 합니다.

// index.js
const worker = new Worker('my-worker.js');
worker.onmessage = (event) => {
    console.log(event) // 3초 뒤 worker is ready!
}
console.log(1)
// my-worker.js
console.log('worker is running')
setTimeout(()=>{
    postMessage('worker is ready!')
},2000)

 

이때 메세지로 전달하는 값은 복사되서 새로운 값으로 만들어져 전달되어 참조값들은 모두 사라집니다.

예를들어서 클래스의 인스턴스를 전달한다면 멤버변수들을 복사되지만, 참조되는 메서드들은 전달되지 않습니다.

class App {
    name = 'worker'

    getName(){
        return this.name
    }
}

const app = new App()

setTimeout(()=>{
    postMessage(app) // {name:'worker'} *getName() 은 사라짐
},2000)

 

importScripts

워커는 main thread 에 직접적으로 접근할 수 없기 때문에, 외부 라이브러리를 사용한다면 importScripts 로 외부 스크립트를 가져와 전역으로 사용해야합니다.

// math.js
function sum(x, y) {
    return x + y;
}
// my-worker.js
importScripts('math.js')
console.log(sum) // sum 함수

Error

워커에서 발생한 에러는 main thread 로 전달되지 않기 때문에 onerror 로 수신할 수 있습니다.

worker.onerror = (event) => {
    console.log(event)
}

 

Service Worker

서비스 워커는 브라우저의 네트워크 프로세스의 Proxy 역할, background 작업, 캐싱, 오프라인 등의 작업을 할 수 있습니다.

 

하지만 크롬의 브라우저는 네비게이션 요청이 들어오면

브라우저 프로세스 -> 네트워크 프로세스 ->  렌더러 프로세스 순서인데, 중요한점은 서비스 워커는 렌더러 프로세스에서 실행되는 javascript 코드이기 때문에, 네트워크를 요청하기 전 서비스워커의 유무를 확인  하는 절차가 필요합니다.

그래서 브라우저는 처음 요청했을 때 해당 도메인에 등록된 서비스워커 javascript 파일의 존재 유무를 확인하고 네트워크 요청을 할지, 캐시에서 데이터를 가져올지 결정합니다.

 

하지만 서비스워커를 거쳐서 네트워크 요청을 하게되면, 그에 따른 지연시간도 생기기 때문에, 네비게이션 프리로드 가 서비스워크와 병렬로 실행되면서 지연 없는 네트워크 요청을 하도록 합니다. 

 

대표적으로 PWA 가 서비스 워커를 사용하기 때문에 PWA 를 실습해 보는게 이해하기 좋은 방법이라 생각합니다.

기회가 되면 PWA 도 포스팅으로 다루어보도록 하겠습니다.

 

아래는 web worker 와 service worker 를 좀더 간편하게 사용하기 위한 라이브러리 입니다.

web worker : comlink

service worker : workerbox 

반응형