Procesando archivos grandes en node.js

Procesando archivos grandes en node.js

Node.js ha revolucionado el desarrollo de back-end con su arquitectura basada en eventos, permitiendo el manejo eficiente de operaciones I/O sin bloqueo. Una de estas operaciones I/O es la lectura de archivos. En este artículo, te mostraré estrategias para leer archivos en Node.js: la lectura convencional con el módulo fs, el uso de streams y la introducción de la biblioteca EventStream.


Antes de empezar ¿Qué es eso de Streams?

Los streams en informática representan un flujo continuo de datos, similares a cómo fluye el agua en un río. En Node.js, son objetos que permiten procesar información (ya sea lectura o escritura) de manera secuencial y fragmentada, en lugar de cargarla toda de una vez.

¿Por qué es importante utilizarlos?

1. Eficiencia en memoria: Permiten manejar grandes cantidades de datos sin consumir toda la memoria, procesándolos por fragmentos. 2. Velocidad: Comienzan a procesar datos tan pronto como están disponibles, sin esperar la carga completa. 3. Flexibilidad: Se pueden adaptar y encadenar para realizar diversas operaciones, como leer, transformar y escribir datos.


Utilizando node:fs

La forma más básica de leer un archivo en Node.js es mediante el módulo fs (file system). Para ilustrar, consideremos la tarea de encontrar una palabra específica dentro de un archivo.

const fs = require('fs');

const findWordInFile = (filePath, word) => {
  const content = fs.readFileSync(filePath, 'utf8');
  return content.includes(word);
};

const filePath = './really_huge_file.txt';
const wordToFind = 'keyword';

console.log(findWordInFile(filePath, wordToFind) ? 'Keyword found!' : 'Keyword not found');

Pros:

  • Simplicidad: Leer un archivo es directo y el código es conciso.
  • Velocidad: Para archivos pequeños, esta es una forma rápida de obtener todo el contenido.

Contras:

  • Uso de memoria: Leer un archivo grande todo de una vez puede consumir mucha memoria.
  • Bloqueo: readFileSync bloquea el hilo principal, lo que puede afectar el rendimiento en aplicaciones en tiempo real.
  • Ineficiencia: Si solo necesitas una parte del archivo, leerlo por completo puede ser innecesario.

Utilizando Streams

Los streams permiten procesar datos "pieza por pieza", lo que puede ser extremadamente útil para archivos grandes, entonces usando la lógica anterior pero aplicada a streams el código se vería así:

const fs = require('fs');
const readline = require('readline');

const findWordInStream = (filePath, word) => {
  // valdria la pena revisar todas las opciones adicionales de readStream
  const readStream = fs.createReadStream(filePath, {
    encoding: 'utf8',
    highWaterMark: 16 * 1024 // Esto define el tamaño de cada fragmento (en este caso, 16KB)
  });
  const lineReader = readline.createInterface({
    input: readStream,
    crlfDelay: Infinity
  });

  lineReader.on('line', (line) => {
    if (line.includes(word)) {
      console.log('Keyword found!');
      readStream.destroy(); // Detener la ejecucion aqui
    }
  });

  lineReader.on('close', () => {
    console.log('Finished reading the file.');
  });
};

const filePath = './really_huge_file.txt';
const wordToFind = 'keyword';

findWordInStream(filePath, wordToFind);

Pros:

  • Eficiencia en memoria: Ideal para archivos grandes porque no necesita cargarlos por completo en memoria, ya que los cargará por fragmentos (chunks).
  • Procesamiento en tiempo real: Permite procesar datos a medida que se leen.
  • Rapidez: Puedes detener la lectura tan pronto como encuentres lo que buscas.

Contras:

  • Complejidad: Puede ser más complicado implementar y manejar, especialmente para casos más avanzados.

Utilizando EventStream

EventStream es una biblioteca que simplifica la creación y manejo de streams en Node.js. Una de las principales razones por las que se usa EventStream es por su habilidad de manejar operaciones comunes con streams, como mapear, filtrar, y reducir, de una manera fácil y legible.

Entonces trasladaré el mismo ejemplo usando EventStream para leer y procesar cada línea del archivo de forma secuencial.

const fs = require('fs');
const eventStream = require('event-stream');

const findWordWithEventStream = (filePath, word) => {
  const readStream = fs.createReadStream(filePath, 'utf8');

  readStream
    .pipe(eventStream.split())
    .pipe(eventStream.mapSync((line) => {
      if (line.includes(word)) {
        console.log('Keyword found!');
        readStream.destroy(); // Stop reading the file
      }
    }))
    .on('end', () => {
      console.log('Finished reading the file.');
    });
};

const filePath = './really_huge_file.txt';
const wordToFind = 'keyword';

findWordWithEventStream(filePath, wordToFind);

Pros:

  • Facilidad de uso: EventStream simplifica el manejo de streams al proporcionar métodos fáciles de usar para operaciones comunes.
  • Composición: Puedes encadenar múltiples operaciones juntas para construir flujos de datos complejos, lo que facilita la legibilidad y mantenimiento del código.

Contras:

  • Dependencia adicional: Si bien Node.js tiene soporte nativo para streams, al utilizar EventStream introduces una dependencia adicional en tu proyecto.

Comparación: Streams vs EventStream

A primera vista, streams nativos y EventStream pueden parecer similares, pero hay diferencias clave que vale la pena entender.

  • Streams nativos: Son parte del núcleo de Node.js y proporcionan la base sobre la que se construyen bibliotecas como EventStream. Son poderosos y flexibles, pero pueden ser verbosos y complejos para tareas comunes.

  • EventStream: Es esencialmente un envoltura (wrapper) de streams nativos. Lo que hace es abstraer algunas de las complejidades de los streams nativos y proporcionar métodos de utilidad para operaciones comunes, como dividir datos, filtrar, mapear, etc.

La ventaja principal de EventStream es su simplicidad y facilidad de uso. Puedes realizar tareas complejas con menos código y de una manera más legible. Sin embargo, esta facilidad viene al costo de introducir una dependencia adicional en tu proyecto.

¿Cuál es la mejor opción para archivos grandes?

Para la lectura y procesamiento de archivos grandes, tanto streams nativos como EventStream son opciones superiores a fs. Están diseñados para manejar grandes cantidades de datos sin consumir toda la memoria. Entre estos dos, EventStream tiene la ventaja en términos de simplicidad y legibilidad. Si bien significa depender de una biblioteca de terceros, la eficiencia y claridad que aporta a menudo justifica su inclusión, especialmente en proyectos donde la legibilidad y mantenimiento del código son primordiales.


Finalmente, el uso de estas librerías siempre dependerá de lo que necesita, mientras que fs es una opción rápida y sencilla para archivos más pequeños, streams nativos y EventStream ofrecen un manejo más avanzado y eficiente de archivos grandes, yo particularmente tuve que lidiar con un archivo de 6GB y EventStreams me ayudó bastante en la tarea de procesamiento, fue una combinacion entre event-stream y turf que usé para localizar coordenadas en polígonos.

Yo como siempre, espero que esta explicación te ayude a comprender los streams y la lectura de archivos por chunks.

Happy coding! :D


Photo by Mitchell Kmetz on Unsplash

Jack Fiallos

Jack Fiallos

Te gustó este artículo?