Các bài cùng serial: Tối ưu xử lý ảnh dung lượng lớn trong Node.js
- Tối ưu xử lý bộ nhớ khi xử lý ảnh trong Node.js
- Cách giải phóng bộ nhớ sau mỗi ảnh được xử lý khi dùng sharp trong Node.js(bạn đang xem)
- Cách giải phóng bộ nhớ sau mỗi lần trim ảnh với thư viện trim-image trong Node.js
Nếu bạn sử dụng Node.js để trim ảnh bằng sharp với số lượng lớn và gặp tình trạng bộ nhớ bị đầy (out of memory), bạn có thể xử lý bằng cách giải phóng bộ nhớ sau mỗi ảnh được xử lý. Dưới đây là một số cách khắc phục:
🔹 1. Giải phóng bộ nhớ bằng cách sử dụng global.gc()
(Garbage Collector)
Nếu bạn chạy Node.js với tùy chọn --expose-gc
, bạn có thể sử dụng global.gc()
để ép buộc Garbage Collector thu hồi bộ nhớ sau mỗi lần xử lý ảnh.
✅ Cách làm
Chạy script Node.js với cờ --expose-gc
:
node --expose-gc trim-image.js
Trong code, thêm global.gc();
sau mỗi lần xử lý ảnh:
const fs = require('fs');
const sharp = require('sharp');
async function trimImage(inputPath, outputPath) {
try {
await sharp(inputPath)
.trim()
.toFile(outputPath);
console.log(`✅ Trimmed: ${outputPath}`);
// Giải phóng bộ nhớ sau mỗi ảnh
if (global.gc) {
global.gc();
}
} catch (error) {
console.error(`❌ Error processing ${inputPath}:`, error);
}
}
// Xử lý nhiều ảnh
async function processImages(imageList) {
for (const [index, image] of imageList.entries()) {
console.log(`Processing ${index + 1}/${imageList.length}...`);
await trimImage(image.input, image.output);
}
}
// Danh sách ảnh cần xử lý
const images = [
{ input: 'input1.jpg', output: 'output1.jpg' },
{ input: 'input2.jpg', output: 'output2.jpg' },
// Thêm nhiều ảnh nữa...
];
// Chạy script
processImages(images);
👉 Lưu ý: global.gc()
chỉ hoạt động nếu Node.js được khởi động với --expose-gc
.
🔹 2. Hạn chế sử dụng bộ nhớ bằng cách xử lý ảnh từng phần (Stream)
Thay vì tải toàn bộ ảnh vào bộ nhớ, bạn có thể sử dụng Stream để xử lý từng phần ảnh.
✅ Cách làm
Dùng pipe() để giảm tải bộ nhớ:
const fs = require('fs');
const sharp = require('sharp');
async function trimImage(inputPath, outputPath) {
return new Promise((resolve, reject) => {
const inputStream = fs.createReadStream(inputPath);
const outputStream = fs.createWriteStream(outputPath);
inputStream.pipe(sharp().trim()).pipe(outputStream);
outputStream.on('finish', () => {
console.log(`✅ Trimmed: ${outputPath}`);
resolve();
});
outputStream.on('error', reject);
});
}
// Xử lý danh sách ảnh
async function processImages(imageList) {
for (const image of imageList) {
await trimImage(image.input, image.output);
}
}
// Danh sách ảnh cần xử lý
const images = [
{ input: 'input1.jpg', output: 'output1.jpg' },
{ input: 'input2.jpg', output: 'output2.jpg' },
// Thêm nhiều ảnh nữa...
];
// Chạy script
processImages(images);
Lợi ích:
✔️ Tiết kiệm bộ nhớ hơn vì không tải toàn bộ ảnh vào RAM.
✔️ Giảm thiểu lỗi “heap out of memory”.
🔹 3. Sử dụng Worker Threads để xử lý ảnh song song nhưng tránh quá tải
Nếu bạn xử lý quá nhiều ảnh cùng lúc, Node.js có thể bị quá tải bộ nhớ. Bạn có thể dùng Worker Threads để hạn chế số lượng ảnh xử lý đồng thời.
✅ Cách làm
Sử dụng Worker Threads để xử lý ảnh theo từng nhóm (batch):
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
const sharp = require('sharp');
if (!isMainThread) {
// Xử lý ảnh trong Worker
sharp(workerData.input)
.trim()
.toFile(workerData.output)
.then(() => {
parentPort.postMessage({ status: 'done', output: workerData.output });
})
.catch(error => {
parentPort.postMessage({ status: 'error', error });
});
} else {
const fs = require('fs');
const images = [
{ input: 'input1.jpg', output: 'output1.jpg' },
{ input: 'input2.jpg', output: 'output2.jpg' },
{ input: 'input3.jpg', output: 'output3.jpg' },
{ input: 'input4.jpg', output: 'output4.jpg' }
];
const MAX_WORKERS = 2; // Giới hạn số worker chạy cùng lúc
let activeWorkers = 0;
let index = 0;
function runNextWorker() {
if (index >= images.length) return;
const image = images[index++];
activeWorkers++;
const worker = new Worker(__filename, { workerData: image });
worker.on('message', msg => {
if (msg.status === 'done') {
console.log(`✅ Trimmed: ${msg.output}`);
}
activeWorkers--;
runNextWorker(); // Chạy worker tiếp theo
});
worker.on('error', error => {
console.error(`❌ Error:`, error);
activeWorkers--;
runNextWorker(); // Tiếp tục với ảnh khác
});
}
// Khởi động các worker ban đầu
for (let i = 0; i < MAX_WORKERS; i++) {
runNextWorker();
}
}
Lợi ích:
✔️ Hạn chế quá tải bộ nhớ bằng cách chỉ chạy tối đa 2 worker cùng lúc.
✔️ Tận dụng CPU đa luồng để tăng tốc xử lý ảnh.
🔹 4. Giải phóng bộ nhớ bằng cách xóa Buffer sau khi xử lý ảnh
Nếu bạn đang sử dụng Buffer để xử lý ảnh, hãy xóa Buffer sau mỗi lần xử lý:
const sharp = require('sharp');
async function trimImage(inputPath, outputPath) {
try {
let imageBuffer = await sharp(inputPath).trim().toBuffer();
await sharp(imageBuffer).toFile(outputPath);
console.log(`✅ Trimmed: ${outputPath}`);
// Xóa buffer khỏi bộ nhớ
imageBuffer = null;
global.gc(); // Ép bộ nhớ được thu hồi (chạy với --expose-gc)
} catch (error) {
console.error(`❌ Error processing ${inputPath}:`, error);
}
}
Lợi ích:
✔️ Tránh tích lũy Buffer làm tốn RAM.
✔️ Phù hợp khi dùng toBuffer()
thay vì toFile()
.
🔹 5. Chạy với --max-old-space-size
để tăng giới hạn bộ nhớ (không khuyến khích)
Nếu bạn không thể tối ưu code ngay, bạn có thể tăng giới hạn bộ nhớ của Node.js:
node --max-old-space-size=8192 trim-image.js
Điều này sẽ giúp Node.js sử dụng tối đa 8GB RAM, nhưng không phải cách tốt nhất.
✅ Kết luận
Để tránh lỗi full bộ nhớ khi trim ảnh số lượng lớn, bạn có thể:
- Ép bộ nhớ thu hồi sau mỗi lần xử lý (
global.gc()
). - Dùng Stream để tránh tải toàn bộ ảnh vào RAM.
- Dùng Worker Threads để giới hạn số ảnh xử lý đồng thời.
- Giải phóng Buffer sau khi xử lý ảnh.
- Tăng giới hạn bộ nhớ (
--max-old-space-size
) (không khuyến khích nếu chưa tối ưu code).
Bạn có thể thử các cách trên xem cách nào hiệu quả nhất với hệ thống của bạn! 🚀
One thought on “Cách giải phóng bộ nhớ sau mỗi ảnh được xử lý khi dùng sharp trong Node.js”