node.js에서 10만개 이상 직렬 Promise 사용해보신 분 있으신가요?

텍스트 데이터를 데이터베이스로 이관을 위해 readline 를 사용하고 있는데 도무지 어떻게 해야할지 모르겠네요.
적용사례는 단순합니다. csv가 있고 그걸 특정 DB에 추가하려고 합니다.

lineReader = require('readline').createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});
lineReader.on('line', function (line) {
  const [ id, name, desc ] = line.split(',');
  dbInsert({ id, name, desc });
});

이렇게 해놓고 head -n 4 importData.csv | node impoter.js 하는 식으로 올리는데
보통 데이터베이스를 동기로 쓰는데는 아무 문제가 없지만

  dbInsert({ id, name, desc }).then(o=>console.log(o.id));

와 같이 쓰기를 하는 부분이 Promise인 경우엔 lineReader가 뚫고 가버려서 한꺼번에 Promise가 종료되지 않은채로 계속 생겨납니다.

Promise를 reduce 해보기도 했는데 이것 역시 처음에 한방에 Promise를 만들고 시작해서 Memory오류든 socket오류든 나더라구요.
결국 느리지만 급해서 일단 웹서버 만들고 처리하긴 했는데
혹시 비슷한 경험 있으신 분들의 조언 부탁드립니다.

async.js 라는거 사용하시면 될 것 같은데요… 직렬도 되고 병렬도 되구요…

insert를 쿼리 한번에 로우 하나로 처리하시지 마시고, 쿼리 한번에 500~1000개씩 밀어넣는 방식으로 변경해보세요.
csv 파일 전체를 node 내에서 로드한 다음 500~1000 라인씩 chunk 처리하고, 청크로 묶인 데이터를 루프돌면서 insert 하면 됩니다.
500개씩만 묶어도 10만개 데이터는 루프 200번만에 처리 가능합니다.
스트림으로 읽으면서 db 쿼리 10만번 날리는 것 보다 한번에 읽어서 처리하는게 부하가 더 적고 빠를거에요.

node 8 버전 이상은 async, await을 지원하니 async function 으로 생성해서 호출하면 순차적으로 쿼리 실행되게 만들기가 쉬울 것 같습니다. 8 버전 미만이면 babel-node 써서 실행시키면 되구요.

대충 아래 같은 로직으로 처리하면 될 것 같아요.

// TODO: csv 파일 읽기, chunk 처리
const chunkedItems = [[{ id, name, desc }, '...499개'], ['...500개'],];

const run = async () => {
  // 500개 데이터 밀어넣기
  for (let items of chunkedItems) {
    await dbInserts(items);
  }
};

run()
  .then(() => console.log('finished!'));

facebook에서 .once를 사용하라는 답변이 달렸는데 좋네요.

by Doojin Kang
once 를 사용해서, dbInsert 끝난 후에 라인을 읽으면 시간적인 문제는 해결될 수 있을 것 같습니다.

function oneLine() {
lineReader.once(‘line’, function (line) {
const [ id, name, desc ] = line.split(’,’);
dbInsert({ id, name, desc })
.then(function(o) {
console.log(o.id)
lineReader.once(‘line’, oneLine)
});
});
}
lineReader.once(‘line’, oneLine)

1개의 좋아요