티스토리 뷰

반응형

브라우저에서 대용량 파일 block없이 읽기

String으로 되어있는 파일을 읽기 위해서는 브라우저의 메모리에 올려야한다. 용량이 큰 파일은 전체를 올리는게 한계가 있다.

체 파일을 한번에 읽지않고 chunk로 쪼개어 2gb가 넘는 파일을 브라우저에서 읽는 방법을 알아보자

 

 

이러한 작업이 필요했던 이유

  • 사용자가 2gb가 넘는 소설파일을 업로드할 수 있다.
  • 이 파일의 내용은 string으로 이루어져있고 각 챕터마다 @@@@라는 구분자가 존재한다고 해보자.
  • 이 파일이 몇챕터로 이루어져있는지 알고싶다.
  • 백엔드를 거쳐서 돌아오게 된다면 2gb파일을 업로드해야하므로 약 1분의 시간과 비용이 발생한다.
    • 백엔드를 거친다해도 그 서버의 메모리를 2gb사용하는 것이므로 요청이 많아지면 서버에 부하가 올 것이다.
  • 이를 브라우저에서 바로 처리해볼순 없을까 ?

 

 

1. 전체 파일 읽어보기 - readAsText

const reader = new FileReader();
​
reader.onload = async () => {
  reader.result; // 파일의 전체 Text
}
​
reader.readAsText(file);
  • readAsText는 load된 파일의 전체 데이터를 string으로 갖고있어 데이터를 메모리에 올린다. 용량이 커지면 memory부족으로 app이 crash될 수 있다. - 2gb파일은 읽을 수 없다.

 

2. Chunk파일 읽기 - readAsArrayBuffer

파일을 읽을 때 파일데이터를 정해진 사이즈의 바이너리형식의 Arraybuffer로도 받아올 수 있다.

Arraybuffer는 읽은 다음 비워지므로 허용한 양의 메모리만 사용되는 방식이다.

즉, 2gb의 파일을 각각 1mb씩 잘라서 2000번 읽어도 읽는 시간에는 1mb의 메모리만 사용된다.

const reader = new FileReader();
const chunkSize = 1024; // 1mb의 사이즈로 자를 것
​
reader.onload = async () => {
  for (let i = 0; i < 2000; i++) { // 2000번은 예시로, 파일사이즈를 청크로 나눈 값을 반복해야한다.
    const sliced = reader.result.slice(i * chunkSize, (i + 1) * chunkSize); // 2gb의 파일을 2000번 자른다.
    /* sliced는 1/2000의 바이너리 데이터 */
  }
}
​
reader.readAsArrayBuffer(file);

chunk데이터는 slice 메소드를 사용하면 되는데 어디서부터 어디까지 자를지 메모리 사이즈를 지정해두면 된다.

예시에는 2000번 반복돌게 했지만 해당파일이 2gb + 2kb의 용량을 가질 수 있으므로 2001번을 돌게 해야한다.

그래서 파일 데이터를 읽어서 chunksize로 나눈 다음 반올림해서 작업해야 파일 전체 데이터를 읽을 수 있다.

Chunk로 나누긴 했지만 2gb의 파일을 읽는 작업은 10초 이상의 시간이 소요될만큼 오래걸리는 작업이다.

이 시간동안 파일을 읽느라 브라우저의 액션이 blocking된다면 오류가 발생한줄 알고 서비스 이탈률이 매우 높아질 것이다.

오래걸리는 작업들은 background에서 돌리는 것이 바람직하다. 이는 Web Worker 를 사용해 구현해볼 수 있다.

 

앞선 예제에서 @@@@ 의 구분자를 찾아야한다고 했는데 1mb가 정확히 @@@@ 의 단위로 잘라주지 않을 것이므로 전체 구분자의 숫자를 카운팅하고 4로 나누는 방법도 될 것 같다.

 

 

2.1 Web Worker what executes in background

웹 워커(Web worker)는 스크립트 연산을 웹 어플리케이션의 주 실행 스레드와 분리된 별도의 백그라운드 스레드에서 실행할 수 있는 기술입니다. 웹 워커를 통해 무거운 작업을 분리된 스레드에서 처리하면 주 스레드(보통 UI 스레드)가 멈추거나 느려지지 않고 동작할 수 있습니다. Web workers API

nextjs에서 worker를 다음과 같이 사용할 수 있다.

new Worker(new URL("워커파일경로", import.meta.url));

worker파일은 addEventListenermessage 이벤트를 등록시켜 메인쓰레드와 워커쓰레드간 통신을 할 수 있는데

메인 -> worker는 postMessage로 내용을 전달할 수 있고 worker로부터의 수신은 worker에서 postMessage, 메인쪽에서 onmessage로 받을 수 있다.

 

 

example code

const worker = useRef<Worker>();
​
useEffect(()=> {
  worker.current = new Worer(new URL("./worker.ts", import.meta.url));
  worker.current.onmessage = (e) => {
    console.log(e.data); // FROM WORKER
  //worker로부터 받은 내용 처리
  }
  
  
  const handleClick = () => {
    worker.current.postMessage("FROM MAIN");
  }
}, []);
//worker.ts
addEventListener("message", (event) => {
  console.log(event.data); // FROM MAIN
  postMessage("FROM WORKER"); // main으로 내용 전달
});

대용량의 파일을 Arraybuffer를 chunk로 쪼개어 처리해보고 worker를 활용해 백그라운드에서 처리까지 진행해보았다.

worker로 오래걸리는 작업을 백그라운드에서 처리함으로써 UI blocking을 막아 UX상 이점을 가져올 수 있다는 것이 매력적이다.

반응형

'프로그래밍 > React' 카테고리의 다른 글

유저액션 중심의 컴포넌트 테스트를 위한 도구 testing library  (1) 2022.09.27
React Query - useQuery  (0) 2022.09.14
요즘 핫한 React Query  (0) 2022.09.14
useLayoutEffect(2) SSR  (0) 2022.07.10
Image progressive load  (0) 2022.06.14
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
농담곰의 고군분투 개발기