서버/Node.js

Node.js를 활용한 파일 처리 시스템 구축: 대용량 파일 처리

JohnnyDeveloper 2024. 7. 29. 00:31

Node.js를 활용하여 대용량 파일을 처리하는 시스템을 구축하는 방법을 설명합니다. 스트림을 사용한 파일 읽기와 쓰기, 데이터 변환 및 최적화 기법을 다룹니다.

 

1. Node.js의 스트림 개념

Node.js의 스트림은 대용량 데이터를 작은 단위로 나누어 처리할 수 있는 기능을 제공합니다. 이는 메모리 사용을 최소화하고, 데이터를 효율적으로 관리할 수 있도록 돕습니다. 스트림에는 읽기 스트림, 쓰기 스트림, 변환 스트림 등이 있습니다.

1.1 스트림의 장점

  • 메모리 효율성: 스트림은 데이터를 한꺼번에 메모리에 로드하지 않고, 작은 청크로 나누어 처리합니다. 이를 통해 메모리 사용량을 줄이고, 시스템의 안정성을 높일 수 있습니다.
  • 속도 최적화: 스트림은 데이터를 처리하는 동안 다음 데이터를 준비할 수 있어, 전체 처리 시간을 단축시킬 수 있습니다.
  • 유연성: 스트림은 데이터를 읽고 쓰는 동안 변환하거나, 압축/압축 해제와 같은 추가 작업을 수행할 수 있습니다.

2. 파일 읽기와 쓰기: 스트림의 활용

Node.js에서 대용량 파일을 처리할 때 스트림을 사용하는 것이 가장 효과적입니다. 스트림을 사용하여 파일을 읽고 쓰는 기본적인 방법을 알아보겠습니다.

2.1 파일 읽기 스트림

파일 읽기 스트림은 대용량 파일을 효율적으로 읽어들일 수 있는 방법입니다. fs.createReadStream 메서드를 사용하여 파일을 스트림으로 읽을 수 있습니다.

const fs = require('fs');

const readStream = fs.createReadStream('largeFile.txt', { encoding: 'utf8' });

readStream.on('data', chunk => {
  console.log('Reading chunk:', chunk);
  // 데이터 처리 로직 추가
});

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

 

이 코드는 지정된 파일을 스트림으로 읽어와, 각 청크(chunk)를 data 이벤트로 처리합니다. 파일의 모든 데이터를 읽으면 end 이벤트가 발생합니다.

2.2 파일 쓰기 스트림

파일 쓰기 스트림은 데이터를 파일로 출력하는 방법입니다. fs.createWriteStream 메서드를 사용하여 파일에 데이터를 쓰는 스트림을 생성할 수 있습니다.

const fs = require('fs');

const writeStream = fs.createWriteStream('outputFile.txt', { encoding: 'utf8' });

writeStream.write('This is a line of text.\n');
writeStream.write('Another line of text.\n');
writeStream.end(); // 스트림 종료

 

이 코드는 outputFile.txt 파일에 텍스트 데이터를 씁니다. write 메서드를 사용하여 데이터를 스트림에 추가하고, end 메서드를 호출하여 스트림을 종료합니다.


3. 파일 처리 시스템 구축: 실전 예제

이제 Node.js의 스트림을 활용하여 대용량 파일 처리 시스템을 구축하는 방법을 살펴보겠습니다. 이 시스템은 파일을 읽고, 데이터 변환을 수행한 후, 변환된 데이터를 새로운 파일에 저장하는 기능을 포함합니다.

3.1 파일 변환과 필터링

파일 처리 시스템의 주요 기능 중 하나는 데이터 변환입니다. 예를 들어, CSV 파일을 읽어와 특정 열의 데이터를 변환하거나, 필터링된 데이터를 추출할 수 있습니다.

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

const inputFilePath = 'input.csv';
const outputFilePath = 'filteredOutput.csv';

const readStream = fs.createReadStream(inputFilePath);
const writeStream = fs.createWriteStream(outputFilePath);
const rl = readline.createInterface({ input: readStream });

rl.on('line', (line) => {
  const columns = line.split(',');
  // 예를 들어, 특정 조건에 맞는 행만 출력
  if (columns[2] === 'someValue') {
    writeStream.write(line + '\n');
  }
});

rl.on('close', () => {
  writeStream.end();
  console.log('File processing completed.');
});

 

이 예제에서는 readline 모듈을 사용하여 CSV 파일의 각 행을 읽고, 특정 조건에 맞는 데이터를 필터링하여 새로운 파일에 저장합니다.

3.2 데이터 압축과 압축 해제

대용량 데이터를 처리할 때 압축을 사용하여 데이터 크기를 줄이고, 저장 공간을 절약할 수 있습니다. Node.js는 zlib 모듈을 사용하여 데이터를 압축하고 압축 해제할 수 있습니다.

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

const inputFile = 'data.txt';
const outputFile = 'data.txt.gz';

const readStream = fs.createReadStream(inputFile);
const writeStream = fs.createWriteStream(outputFile);
const gzip = zlib.createGzip();

readStream.pipe(gzip).pipe(writeStream).on('finish', () => {
  console.log('File successfully compressed');
});

 

이 코드에서는 zlib 모듈의 createGzip 메서드를 사용하여 데이터를 압축하고, 압축된 데이터를 새로운 파일에 저장합니다. pipe 메서드는 스트림을 연결하여 데이터를 효율적으로 처리할 수 있게 합니다.


4. 확장성과 최적화

대용량 파일 처리 시스템은 데이터의 양이 늘어날수록 성능 최적화와 확장이 필요합니다. 다음은 이러한 시스템의 성능을 향상시키기 위한 몇 가지 전략입니다.

4.1 비동기 작업의 병렬 처리

Node.js의 비동기 특성을 활용하여 여러 파일을 동시에 처리하거나, 여러 작업을 병렬로 수행할 수 있습니다. 이를 위해 워커 스레드를 사용하거나, Promise.all을 사용하여 비동기 작업을 병렬로 처리할 수 있습니다.

const fs = require('fs');
const zlib = require('zlib');
const path = require('path');

const files = ['file1.txt', 'file2.txt', 'file3.txt'];

async function compressFiles(files) {
  const tasks = files.map(file => {
    const inputFile = path.join(__dirname, file);
    const outputFile = `${inputFile}.gz`;
    return new Promise((resolve, reject) => {
      const readStream = fs.createReadStream(inputFile);
      const writeStream = fs.createWriteStream(outputFile);
      const gzip = zlib.createGzip();

      readStream.pipe(gzip).pipe(writeStream)
        .on('finish', () => resolve(`Compressed ${file}`))
        .on('error', reject);
    });
  });

  try {
    const results = await Promise.all(tasks);
    console.log(results);
  } catch (error) {
    console.error('Error compressing files:', error);
  }
}

compressFiles(files);

 

이 예제에서는 여러 파일을 동시에 압축하는 비동기 작업을 Promise.all을 사용하여 병렬로 처리합니다. 이를 통해 전체 작업 시간을 단축할 수 있습니다.

4.2 데이터베이스와의 연동

대용량 데이터를 처리한 후, 이를 데이터베이스에 저장하여 관리할 수 있습니다. MongoDB와 같은 NoSQL 데이터베이스는 대용량 데이터를 효과적으로 저장하고, 빠르게 검색할 수 있는 기능을 제공합니다.

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/filedata', { useNewUrlParser: true, useUnifiedTopology: true });

const dataSchema = new mongoose.Schema({
  filename: String,
  size: Number,
  dateProcessed: { type: Date, default: Date.now }
});

const Data = mongoose.model('Data', dataSchema);

function saveFileData(filename, size) {
  const fileData = new Data({ filename, size });
  fileData.save(err => {
    if (err) console.error(err);
    console.log('File data saved to database');
  });
}

saveFileData('file1.txt', 1024);

 

이 코드는 처리된 파일 정보를 MongoDB에 저장하는 예제입니다. 데이터베이스를 통해 파일 처리 이력을 관리하고, 필요한 경우 데이터를 쉽게 검색할 수 있습니다.


5. 보안과 데이터 무결성

파일 처리 시스템에서는 보안과 데이터 무결성이 중요합니다. 특히 민감한 데이터를 다루는 경우, 데이터 전송과 저장 시 암호화를 사용하는 것이 필수적입니다.

5.1 데이터 암호화

Node.js의 crypto 모듈을 사용하여 데이터를 암호화하고, 안전하게 전송할 수 있습니다. 데이터를 저장할 때도 암호화하여 보호할 수 있습니다.

const crypto = require('crypto');

function encryptData(data) {
  const algorithm = 'aes-256-cbc';
  const key = crypto.randomBytes(32);
  const iv = crypto.randomBytes(16);

  const cipher = crypto.createCipheriv(algorithm, key, iv);
  let encrypted = cipher.update(data, 'utf8', 'hex');
  encrypted += cipher.final('hex');

  return { encrypted, key, iv };
}

const data = 'Sensitive data that needs to be encrypted';
const encryptedData = encryptData(data);
console.log('Encrypted data:', encryptedData.encrypted);

 

이 예제에서는 AES-256-CBC 알고리즘을 사용하여 데이터를 암호화합니다. 암호화된 데이터는 안전하게 저장하거나 전송할 수 있습니다.

5.2 접근 제어와 권한 관리

파일 처리 시스템에서 특정 사용자나 시스템만이 민감한 데이터를 접근하고 처리할 수 있도록 접근 제어와 권한 관리를 구현해야 합니다. 이를 위해 인증과 권한 부여 시스템을 설계하고, 필요에 따라 로그를 기록하여 누가 어떤 데이터를 접근했는지 추적할 수 있도록 합니다.