web-dev-qa-db-ja.com

ノードとエラー:EMFILE、開いているファイルが多すぎます

数日間、私はエラーに対する有効な解決策を探しました

Error: EMFILE, too many open files

多くの人が同じ問題を抱えているようです。通常の答えは、ファイル記述子の数を増やすことです。だから、私はこれを試しました:

sysctl -w kern.maxfiles=20480

デフォルト値は10240です。ディレクトリ内で処理しているファイルの数が10240未満であるため、これは私の目には少し奇妙です。 。

2番目の質問:

何度も検索した結果、「開いているファイルが多すぎる」という問題の回避策が見つかりました。

var requestBatches = {};
function batchingReadFile(filename, callback) {
  // First check to see if there is already a batch
  if (requestBatches.hasOwnProperty(filename)) {
    requestBatches[filename].Push(callback);
    return;
  }

  // Otherwise start a new one and make a real request
  var batch = requestBatches[filename] = [callback];
  FS.readFile(filename, onRealRead);

  // Flush out the batch on complete
  function onRealRead() {
    delete requestBatches[filename];
    for (var i = 0, l = batch.length; i < l; i++) {
      batch[i].apply(null, arguments);
    }
  }
}

function printFile(file){
    console.log(file);
}

dir = "/Users/xaver/Downloads/xaver/xxx/xxx/"

var files = fs.readdirSync(dir);

for (i in files){
    filename = dir + files[i];
    console.log(filename);
    batchingReadFile(filename, printFile);

残念ながら、私はまだ同じエラーを受け取ります。このコードの何が問題になっていますか?

最後の質問(javascriptとノードは初めてです)、私は毎日約5000人のユーザーに多くのリクエストを送るWebアプリケーションを開発中です。私はpythonやJavaのような他の言語を使ったプログラミングで長年の経験があります。もともと、このアプリケーションをDjangoで開発するか、フレームワークをプレイすることを考えていました。次に、ノードを発見しました。ノンブロッキングI/Oモデルのアイデアは本当にすてきで、魅力的で、何よりも非常に高速だと言わなければなりません。

しかし、ノードではどのような問題が発生しますか?実稼働で実証済みのWebサーバーですか?あなたの経験は何ですか?

135
xaverras

graceful-fs が機能しない場合、または単にリークの原因を理解したい場合。このプロセスに従ってください。

(たとえば、問題がソケットにある場合、graceful-fsはワゴンを修正しません。)

私のブログ記事から: http://www.blakerobertson.com/devlog/2014/1/11/how-to-determine-whats-causing-error-connect-emfile-nodejs.html

分離する方法

このコマンドは、nodejsプロセスの開いているハンドルの数を出力します。

lsof -i -n -P | grep nodejs

COMMAND     PID    USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
...
nodejs    12211    root 1012u  IPv4 151317015      0t0  TCP 10.101.42.209:40371->54.236.3.170:80 (ESTABLISHED)
nodejs    12211    root 1013u  IPv4 151279902      0t0  TCP 10.101.42.209:43656->54.236.3.172:80 (ESTABLISHED)
nodejs    12211    root 1014u  IPv4 151317016      0t0  TCP 10.101.42.209:34450->54.236.3.168:80 (ESTABLISHED)
nodejs    12211    root 1015u  IPv4 151289728      0t0  TCP 10.101.42.209:52691->54.236.3.173:80 (ESTABLISHED)
nodejs    12211    root 1016u  IPv4 151305607      0t0  TCP 10.101.42.209:47707->54.236.3.172:80 (ESTABLISHED)
nodejs    12211    root 1017u  IPv4 151289730      0t0  TCP 10.101.42.209:45423->54.236.3.171:80 (ESTABLISHED)
nodejs    12211    root 1018u  IPv4 151289731      0t0  TCP 10.101.42.209:36090->54.236.3.170:80 (ESTABLISHED)
nodejs    12211    root 1019u  IPv4 151314874      0t0  TCP 10.101.42.209:49176->54.236.3.172:80 (ESTABLISHED)
nodejs    12211    root 1020u  IPv4 151289768      0t0  TCP 10.101.42.209:45427->54.236.3.171:80 (ESTABLISHED)
nodejs    12211    root 1021u  IPv4 151289769      0t0  TCP 10.101.42.209:36094->54.236.3.170:80 (ESTABLISHED)
nodejs    12211    root 1022u  IPv4 151279903      0t0  TCP 10.101.42.209:43836->54.236.3.171:80 (ESTABLISHED)
nodejs    12211    root 1023u  IPv4 151281403      0t0  TCP 10.101.42.209:43930->54.236.3.172:80 (ESTABLISHED)
....

1023u(最終行)-デフォルトの最大値である1024番目のファイルハンドルです。

さて、最後の列を見てください。これは、開いているリソースを示します。おそらく同じリソース名の行が多数表示されるでしょう。うまくいけば、これでコードのどこにリークがないかを確認できます。

複数のノードプロセスがわからない場合は、最初にどのプロセスがpid 12211を持っているかを検索します。これにより、プロセスがわかります。

上記の私の場合、非常によく似たIPアドレスがたくさんあることに気付きました。それらはすべて54.236.3.###でした。IPアドレスルックアップを行うことで、私の場合はpubnubに関連していると判断できました。

コマンドリファレンス

この構文を使用して、プロセスが開いているオープンハンドルの数を決定します...

特定のpidの開いているファイルの数を取得するには

このコマンドを使用して、アプリでさまざまなイベントを実行した後に開かれたファイルの数をテストしました。

lsof -i -n -P | grep "8465" | wc -l

# lsof -i -n -P | grep "nodejs.*8465" | wc -l
28
# lsof -i -n -P | grep "nodejs.*8465" | wc -l
31
# lsof -i -n -P | grep "nodejs.*8465" | wc -l
34

プロセスの制限は何ですか?

ulimit -a

必要な行は次のようになります:open files (-n) 1024

制限を永続的に変更します。

  • ubuntu 14.04、nodejs v。7.9でテスト済み

多くの接続を開くことを期待している場合(websocketsが良い例です)、永久に制限を増やすことができます:

  • ファイル:/etc/pam.d/common-session(末尾に追加)

    session required pam_limits.so
    
  • ファイル:/etc/security/limits.conf(最後に追加、または既に存在する場合は編集)

    root soft  nofile 40000
    root hard  nofile 100000
    
  • nodejsを再起動し、sshからログアウト/ログインします。

  • これは古いNodeJSでは機能しない場合があります。サーバーを再起動する必要があります。
  • ノードが異なるuidで実行されている場合の代わりに使用します。
73
blak3r

Isaac Schlueter(node.jsメンテナー)が graceful-fs モジュールを使用することが、おそらく最も適切なソリューションです。 EMFILEが検出されると、増分バックオフを実行します。組み込みのfsモジュールのドロップイン置換として使用できます。

68
Myrne Stol

今日、私はこの問題に遭遇し、それに対する良い解決策が見つからなかったため、それに対処するモジュールを作成しました。 @fbarthoのスニペットに触発されましたが、fsモジュールの上書きを避けたいと思いました。

私が書いたモジュールは Filequeue であり、fsと同じように使用します。

var Filequeue = require('filequeue');
var fq = new Filequeue(200); // max number of files to open at once

fq.readdir('/Users/xaver/Downloads/xaver/xxx/xxx/', function(err, files) {
    if(err) {
        throw err;
    }
    files.forEach(function(file) {
        fq.readFile('/Users/xaver/Downloads/xaver/xxx/xxx/' + file, function(err, data) {
            // do something here
        }
    });
});
6
Trey Griffith

読んでいるファイルが多すぎます。 Nodeはファイルを非同期で読み取ります。すべてのファイルを一度に読み取ります。したがって、おそらく10240の制限を読んでいます。

これが機能するかどうかを確認します。

var fs = require('fs')
var events = require('events')
var util = require('util')
var path = require('path')

var FsPool = module.exports = function(dir) {
    events.EventEmitter.call(this)
    this.dir = dir;
    this.files = [];
    this.active = [];
    this.threads = 1;
    this.on('run', this.runQuta.bind(this))
};
// So will act like an event emitter
util.inherits(FsPool, events.EventEmitter);

FsPool.prototype.runQuta = function() {
    if(this.files.length === 0 && this.active.length === 0) {
        return this.emit('done');
    }
    if(this.active.length < this.threads) {
        var name = this.files.shift()

        this.active.Push(name)
        var fileName = path.join(this.dir, name);
        var self = this;
        fs.stat(fileName, function(err, stats) {
            if(err)
                throw err;
            if(stats.isFile()) {
                fs.readFile(fileName, function(err, data) {
                    if(err)
                        throw err;
                    self.active.splice(self.active.indexOf(name), 1)
                    self.emit('file', name, data);
                    self.emit('run');

                });
            } else {
                self.active.splice(self.active.indexOf(name), 1)
                self.emit('dir', name);
                self.emit('run');
            }
        });
    }
    return this
};
FsPool.prototype.init = function() {
    var dir = this.dir;
    var self = this;
    fs.readdir(dir, function(err, files) {
        if(err)
            throw err;
        self.files = files
        self.emit('run');
    })
    return this
};
var fsPool = new FsPool(__dirname)

fsPool.on('file', function(fileName, fileData) {
    console.log('file name: ' + fileName)
    console.log('file data: ', fileData.toString('utf8'))

})
fsPool.on('dir', function(dirName) {
    console.log('dir name: ' + dirName)

})
fsPool.on('done', function() {
    console.log('done')
});
fsPool.init()
5
Tim P.

この問題を自分で解決するためのコードを少し書き終えたところです。他のソリューションはすべて非常に重く、プログラム構造を変更する必要があります。

このソリューションは、fs.readFileまたはfs.writeFileの呼び出しをストールするだけで、特定の時間に設定された数を超えないようにします。

// Queuing reads and writes, so your nodejs script doesn't overwhelm system limits catastrophically
global.maxFilesInFlight = 100; // Set this value to some number safeish for your system
var origRead = fs.readFile;
var origWrite = fs.writeFile;

var activeCount = 0;
var pending = [];

var wrapCallback = function(cb){
    return function(){
        activeCount--;
        cb.apply(this,Array.prototype.slice.call(arguments));
        if (activeCount < global.maxFilesInFlight && pending.length){
            console.log("Processing Pending read/write");
            pending.shift()();
        }
    };
};
fs.readFile = function(){
    var args = Array.prototype.slice.call(arguments);
    if (activeCount < global.maxFilesInFlight){
        if (args[1] instanceof Function){
            args[1] = wrapCallback(args[1]);
        } else if (args[2] instanceof Function) {
            args[2] = wrapCallback(args[2]);
        }
        activeCount++;
        origRead.apply(fs,args);
    } else {
        console.log("Delaying read:",args[0]);
        pending.Push(function(){
            fs.readFile.apply(fs,args);
        });
    }
};

fs.writeFile = function(){
    var args = Array.prototype.slice.call(arguments);
    if (activeCount < global.maxFilesInFlight){
        if (args[1] instanceof Function){
            args[1] = wrapCallback(args[1]);
        } else if (args[2] instanceof Function) {
            args[2] = wrapCallback(args[2]);
        }
        activeCount++;
        origWrite.apply(fs,args);
    } else {
        console.log("Delaying write:",args[0]);
        pending.Push(function(){
            fs.writeFile.apply(fs,args);
        });
    }
};
2
fbartho

これが誰にも役立つかどうかはわかりませんが、同じエラーを投げた多くの依存関係を持つ大きなプロジェクトに取り組み始めました。私の同僚は、brewを使用してwatchmanをインストールすることを提案しました。

brew update
brew install watchman
2
bh4r4th

nodemonコマンドを実行したときに同じ問題が発生したため、sublime textで開いているファイルの名前を減らし、エラーが消えました。

1
Buhiire Keneth

私たち全員と同様に、あなたも非同期I/Oの犠牲者です。非同期呼び出しでは、多数のファイルをループすると、Node.jsは各ファイルのファイル記述子の読み取りを開始し、閉じるまでアクションを待機します。

ファイル記述子は、サーバー上でそれを読み取るためのリソースが使用可能になるまで開いたままになります。ファイルが小さく、読み取りまたは更新が高速であっても、時間がかかりますが、同時に新しいファイル記述子を開くためにループが停止することはありません。したがって、ファイルが多すぎる場合は、すぐに制限に達し、美しいEMFILEになります。

この影響を回避するためにキューを作成する1つの解決策があります。

Async を書いた人々のおかげで、そのための非常に便利な関数があります。 Async.queue というメソッドがあります。制限付きの新しいキューを作成してから、キューにファイル名を追加します。

注:多くのファイルを開く必要がある場合は、現在開いているファイルを保存し、無限に再度開かないようにすることをお勧めします。

const fs = require('fs')
const async = require("async")

var q = async.queue(function(task, callback) {
    console.log(task.filename);
    fs.readFile(task.filename,"utf-8",function (err, data_read) {
            callback(err,task.filename,data_read);
        }
    );
}, 4);

var files = [1,2,3,4,5,6,7,8,9,10]

for (var file in files) {
    q.Push({filename:file+".txt"}, function (err,filename,res) {
        console.log(filename + " read");
    });
}

各ファイルがキュー(console.logファイル名)に追加されていることがわかりますが、現在のキューが以前に設定した制限を下回っている場合のみです。

async.queueは、コールバックを介してキューの可用性に関する情報を取得します。このコールバックは、データファイルが読み取られ、必要なアクションが達成された場合にのみ呼び出されます。 (fileReadメソッドを参照)

したがって、ファイル記述子に圧倒されることはありません。

> node ./queue.js
0.txt
    1.txt
2.txt
0.txt read
3.txt
3.txt read
4.txt
2.txt read
5.txt
4.txt read
6.txt
5.txt read
7.txt
    1.txt read (biggest file than other)
8.txt
6.txt read
9.txt
7.txt read
8.txt read
9.txt read
1
Plaute

バグパイプでは、変更が必要です

FS.readFile(filename, onRealRead);

=>

var bagpipe = new Bagpipe(10);

bagpipe.Push(FS.readFile, filename, onRealRead))

バグパイプは、並列を制限するのに役立ちます。詳細: https://github.com/JacksonTian/bagpipe

1
user1837639

cwait は、promiseを返す関数の同時実行を制限するための一般的なソリューションです。

あなたの場合、コードは次のようになります:

var Promise = require('bluebird');
var cwait = require('cwait');

// Allow max. 10 concurrent file reads.
var queue = new cwait.TaskQueue(Promise, 10);
var read = queue.wrap(Promise.promisify(batchingReadFile));

Promise.map(files, function(filename) {
    console.log(filename);
    return(read(filename));
})
0
jjrv

@ blak3rの答えに基づいて、他の診断に役立つ場合に使用する簡単な略記を以下に示します。

ファイル記述子が不足しているNode.jsスクリプトをデバッグしようとしている場合、問題のノードプロセスによって使用されるlsofの出力を提供する行があります。

openFiles = child_process.execSync(`lsof -p ${process.pid}`);

これは、現在実行中のNode.jsプロセスによってフィルター処理されたlsofを同期的に実行し、バッファー経由で結果を返します。

次に、console.log(openFiles.toString())を使用してバッファを文字列に変換し、結果を記録します。

0
James