Nodejs 대규모 결과의 효율적인 파일 저장

00_8_3·2023년 3월 5일
0

0 비동기 결과값

  • 어떠한 외부 API가 있고 10~30ms response.

  • 1만 번의 결과를 하나의 파일에 저장.

  • 파일 쓰기 속도는 0에 수렴.

위와 같이 가정한다.

1 Base API 호출 함수

function getAPI = () => {
	return {}; // JSON 형태라고 가정
}

const write = async () => {
  let jsonData;
	try {
      const rv = await getAPI();
      jsonData = JSON.stringfy(rv, null, 2);
    } catch (e) {
      jsonData = JSON.stringfy(e, null, 2); 
      // JSON 형태라고 가정
    }
  
  return fs.writeFile("file1.json", jsonData, () => { ... });
};
                                            
await Promise.all([write, write, ..., write]);                                            

2 하나의 파일에 순차적 저장

하나의 결과마다 순차적 파일에 쓰기.

10_000 * 10 ms = 100 초

느리니 패스.

3 하나의 파일에 병렬적 저장

io 작업이 비동기로 진행이 되기 때문에 race condition에 빠질 수 있다.

3.1 락

mutex를 사용해서 락을 얻은 경우에만 파일 쓰기

https://www.npmjs.com/package/async-lock

3.2 큐

https://www.npmjs.com/package/p-queue

4 여러개의 파일에 병렬적 쓰기 후 통합

마지막에 하나의 파일로 통합

4.1 순차적 머지

모든 API 결과에 대해 각각의 파일을 쓰기 후

모든 파일을 순회하며 하나의 파일로 합친다.

for ... of 파일리스트

각 파일을 읽고 쓰고를 만번을 해야 함으로 속도가 느리다.
O(n)

4.2 병렬적 머지

16개 파일 있다고 가정하면
2개씩 머지하여 8개 파일 만들고 반복. (16강 토너먼트 방식)

4.1 보다는 빠름.
O(log(n))

그런데 이 정도면 DB를 사용하는게 맞는듯 하니 패스.

5 결과를 기다린 후 저장

5.1 모든 결과를 기다린 후 저장

JS에 객체를 생성하여 결과가 반환 될 때 마다 주입하고
완료 되었을 때 파일에 쓰기

5.2 분할된 결과를 저장

100개의 결과가 있다고 할 때
10개의 묶음으로 나누고 10개씩 작업을 한다.

첫 10개 작업의 모든 결과를 기다린 후
5.1와 같은 방법으로 파일에 쓰기 실행.

그 과정에 오류 발생시 의도적으로 멈춤.

불필요한 나머지 90개의 작업을 수행할 필요가 없는 경우

5.3 병렬적 분할 결과 저장

5.2와 다르게 오류가 발생하여도 모든 결과가 필요한 경우

100개의 결과를 10개 묶으로 10개 씩 작업

3.1의 락을 활용함.

10번의 병렬적 작업 마다 각 결과의 모음을 객체로 저장하고
10번의 결과가 모두 작성된 객체가 생성 될 때 마다
락을 사용하여 파일에 쓰기 실행

JS 처리는 싱글스레드

결론

NodeJS의 메인 스레드의 경우 싱글 스레드이지만
IO 작업을 하는 경우는 그러하지 못하다. (fs.write 함수는 원자성 보장 못함)

대규모 쓰기 작업에서 race condition이 발생하여 원하는 결과를 얻지 못하는 상황에
사용하면 좋을 듯 하다.

번외

스레드 풀을 사용하는 piscina를 사용해 처리 하는 방법도 있다.

https://github.com/piscinajs/piscina

0개의 댓글