異常なハードドライブオーバーフローまたは1つのフォルダーから何百万ものファイルを削除する方法

まえがき



おそらく、この記事はベテランのシステム管理者にとってあまり興味深いものではないでしょう。 まず第一に、初心者と同様の問題に直面している人々を対象としています-Linux(私の場合はDebian)の1つのフォルダーから膨大な数のファイルを削除する必要があるだけでなく、df -hが問題になるときにディスクスペースが不足するほぼ30%無料です。



開始する



病気の前兆はありません。

このサイトのサーバーは1年以上(ほぼ500日間の稼働時間)問題なく動作しており、問題はありませんでした。純粋な魂で、私は静かに休暇を取りました。



休暇の最初の日に、彼らは苦情で私に電話をしました-サイトは利用できません。 MySQLがエラー28「デバイスに空き容量がありません」エラーでクラッシュします。



問題は一般的であるように思われます-ディスク容量不足です。 確かに、df -hはディスクに十分な空き容量があることを示していますが、休暇中なので理解するのが面倒です-ディスク上の不要なファイル(古いバックアップなど)を探して削除するようにアドバイスしました。 削除され、すべてが機能したようです。



数時間経過し、問題が再発しました。 奇妙な-この時間中のハードドライブの空き領域は実際には減少しませんでした。 すばやくグーグルで調べた後、serverfaultにトピックが登場しました 。これは、ディスクスペースがなくなったのではなく、 inodeが原因で問題が発生する可能性があるということです。



コンソールにdf -i



と入力すると、実際にiノードが終了しました。



問題



ハードドライブに非常に多くのファイルがあり、すべてのiノードを使用できる場所を探し始めました(500ギガバイトのハードドライブには3000万を超えるiノードがあります)。



そして、私はそれが判明した-それは判明した、 問題はPHPセッションを持つフォルダにあった



どういうわけか、何らかの理由で、このフォルダの自動クリーニングメカニズムが壊れたため、膨大な数のファイルが蓄積されたという事実に至りました。 ls、find、rmなどの標準のLinuxコマンドがないため、どれほど巨大かを言うのは困難です。 -このフォルダでは動作しません。 サーバーがハングするだけでなく、ハングするだけです。 ディレクトリファイル自体の重量が1ギガバイトになり始め、50万を超えるファイルがあったと言えるのは、そこから既に多くのファイルを削除したからです。



解決策



解決策は明らかです。これらのセッションファイルをすべて削除する必要があります。 同時に、サーバーが正常に動作し続けることが望ましいです。 まず、ファイルの束を含むセッションフォルダーの名前を変更し、代わりに空のフォルダーを作成しました。これにより、古い(名前が変更された)フォルダーからすべてのファイルを安全に削除でき、新しいセッションファイルの作成に干渉しません。



また、kronerでは、問題が再発しないように、1時間以上前のセッションファイルの自動削除を追加しました。



そして、彼は主な問題-ハードドライブのクリーニングに目を向けました。



私は解決策「額」を試しました:

 rm -rf ./*
      
      





サーバーがハングし、何も消えなかった



多数のファイルを削除するための既知の方法を試しました

 find . -type f -exec rm -v {} \;
      
      







何も、サーバーがハングし、ファイルは削除されません。



そして今、最も興味深いのは、 mc



ファイルマネージャーがこれらのファイルを削除するタスクにうまく対処したことです! つまり、フォルダーの削除を開始すると、ファイルは削除され、mcはハングしません。 削除は毎分約5,000ファイルの速度で行われますが、これによりハードドライブに大きな負荷がかかり、サーバーが動作不能になります。



しかし、これらのファイルはバックグラウンドで徐々に削除され、サイトの通常の操作を妨げないようにしてください。



実際には、解決策は再びGoogleで見つかりました-Olark getdents



システムコールを使用して、1つのフォルダーに800万個のファイルのリストを表示する方法分割されています



getdents関数のドキュメントと、それを使用するサンプルコードを次に示します。



確かに、この例は私にはまったく向いていませんでした。Olarkがブログでアドバイスしているように、大きなバッファーサイズを設定しても、フォルダー全体を一度に読み取ろうとするとサーバーはフリーズします。



経験的に、30キロバイトのバッファーサイズを選択しました。これにより、サーバーをハングさせたり、ディスクに不要な負荷をかけたりすることなく、ディレクトリから約550個のファイル名を読み取ることができます。 また、サンプルコードを少し書き直して、ファイル名を表示する代わりに削除するようにしました。



その結果、次のコードが得られました。

 #define _GNU_SOURCE #include <dirent.h> /* Defines DT_* constants */ #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/syscall.h> #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) struct linux_dirent { long d_ino; off_t d_off; unsigned short d_reclen; char d_name[]; }; #define BUF_SIZE 1024*30 int main(int argc, char *argv[]) { int fd, nread; char buf[BUF_SIZE]; struct linux_dirent *d; int bpos; int deleted; char d_type; char temp[100]; fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY); if (fd == -1) handle_error("open"); deleted = 0; nread = syscall(SYS_getdents, fd, buf, BUF_SIZE); if (nread == -1) handle_error("getdents"); if (nread != 0) { for (bpos = 0; bpos < nread;) { d = (struct linux_dirent *) (buf + bpos); d_type = *(buf + bpos + d->d_reclen - 1); if(d->d_ino && d->d_ino != 22332748 && d->d_ino != 22332761) { //    inode      ,       "."  ".." -  ,     sprintf(temp,"%s/%s", argv[1], (char *) d->d_name); remove(temp); deleted += 1; } bpos += d->d_reclen; } } printf("deleted %d\n", deleted); exit(EXIT_SUCCESS); }
      
      







通常のgccでコンパイルされたコード

 gcc listdir.c -o listdir
      
      







そして、それはただコマンドラインから始まります:

 ./listdir mod-tmp2
      
      







結果のファイルをクラウンに入れて、サーバーの負荷が通常の範囲内にある間に1分あたり547ファイルで削除されるようになりました。1〜2週間以内にすべてのファイルが削除されることを願っています。



結論



  1. df -h



    がハードディスク上にまだスペースがあることを示している場合、そうでない可能性があります。 df -i



    も見る必要がありdf -i



  2. セッションファイルなどの自動クリーニングのメカニズムに依存するべきではありません-ある時点で機能しなくなる可能性があり、ファイルの山の中にいるので、削除するのは簡単な作業ではありません
  3. ls, rm, find



    などの標準Linuxコマンド 1つのフォルダー内の数百万のファイルのような異常な状況に移行する可能性があります。 この場合、低レベルのシステムコールを使用する必要があります



All Articles