Nodejs: Как я могу оптимизировать написание многих файлов? - PullRequest
0 голосов
/ 18 мая 2018

Я работаю в среде Node на Windows.Мой код получает 30 Buffer объектов (~ 500-900kb каждый) каждую секунду, и мне нужно как можно быстрее сохранить эти данные в файловой системе, не занимаясь какой-либо работой, которая блокирует получение следующих Buffer(т.е. цель состоит в том, чтобы сохранить данные из каждого буфера, в течение ~ 30-45 минут).Для чего это стоит, данные представляют собой последовательные кадры глубины от датчика Kinect.

Мой вопрос: Какой самый эффективный способ записи файлов в Node?

Вот псевдокод:

let num = 0

async function writeFile(filename, data) {
  fs.writeFileSync(filename, data)
}

// This fires 30 times/sec and runs for 30-45 min
dataSender.on('gotData', function(data){

  let filename = 'file-' + num++

  // Do anything with data here to optimize write?
  writeFile(filename, data)
}

fs.writeFileSync кажется намного быстрее, чем fs.writeFile, поэтому я использую это выше.Но есть ли другие способы обработки данных или записи в файл, которые могут ускорить каждое сохранение?

Ответы [ 2 ]

0 голосов
/ 19 мая 2018

Во-первых, вы никогда не захотите использовать fs.writefileSync() при обработке запросов в реальном времени, потому что это блокирует весь цикл событий node.js до тех пор, пока не будет завершена запись в файл.

OK, основываясь на записи каждого блокаданных в другой файл, то вы хотите разрешить одновременную запись нескольких дисков, но не неограниченную запись на диск.Таким образом, все еще уместно использовать очередь, но на этот раз очередь не просто выполняет одну запись за раз, она имеет некоторое количество записей одновременно:

const EventEmitter = require('events');

class Queue extends EventEmitter {
    constructor(basePath, baseIndex, concurrent = 5) {
        this.q = [];
        this.paused = false;
        this.inFlightCntr = 0;
        this.fileCntr = baseIndex;
        this.maxConcurrent = concurrent;
    }

    // add item to the queue and write (if not already writing)
    add(data) {
        this.q.push(data);
        write();
    }

    // write next block from the queue (if not already writing)
    write() {
        while (!paused && this.q.length && this.inFlightCntr < this.maxConcurrent) {
            this.inFlightCntr++;
            let buf = this.q.shift();
            try {
                fs.writeFile(basePath + this.fileCntr++, buf, err => {
                    this.inFlightCntr--;
                    if (err) {
                        this.err(err);
                    } else {
                        // write more data
                        this.write();
                    }
                });
            } catch(e) {
                this.err(e);
            }
        }
    }

    err(e) {
        this.pause();
        this.emit('error', e)
    }

    pause() {
        this.paused = true;
    }

    resume() {
        this.paused = false;
        this.write();
    }
}

let q = new Queue("file-", 0, 5);

// This fires 30 times/sec and runs for 30-45 min
dataSender.on('gotData', function(data){
    q.add(data);
}

q.on('error', function(e) {
    // go some sort of write error here
    console.log(e);
});

Что нужно учесть:

  1. Поэкспериментируйте со значением concurrent, которое вы передадите в конструктор Queue.Начните со значения 5. Затем посмотрите, увеличивает ли это значение более высокую или худшую производительность.Подсистема ввода / вывода файла node.js использует пул потоков для реализации асинхронных операций записи на диск, поэтому существует максимальное количество одновременных записей, что позволит очень быстро увеличить число одновременных записей, вероятно, не заставит дела идти быстрее.

  2. Вы можете поэкспериментировать с увеличением размера пула потоков ввода-вывода, установив переменную среды UV_THREADPOOL_SIZE перед запуском приложения node.js.

  3. Ваш самый большой друг здесь скорость записи на диск .Итак, убедитесь, что у вас быстрый диск с хорошим контроллером диска.Лучше было бы использовать быстрый SSD на быстрой шине.

  4. Если вы можете распределить записи по нескольким реальным физическим дискам, это, вероятно, также увеличит пропускную способность записи (больше дисковых головок в работе).


Это предварительный ответ, основанный на первоначальной интерпретации вопроса (перед редактированием, которое изменило его).

Поскольку, как представляется, вам необходимо выполнитьваш диск записывает по порядку (все в один и тот же файл), тогда я бы посоветовал вам либо использовать поток записи, и позволить объекту потока сериализовать и кэшировать данные для вас, либо вы можете создать очередь самостоятельно, например так:

const EventEmitter = require('events');

class Queue extends EventEmitter {
    // takes an already opened file handle
    constructor(fileHandle) {
        this.f = fileHandle;
        this.q = [];
        this.nowWriting = false;
        this.paused = false;
    }

    // add item to the queue and write (if not already writing)
    add(data) {
        this.q.push(data);
        write();
    }

    // write next block from the queue (if not already writing)
    write() {
        if (!nowWriting && !paused && this.q.length) {
            this.nowWriting = true;
            let buf = this.q.shift();
            fs.write(this.f, buf, (err, bytesWritten) => {
                this.nowWriting = false;
                if (err) {
                    this.pause();
                    this.emit('error', err);
                } else {
                    // write next block
                    this.write();
                }
            });
        }
    }

    pause() {
        this.paused = true;
    }

    resume() {
        this.paused = false;
        this.write();
    }
}

// pass an already opened file handle
let q = new Queue(fileHandle);

// This fires 30 times/sec and runs for 30-45 min
dataSender.on('gotData', function(data){
    q.add(data);
}

q.on('error', function(err) {
    // got disk write error here
});

Вы можете использовать writeStream вместо этого пользовательского класса Queue, но проблема в том, что writeStream может заполниться, и тогда вам потребуется отдельный буфер в качестве места для размещенияданные в любом случае.Использование собственной настраиваемой очереди, как указано выше, решает обе проблемы одновременно.

Другие комментарии о масштабируемости / производительности

  1. Поскольку вы, кажется, пишетеЕсли данные последовательно поступают в один и тот же файл, запись на диск не принесет пользы от кластеризации или параллельного выполнения нескольких операций, поскольку они в основном должны быть сериализованы.

  2. Если на вашем сервере node.js естькроме выполнения этих операций записи, может быть небольшое преимущество (должно быть проверено тестированием) для создания второго процесса node.js и выполнения всей записи на диск в этом другом процессе.Ваш основной процесс node.js получит данные, а затем передаст их дочернему процессу, который будет поддерживать очередь и выполнять запись.

  3. Еще одна вещь, с которой вы можете поэкспериментировать, - это объединение записей.Если в очереди более одного элемента, вы можете объединить их в одну запись.Если записи уже имеют значительный размер, это, вероятно, не имеет большого значения, но если записи были небольшими, это могло бы иметь большое значение (объединение большого количества записей на маленький диск в одну большую запись обычно более эффективно).

  4. Ваш самый большой друг здесь скорость записи на диск .Итак, убедитесь, что у вас быстрый диск с хорошим контроллером диска.Лучше бы быстрый SSD.

0 голосов
/ 18 мая 2018

Я написал сервис, который делает это всесторонне, и лучшее, что вы можете сделать, - это направить входные данные непосредственно в файл (если у вас также есть входной поток).Простой пример, где вы загружаете файл таким образом:

const http = require('http')

const ostream = fs.createWriteStream('./output')
http.get('http://nodejs.org/dist/index.json', (res) => {
    res.pipe(ostream)                                                                                                                                                                                              
})
.on('error', (e) => {
    console.error(`Got error: ${e.message}`);
})

Таким образом, в этом примере промежуточное копирование всего файла не выполняется.Поскольку файл читается частями с удаленного http-сервера, он записывается в файл на диске.Это гораздо эффективнее, чем загрузка всего файла с сервера, сохранение его в памяти и последующая запись в файл на диске.

Потоки являются основой многих операций в Node.js, поэтому вам следует изучить их.также.

Еще одна вещь, которую вы должны исследовать в зависимости от ваших сценариев, это UV_THREADPOOL_SIZE, поскольку операции ввода-вывода используют пул потоков libuv, который по умолчанию установлен в 4, и вы можете заполнить его, если вы выполняете многописьменная форма.

...