تغییرات اخیر

در اینجا اطلاعیه‌ها، نسخه‌ها و تغییرات جدید لیارا فهرست می‌شوند.

    بهینه‌ترین روش‌ خواندن فایل در Node.js


    ۲۶ اردیبهشت ۱۴۰۰
    بهینه‌ترین روش‌ خواندن فایل در node.js

    ممکن در موارد مختلفی مانند ثبت خطاهای برنامه در فایل Log یا بررسی عملکرد برنامه نیاز باشد تا با فایل‌ها کار کنید. صرف نظر از دلیل شما برای کار با فایل‌ها، خواندن یک فایل در برنامه‌ی Node.js بسیار ساده و آسان است اما اگر اندازه‌ی فایل مورد نظر بیشتر از مقدار RAM سرور باشد یا حتی محدودیت‌هایی بر روی سرور شما اعمال شده باشد با مشکل روبرو خواهید شد بنابراین باید به‌دنبال راه حلی بهینه‌تر برای خواندن فایل‌ها در Node.js باشید.

    حال در این مقاله تصمیم داریم بهینه‌ترین روش برای خواندن فایل‌ها در Node.js را با بررسی راه حل‌های زیر پیدا کنیم:

    • fs.readFileSync
    • fs.createReadStream
    • fs.read

    نگاهی به نمودارها

    نمودار زیر از برنامه‌ی پیاده‌سازی شده‌‌ی Node.js که در یک Docker container اجرا شده و مسئولیت آن خواندن یک فایل یک گیگابایتی به‌صورت Chunkهای ده مگابایتی بوده، به‌دست آمده است. همچنین توجه داشته باشید که مقدار memory در نمودار زیر حاصل جمع memory استفاده شده توسط Node.js و تمام فرایند‌های Docker container است.

    نمودار میزان ram استفاده شده توسط متدهای مختلف برای خواندن فایل در node.js

    همان‌طور که مشاهده می‌کنید متد fs.readFileSync بیش از یک گیگابایت از حافظه را اشغال می‌کند. پس از آن میزان حافظه‌ی استفاده شده توسط متد fs.createReadStream را مشاهده می‌کنید و درنهایت میزان حافظه‌ی استفاده شده توسط متد fs.read وجود دارد که حدودا دو برابر اندازه‌ی هر Chunk از حافظه‌ی سرور استفاده می‌کند.

    اولین انتخاب توسعه‌دهندگان برای خواندن فایل‌ها در Node.js

    متد fs.readFileSync یا متد Asynchronous آن یعنی fs.readFile از اولین انتخاب‌های توسعه‌دهندگان برای خواندن فایل‌ها در Node.js است زیرا با نوشتن چند خط کد بسیار ساده و یک حلقه‌ی for به تمام داده‌های موجود در فایل دسترسی پیدا می‌کنند و با پیمایش آن می‌توانند کارهای مختلفی انجام دهند:

    const CHUNK_SIZE = 10000000; // 10MB
    const data = fs.readFileSync('./file');
    for (let bytesRead = 0; bytesRead < data.length; bytesRead = bytesRead + CHUNK_SIZE) {
        // do something with data 
    }

    اما با مشاهده‌ی نمودارهای بخش قبل متوجه می‌شوید که این راه حل مقدار زیادی از RAM را اشغال می‌کند زیرا در این روش تمام داده‌های فایل در متغیر data ذخیره می‌شود بنابراین جای تعجب نیست که برای یک فایل یک گیگابایتی بیش از یک گیگابایت از RAM را اشغال کند.

    خواندن فایل‌ها در Node.js با استفاده از fs.createReadStream

    استفاده از متد fs.createReadStream به‌سادگی متد fs.readFile است اما این متد مقدار stream را return می‌کند بنابراین شما به یک فرایند پردازشی دیگر نیاز خواهید داشت تا به داده‌های واقعی دسترسی پیدا کنید و ما در مثال زیر از یک حلقه‌ی for await استفاده کرده‌ایم که پیمایش آرایه را برای ما آسان‌تر می‌کند:

    const CHUNK_SIZE = 10000000; // 10MB
    async function start() {
        const stream = fs.createReadStream('./file', { highWaterMark: CHUNK_SIZE });
        for await (const data of stream) {
            // do something with data 
        }
    }
    start();

    متغیر highWaterMark باعث می‌شود که فقط به‌اندازه‌ی تعداد بایت‌های تعریف شده از فایل مورد نظر خوانده شود و به این شکل شاهد کارکرد بهینه‌تر حافظه خواهیم بود زیرا اندازه‌ی محدودی از داده‌ها در حافظه نگهداری می‌شود.

    کنترل بیشتر با استفاده از fs.read

    این روش کمی پیچیده‌تر از دو روش قبلی است بااین‌‌حال کمترین میزان استفاده از RAM را در این روش شاهد هستیم اما قبل‌ از بررسی جزئیات باید بدانید که shared buffer چیست؟

    مقایسه shared buffer و seprate buffer

    shared buffer یک متغیر passed by refrence است و به‌جای ایجاد یک buffer جدید در هر فانکشن، یک buffer واحد در ابتدای برنامه ایجاد می‌شود. در نمودار زیر می‌توانید مقایسه‌ی بین دو برنامه‌ی مختلف را مشاهده کنید که در یکی از shared buffer استفاده شده است.

    نمودار استفاده از حافظه با shared buffer و بدون آن

    همان‌طور که مشاهده می‌کنید با استفاده از shared buffer شاهد استفاده‌ی بهینه‌تر حافظه هستیم و علاوه‌برآن سازگاری بیشتری در برنامه‌ی ما به‌وجود خواهد آمد.

    حال که مفهوم shared buffer را متوجه شدید به‌سراغ پیاده‌سازی برنامه می‌رویم. در مرحله‌ی اول باید یک فانکشن با نام readBytes توسعه دهیم که یک Promise از fs.read را return می‌کند:

    function readBytes(fd, sharedBuffer) {
        return new Promise((resolve, reject) => {
            fs.read(
                fd,
                sharedBuffer,
                0,
                sharedBuffer.length,
                null,
                (err) => {
                    if (err) { return reject(err); }
                    resolve();
                }
            );
        });
    }

    به دلیل اینکه نمی‌خواهیم به ریزجزئیات بپردازیم توصیه می‌شود که برای درک کدهای بالا به مستندات fs.read در سایت Node.js مراجعه کنید.

    در مرحله‌ی بعد یک asynchronous generator با نام generateChunks را توسعه می‌دهیم:

    async function* generateChunks(filePath, size) {
        const sharedBuffer = Buffer.alloc(size);
        const stats = fs.statSync(filePath); // file details
        const fd = fs.openSync(filePath); // file descriptor
        let bytesRead = 0; // how many bytes were read
        let end = size;
    
        for (let i = 0; i < Math.ceil(stats.size / size); i++) {
            await readBytes(fd, sharedBuffer);
            bytesRead = (i + 1) * size;
            if (bytesRead > stats.size) {
                // When we reach the end of file, 
                // we have to calculate how many bytes were actually read
                end = size - (bytesRead - stats.size);
            }
            yield sharedBuffer.slice(0, end);
        }
    }

    درنهایت داده‌ها را به‌کمک حلقه‌ی for پردازش خواهیم کرد:

    const CHUNK_SIZE = 10000000; // 10MB
    
    async function main() {
        for await (const chunk of generateChunks('./file', CHUNK_SIZE)) {
            // do someting with data       
        }
    }
    
    main();

    البته باید بدانید که استفاده از این روش کمی ریسک به‌همراه دارد و امکان دارد Data leak یا Data malformation رخ دهد بنابراین باید در استفاده از این روش محتاط باشید.

    منبع: https://betterprogramming.pub/a-memory-friendly-way-of-reading-files-in-node-js-a45ad0cc7bb6

    برچسب‌ها:

    ســــــــــــــــــــــال‌هاست که هستیم

    ۶ سال در کنار شما تجربه جمع کردیم. تازه در ابتدای مسیر هستیم، مسیر ساخت آینده.

    جمع‌مـــــــــــان، جمع است

    بیش از ۴۰ هزار توسعه‌دهنده و صاحبان کسب و کار در جمع ما هستند. جای شما خالی‌ست...

    خدمات رایگان لیارا

    ۲.۵ گیگابایت فضای ذخیره‌سازی ابری رایگان

    ۲.۵ گیگابایت Object Storage سازگار با پروتکل S3 با دیسک‌های SSD به‌صورت رایگان دریافت کنید.

    هاست رایگان برای دیتابیس‌

    دیتابیس‌های MariaDB، PostgreSQL و Redis را فقط با یک کلیک و به‌صورت رایگان تهیه کنید.

    سرویس DNS رایگان

    به سادگی دامنه‌تان را اضافه کنید و به صورت رایگان رکورد‌های آن را مدیریت کنید.

    ۱۰۰ هزار تومان اعتبار اولیه

    بعد از ثبت نام در لیارا مبلغ ۱۰۰ هزار تومان اعتبار هدیه دریافت می‌کنید که با توجه به ساعتی بودن هزینه سرویس‌ها، می‌توانید تمامی خدمات پولی را برای چندین هفته رایگان استفاده کنید.

    ارسال ۱۰۰ ایمیل تراکنشی رایگان در هر ماه

    در سرویس ایمیل لیارا شما می‌توانید تا ۱۰۰ ایمیل رایگان در هر ماه ارسال کنید. (به‌همراه دسترسی SMTP)

    هاست رایگان برای انواع وبسایت

    تفاوتی ندارد برای وبسایت خود از Node استفاده می‌کنید یا Laravel و Django، در لیارا می‌توانید به صورت کاملا رایگان آن را میزبانی کنید.

    همراه شما هستیم

    در خصوص سفارش یا استفاده از سرویس‌ها سوالی دارید؟
    تلفن واحد فروش:
    ۰۲۵-۳۳۵۵۷۶۱۹ (روزهای کاری ۹ الی ۱۷)
    تلفن واحد فروش: ۳۳۵۵۷۶۱۹-۰۲۵ (روزهای کاری ۹ الی ۱۷)