Grandes quantidades de dados com menos recursos

Escrito por

A dica de hoje é um ponto bastante importante para performance em NodeJS. Eu já comentei um pouquinho sobre esse assunto, mas vou reforçar esse cuidado que a gente pode ter aqui para poupar memória quando estamos processando grandes quantidades de dados.

Primeiramente eu baixei um arquivo com mil registros em CSV, eu quero então processar esses arquivos. Vou criar um arquivo sem-otimizar.js:

1const fs = require(‘fs’)
2fs.readFile(‘data.csv’, (err, contents) =>{
3 console.log(contentes.toString())
4})

Uma outra abordagem seria, por exemplo:

1const fs = require(‘fs’)
2fs.readFile(‘data.csv’, (err, contents) =>{
3 const lines = contents.toString().split(‘\\n’)
4 line.forEach(line => console.log(lines.split(,)))
5})

Agora imagine o seguinte, ao invés de colocar da forma anterior, vamos imaginar que vai ser salva a linha no banco de dados:

1const fs = require(‘fs’)
2
3fs.readFile(‘data.csv’, (err, contents) =>{
4 const lines = contents.toString().split(‘\\n’)
5 line.forEach(line => {
6 console.log()
7 })
8})

Nosso primeiro erro é fazer essa operação sem se preocupar muito se ela retornou algo ou não. Para simular isso, vamos fazer um record e retornar uma promise:

1const saveDB = record => {
2 return new Promise((resolve, reject) =>{
3 setTimeOut(resolve, Math.ceil(Math.random()*4000))
4 })
5}

Com isso vamos simular um save no banco de dados, variando o tempo de resposta não ultrapassando 4 segundos. Agora vamos fazer algumas modificações no código readFiles:

1fs.readFile(‘data.csv’, (err, contents) =>{
2 const lines = contents.toString().split(‘\\n’)
3 line.forEach(line, i) => {
4 saveDB(line).then(() => console.log(i))
5 })
6})

Perceba que ao rodar o código, ele trará os registros lentamente e fora de ordem. Estamos empilhando de forma gigantesca, gastando muita memória, porque carregamos todos os registros, fizemos um split e depois ainda mandei salvar tudo de uma vez, além do banco tendo que lidar com todas as requisições ao mesmo tempo.

Mas qual a maneira ideal de fazer isso?

Deixar que esses dados passem pelo aplicativo, nós resolvemos o que tem que resolver e vamos para o próximo.

Vou criar um arquivo otimizado.js para fazer da maneira correta e vou utilizar o fast-csv:

1yarn add fast-csv

Em seguida, no código, faremos algo um pouco diferente. Vamos importar o fast-csv e criar um readStream para a entrada e outro construtor para essa entrada:

1const fs = require(‘fs’)
2const csv =require(‘fast-csv’)
3const entrada = fs.createReadStream(‘data.csv’)
4const csvStream = csv.fromStream(entrada, {
5 headers: true
6}).on(‘data’, data => {
7 console.log(data)
8})

Agora imagine que temos a mesma função de salvar, o caminho mais interessante é passar os dados, porém, enquanto não terminarmos de salvar esses dados, não manda mais nada:

1const fs = require(‘fs’)
2const csv =require(‘fast-csv’)
3const entrada = fs.createReadStream(‘data.csv’)
4const saveDB = record => {
5 return new Promise((resolve, reject) =>{
6 setTimeOut(resolve, Math.ceil(Math.random()*4000))
7 })
8}
9const csvStream = csv.fromStream(entrada, {
10 headers: true
11}).on(‘data’, data => {
12 csvStream.pause()
13 saveDB(data).then((){
14 console.log(data)
15 csvStream.resume
16 })
17})

Dessa maneira, utilizando o pause, não jogamos tudo na memória para processar. Resolvido o problema no banco de dados, vamos mandar mais informações.

Confira o video:

Deixe suas dúvidas e sugestões nos comentários. Curta o DevPleno no Facebook, se inscreva no canal no YouTube e cadastre seu e-mail para não perder as atualizações. Abraço!

Evolua mais rápido

Junte-se a milhares de desenvolvedores no nosso time de alunos premium e alcance mais rápido o próximo nível da sua carreira.

Ver cursos Premium