ファイル数が多すぎるフォルダで「ls」コマンドが遅くなってしまう問題の解決方法
ログ置き場などでは、いつの間にか一つのフォルダ内のファイル数が数百万個に膨れ上がってしまうことがあります。そんなフォルダではファイルの一覧をリストアップするための「ls」コマンドの動作も遅くなり、整理作業も一苦労。最初からフォルダを分けておけば問題ないのですが、うっかり一つのフォルダに大量のファイルを保存してしまった場合にはどうすれば良いのかについて、エンジニアのベン・コングルトンさんが解決方法をブログにまとめています。
You can list a directory containing 8 million files! But not with ls.
http://be-n.com/spw/you-can-list-a-million-files-in-a-directory-but-not-with-ls.html
コングルトンさんによると、「ls」や「find .」などの実用的なファイル一覧コマンドの実装はlibcの「readdir()」に依存しているとのこと。この「readdir()」は一度にディレクトリエントリを32KBしか読み込むことができず、ファイル数が膨れ上がってディレクトリエントリが数百MBになってしまったようなディレクトリのすべてのエントリを読み込むのには非常に時間がかかってしまいます。そこで、コングルトンさんは「readdir()」を使わず、「getdents()」システムコールを直接呼び出す方法を考えたそうです。
「getdents()」はディスクからディレクトリエントリを呼び出すための低レベルシステムコールで、「ファイルハンドル」「ディレクトリエントリのポインタ」「バッファサイズ」の3つの引数を取ります。コングルトンさんはマニュアルページの例を元に、まずバッファサイズを5MBに増やしたとのこと。
#define BUF_SIZE 1024*1024*5
そしてメインループのファイル情報を書き出す部分にて「inode」が0のものを除外するように設定。
if (dp->d_ino != 0) printf(...);
コングルトンさんの場合はファイル名だけが重要だったそうで、ファイル名以外を出力しないように変更したとのこと。
if(d->d_ino) printf("%sn ", (char *) d->d_name);
そしてコンパイルして実行。
gcc listdir.c -o listdir ./listdir [directory with insane number of files] > output.txt
こうしてコングルトンさんは800万個ものファイルを一覧に出すことに成功しました。「ls -dl」コマンドでディレクトリエントリのサイズを確認してみると、約513MBにもなっており、32KBずつの読み込みだと1万6416回のシステムコールが必要になっていたとのこと。特に低速な仮想ディスク環境ではこのシステムコールの回数が実行時間に大きく影響を与えているため、「getdents()」のバッファサイズを増やしてシステムコール回数を減らすことが大切だったとコングルトンさんは述べています。
・関連記事
Linuxの基礎用語を完全理解するためにエンジニアが作成した「10のミニプロジェクト」とは? - GIGAZINE
「Steam」開発のValveはなぜDebianからArch Linuxに乗り換えたのか? - GIGAZINE
Linuxを生み出したリーナス・トーバルズが考える「優れたコード」とは何か? - GIGAZINE
Linux生みの親リーナス・トーバルズの当時のメールで振り返る「Linux」誕生の瞬間 - GIGAZINE
2万7000行ものコードをひとつのファイルに書いたLinuxカーネルパッチが送りつけられる - GIGAZINE
・関連コンテンツ