티스토리 뷰
브라우저에서 대용량 파일 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파일은 addEventListener로 message 이벤트를 등록시켜 메인쓰레드와 워커쓰레드간 통신을 할 수 있는데
메인 -> 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' 카테고리의 다른 글
nextjs15 migration - 1 (0) | 2025.01.17 |
---|---|
유저액션 중심의 컴포넌트 테스트를 위한 도구 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 |
- Total
- Today
- Yesterday