UNIXネタでも書いておこうかと思ったので書いておきます。lsは悪く無い・・・わけでも無いですが、大量にファイルを保存したディレクトリでlsを打ってあっちゃーとなってしまうエンジニアの悲哀。
都市伝説ではありませんが、どこのサーバーにも、「lsしちゃいけないフォルダ」があります。ログファルやデータファイルが大量に保持されていて、ls打つと返ってこない、って、あれです。返ってきたとして、ターミナルを数万というファイル名が流れ去るまで持っていかれるという、あれです。
まぁそんなのは、ファイルの保管方法が悪いというのが根本なんですが、先日、ファイルメンテスクリプトを書いている同僚が、「ファイルが多すぎてlsの応答が遅くて処理遅延アラームが出る」ということを言い出したので、ちょっと調べてみました、と。
lsって何してるんでしたっけ・・・と考えると、ディレクトリのリストを取っている。ただリストを取るだけなら、readdirですから、そんなに時間がかかるわけがない。じゃぁコイツは何をしてるんだ・・・って、まぁタイトルでネタバレしてるんですが、sortが遅いんです。というわけで、試験してみました。試験とは言っても、他に負荷が無い環境での試験ですので、実運用負荷やbufferの効果は考慮してません。参考程度に。
対象はCentOS6、ext4fs。まずは軽く100万件ほどファイルのあるフォルダを作ります。ファイル名はMD5とかで適当に作ります。
#!/usr/bin/perl use Digest::MD5 qw(md5_hex); $j=1000000; for my $i (0..$j) { $x=md5_hex($i); open(FILE,">$x") && close(FILE); }
シェルスクリプトとか時間かかりそうなのでワンショットperlで。これで軽く100万個のファイルのあるディレクトリが出来上がります。ちなみにディレクトリサイズはこんな感じ。
【mkdir直後】 # ls -ld aaa drwxr-xr-x 2 root root 4096 12月 19 22:25 2015 aaa 【100万ファイル保存後】 # ls -ld aaa drwxr-xr-x 2 root root 63397888 12月 19 22:27 2015 aaa
見ただけでlsしたくなくなります。まずはディレクトリをただ読むだけ、ということで、findから。
# time find aaa > /dev/null real 0m0.938s user 0m0.350s sys 0m0.586s
例え100万件ファイルがあったとして、ディレクトリを読むだけなら1秒です。typeオプション付けるとstatまでかかるのでちょっと時間はかかりますが、それでも3秒。
# time find aaa -type f > /dev/null real 0m2.780s user 0m0.638s sys 0m2.133s
では期待のlsです。
# time /bin/ls aaa> /dev/null real 0m6.789s user 0m5.493s sys 0m1.165s
期待通りの動きをしてくれました。概ね6〜7秒です。そんなに遅くはないですが、これはあくまで、すべてbufferに載っている状態です。ポイントは、systemタイムはそれほど大きくないですが、userタイムが出ているところ。要するに、lsはsortに時間がかかっている。
じゃぁ、lsにsortさせないオプションは無いのかというと、あります。-Uです。
# time /bin/ls -U aaa> /dev/null real 0m0.914s user 0m0.417s sys 0m0.496s
これがlsの真の実力です(苦笑)。つまり、lsコマンドが大量ファイルを含んだディレクトリのリスティングが遅いのは、ファイルシステムの読み込みが遅いのではなく、sortしているから遅い。それだけ。さらに、lsには罠があります。aliasです。
# which ls alias ls='ls --color=auto'
rootのbash環境でさえ、最近はこういうaliasが平然と入っていることがありますので、注意が必要です。cpやmvも、素のコマンドになっていないことが多いので、私は必ず、/bin/ls、/bin/cp等と明示的に絶対パスを叩くようにしています。
ここまでの結論。明日とは言わず、今からでも、シェルスクリプトに書いてあるlsコマンドはすべて/bin/ls -Uに置き換えろ、ということ。もちろん、順序性を重視している場合は別として、ですが、とりあえずリストを取るためにls取っている場合は-Uオプションを付けておいて悪くは無いと思います。
追加で、ですが。これらシステムコマンドって、見た目以上にいろんな処理が行われていて、かつ、処理上もなるべく安全に、誤動作しないように作られていますから、普通に言って「遅い」です。スピード狂な方は、スクリプト言語で書いてしまう、あるいはシェルスクリプトであればシェルのbuilt-inコマンドを使うほうが速いことが多いです。
私は古いエンジニアなので、こういう場合はいつもperlなんですが・・・
#!/usr/bin/perl use Time::HiRes qw(tv_interval gettimeofday); opendir(DIR,"."); $t0 = [gettimeofday]; while ($x=readdir(DIR)) { push @a,$x; } print tv_interval($t0,[gettimeofday])."\n"; $t0 = [gettimeofday]; @b=sort @a; print tv_interval($t0,[gettimeofday])."\n"; 【実行結果】 1.292937 3.218303 real 0m4.604s user 0m3.868s sys 0m0.721s
若干乱暴ですが、sortのuser timeが3秒台です。sort(sort)やフィルタ(grep)は、大抵、ちょっとプログラムを書いて、「外」でやらせたほうが速いケースが多いというのが経験則。普段はそんなに気にならないところですが、相手が数GBにも及ぶログファイルとなってくると、業務効率に直結しますので無視できません。もちろん、本気で検索したいなら、groonga/mroonga、KVS、MongoDB等、保存方法の要件を考えたほうが効率的なんですが、大抵、こういう依頼が来るのは、本来、想定しない要件(例えば障害対応)が多いですから。
なので、不要なsortは止めましょう。暗黙のsortが、処理を遅くしている場合があります。
【追記】100万ファイル試験、なんだかやたら速いなぁと思いましたが、これ、ベアメタルでディスクはSSDです。速くて当然だわ。
コメント