Latest Post

PyTorch: Tương lai của trí tuệ nhân tạo & machine learning Lỗi “Hành vi trên trang web: Điều hướng” trong AdMob – Nguyên nhân và Cách khắc phục

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ể:

  1. Ép bộ nhớ thu hồi sau mỗi lần xử lý (global.gc()).
  2. Dùng Stream để tránh tải toàn bộ ảnh vào RAM.
  3. Dùng Worker Threads để giới hạn số ảnh xử lý đồng thời.
  4. Giải phóng Buffer sau khi xử lý ảnh.
  5. 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

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *