Featured image of post NodeJS - Cách mình sử dụng stream để tối ưu việc đọc ghi FILE DATA LỚN như thế nào?

NodeJS - Cách mình sử dụng stream để tối ưu việc đọc ghi FILE DATA LỚN như thế nào?

Trong quá trìnhh phát triển các hệ thống liên quan đến quản lý và vận hànhh dữ liệu, việc xử lý các file dữ liệu lớn là một thách thức không nhỏ, đặc biệt khi bạn cần đảm bảo hiệu suất và sử dụng tài nguyên hệ thống một cách tối ưu. NodeJS cung cấp một thư viện mạnh mẽ là Stream, giúp bạn giải quyết vấn đề này một cách hiệu quả. Trong bài viết này, tôi sẽ chia sẻ cách sử dụng Stream trong NodeJS để tối ưu việc đọc và ghi file dữ liệu lớn, từ đó giúp ứng dụng của bạn hoạt động mượt mà hơn, tránh được những hạn chế khi xử lý dữ liệu theo cách truyền thống.

Tại sao phải cần Stream?

Tiết Kiệm Bộ Nhớ

Khi làm việc với các file lớn hoặc lượng dữ liệu khổng lồ như file log, video, data export từ một bên khác…. Nếu bạn xử lý theo cách thông thường mà khhông sử dụng Stream thì service sẽ đọc toàn bộ dữ liệu vào bộ nhớ - có thể là RAM hoặc CPU cache trước khi xử lý, hệ thống của bạn có thể gặp tình trạng hết bộ nhớ (memory exhaustion). Điều này có thể dẫn đến việc ứng dụng bị treo hoặc thậm chí là crash.

Tăng Hiệu Suất

Streams cho phép bạn bắt đầu xử lý dữ liệu ngay khi một phần của dữ liệu đó sẵn sàng, thay vì phải chờ đợi toàn bộ dữ liệu được tải xuống. Điều này giúp giảm độ trễ và tăng tốc độ xử lý.
Hãy hình dung bạn và gia đình khhoảng 10 người bước vào nhà hàng và mỗi người gọi 1 món - nếu không sử dụng Stream thì nhà hàng sẽ đợi đến khi làm xong 10 món thì mới đem lên cho gia đình bạn dùng. Điều này có thể không cần thiết gây tốn thời gian và tài nguyên. Nếu bạn sử dụng Stream thì nhà hàng sẽ phục ngay khi 1 món được hoàn thành, như vậy sẽ giúp tiết kiệm được thời gian hơn.

Xử Lý Dữ Liệu Liên Tục

Streams rất hữu ích khi làm việc với các nguồn dữ liệu không có điểm dừng rõ ràng, chẳng hạn như việc đọc dữ liệu từ một API phát dữ liệu liên tục, xử lý video, hoặc xử lý dữ liệu từ một socket.
Trong các tình huống này, bạn cần xử lý dữ liệu khi nó đến, mà không cần phải chờ đợi toàn bộ dữ liệu có sẵn.

Linh Hoạt và Dễ Kết Hợp

Bạn có thể kết hợp nhiều streams lại với nhau, chẳng hạn như đọc dữ liệu từ một file, nén dữ liệu, và sau đó gửi dữ liệu đã nén đến một máy chủ từ xa, tất cả chỉ trong một chuỗi các streams.

Vậy tóm lại khi nào thì bạn nên sử dụng Stream:

  • Khi bạn cần xử lý các file hoặc dữ liệu lớn mà không muốn tải toàn bộ vào bộ nhớ.
  • Khi bạn muốn tăng tốc độ xử lý dữ liệu, đặc biệt là với các ứng dụng yêu cầu xử lý real time.

Cách mà Stream thực sự hoạt động

Ví dụ về việc đọc ghi FILE LỚN với Stream

Giả sử mình có một FILE DATA với khoảng 200,000 records. Nếu mình không sử dụng Stream để đọc và ghi file data này thì node process của mình phải cần 1 lượng MEMORY rất lớn để store data cho toàn bộ file -> sau đó mới tiến hành copy toàn bộ data đó vào file output. Mình sẽ minh hoạ bằng đoạn code sau:

no-stream.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
'use strict';

const fs = require('fs');

const server = require('http').createServer ();
console.log('create server');
server.listen(8000);
fs.readFile(
  './customers-2000000-recordcsv.csv',
  (err, data) => {
    if (err) throw err;
    fs.writeFile('./output-without-stream.csv', data, (err) => {
      console.log('success read and write file without streeam port 8000');
    });
  }
);
process.title = 'node withhout stream 8000';

khi tiến hành chạy đoạn code trên với lệnh node no-stream.js và quan sát memory sử dụng ta sẽ thấy process của chúng ta lúc này tăng lượng RAM đột biến lên khoảng 300MB: Process node lúc này phải dùng 1 lượng RAM khá lớn để store data

Bây giờ, hãy thử tối ưu đoạn code trên lại bằng cách sử dụng stream và pipeline của NodeJS nhé:

stream.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
'use strict';

const fs = require('fs');

const { pipeline } = require('stream/promises');

const server = require('http').createServer ();
console.log('create server');
server.listen(8001);
const readStream = fs.createReadStream('./customers-2000000-recordcsv.csv');
const writeStream = fs.createWriteStream('./output-with-stream.csv');

pipeline(readStream,writeStream);


process.title = 'node with stream 8001';

Bây giờ khi run node stream.js và quan sát process ta sẽ thấy process node with stream lúc này luôn sử dụng một lượng memory ổn định khoảng 20MB để đọc và ghi dữ liệu:

Có thể dễ dàng quan sát được process with stream sử dụng ít RAM hơn nhưng output vẫn là như nhau

Trong ví dụ này, với Stream dữ liệu được đọc từ file theo từng phần (chunk) và ngay lập tức ghi vào file đầu ra, giúp tiết kiệm bộ nhớ và tăng hiệu suất.

Tổng Kết

Streams trong NodeJS là một thư viện cực kỳ hữu ích khi xử lý các file dữ liệu lớn. Bằng cách xử lý dữ liệu theo từng phần nhỏ, bạn có thể tiết kiệm bộ nhớ, tăng hiệu suất và tránh các vấn đề liên quan đến việc xử lý dữ liệu lớn. Mặc dù có thể gặp một số khó khăn khi bắt đầu với streams, nhưng lợi ích mà chúng mang lại trong các ứng dụng thực tế là không thể phủ nhận. Nếu bạn đang làm việc với các file lớn hoặc cần xử lý dữ liệu liên tục, hãy xem xét sử dụng streams để tối ưu hóa hiệu suất ứng dụng của mình.

HAPPY CODING!

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy