rm pada direktori dengan jutaan file


104

Latar belakang: server fisik, sekitar dua tahun, drive SATA 7200-RPM terhubung ke kartu RAID 3Ware, noatime yang dipasang di ext3 FS dan data = dipesan, bukan di bawah beban gila, kernel 2.6.18-92.1.22.el5, uptime 545 hari . Direktori tidak mengandung subdirektori, hanya jutaan file kecil (~ 100 byte), dengan beberapa yang lebih besar (beberapa KB).

Kami memiliki server yang sudah agak cuckoo selama beberapa bulan terakhir, tetapi kami baru menyadarinya pada hari lain ketika server mulai tidak dapat menulis ke direktori karena mengandung terlalu banyak file. Secara khusus, itu mulai melempar kesalahan ini di / var / log / messages:

ext3_dx_add_entry: Directory index full!

Disk yang dimaksud memiliki banyak inode yang tersisa:

Filesystem            Inodes   IUsed   IFree IUse% Mounted on
/dev/sda3            60719104 3465660 57253444    6% /

Jadi saya menduga itu berarti kita mencapai batas berapa banyak entri dapat di file direktori itu sendiri. Tidak tahu berapa banyak file yang akan, tetapi tidak bisa lebih, seperti yang Anda lihat, lebih dari tiga juta. Bukan itu bagus, ingatlah! Tapi itu bagian pertama dari pertanyaan saya: persis apa batas atas itu? Apakah bisa merdu? Sebelum saya dimarahi - saya ingin meremehkannya ; direktori besar ini menyebabkan segala macam masalah.

Bagaimanapun, kami melacak masalah dalam kode yang menghasilkan semua file itu, dan kami telah memperbaikinya. Sekarang saya terjebak dengan menghapus direktori.

Beberapa opsi di sini:

  1. rm -rf (dir)

    Saya mencoba ini dulu. Saya menyerah dan membunuhnya setelah berjalan selama satu setengah hari tanpa dampak yang jelas.

  2. batalkan tautan (2) pada direktori: Pasti layak dipertimbangkan, tetapi pertanyaannya adalah apakah akan lebih cepat untuk menghapus file di dalam direktori melalui fsck daripada menghapus melalui batalkan tautan (2). Artinya, dengan satu atau lain cara, saya harus menandai inode tersebut sebagai tidak terpakai. Ini mengasumsikan, tentu saja, bahwa saya dapat memberitahu fsck untuk tidak menjatuhkan entri ke file in / lost + found; jika tidak, saya baru saja memindahkan masalah saya. Selain semua kekhawatiran lain, setelah membaca tentang ini sedikit lebih banyak, ternyata saya mungkin harus memanggil beberapa fungsi FS internal, karena tidak ada varian unlink (2) yang dapat saya temukan yang akan memungkinkan saya untuk menghapus dengan blithely direktori dengan entri di dalamnya. Pooh.
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    Ini sebenarnya adalah versi singkat; yang sebenarnya saya jalankan, yang hanya menambahkan beberapa pelaporan kemajuan dan berhenti bersih ketika kita kehabisan file untuk dihapus, adalah:

    ekspor i = 0;
    waktu (sementara [benar]; lakukan
      ls -Uf | kepala -n 3 | grep -qF '.png' || istirahat;
      ls -Uf | kepala -n 10000 | xargs rm -f 2> / dev / null;
      ekspor i = $ (($ i + 10000));
      gema "$ i ...";
    Selesai )

    Ini sepertinya bekerja dengan baik. Saat saya menulis ini, ia telah menghapus 260.000 file dalam sekitar tiga puluh menit terakhir.

Sekarang, untuk pertanyaan:
  1. Seperti disebutkan di atas, apakah batas entri per-direktori dapat diatur?
  2. Mengapa diperlukan "real 7m9.561s / pengguna 0m0.001s / sys 0m0.001s" untuk menghapus satu file yang merupakan yang pertama dalam daftar yang dikembalikan oleh ls -U, dan mungkin butuh sepuluh menit untuk menghapus 10.000 entri pertama dengan perintah di # 3, tapi sekarang sudah cukup senang? Untuk itu, itu dihapus 260.000 dalam waktu sekitar tiga puluh menit, tetapi sekarang butuh lima belas menit lagi untuk menghapus 60.000 lebih. Mengapa ayunan besar dalam kecepatan?
  3. Apakah ada cara yang lebih baik untuk melakukan hal semacam ini? Tidak menyimpan jutaan file dalam direktori; Saya tahu itu konyol, dan itu tidak akan terjadi pada jam tangan saya. Menelusuri masalah dan melihat melalui SF dan SO menawarkan banyak variasi findyang tidak akan secara signifikan lebih cepat daripada pendekatan saya karena beberapa alasan yang jelas. Tapi apakah ide delete-via-fsck punya kaki? Atau sesuatu yang lain sama sekali? Saya ingin sekali mendengar pemikiran out-of-the-box (atau di dalam-tidak-terkenal).
Terima kasih telah membaca novel kecil; jangan ragu untuk bertanya dan saya pasti akan menjawab. Saya juga akan memperbarui pertanyaan dengan jumlah file terakhir dan berapa lama skrip hapus berjalan setelah saya memilikinya.

Output skrip akhir !:

2970000...
2980000...
2990000...
3000000...
3010000...

real    253m59.331s
user    0m6.061s
sys     5m4.019s

Jadi, tiga juta file dihapus dalam waktu lebih dari empat jam.


1
rm (GNU coreutils) 8.4 memiliki opsi ini: "-v, --verbose jelaskan apa yang sedang dilakukan" . Ini akan menampilkan semua file yang sedang dihapus.
Cristian Ciupitu

2
Sebenarnya, itu akan menjadi cara yang rapi untuk melakukan progress bar: karena setiap file panjangnya tiga puluh tujuh karakter (36 + a '\ n'), saya dapat dengan mudah menulis parser untuk itu, dan karena printf () adalah murah dan perintah rm sudah memiliki nama file dimuat, tidak ada penalti kinerja utama. Sepertinya bukan pemula untuk melakukan seluruh shebang, karena saya tidak pernah bisa "rm" melakukan hal seperti itu. Tapi itu bisa bekerja dengan baik sebagai bilah kemajuan intra-10.000; mungkin "." untuk setiap seratus file?
BMDan

8
rm -rfv | pv -l >/dev/null. pv harus tersedia di repositori EPEL .
Cristian Ciupitu

5
pv sangat luar biasa. Saya meninggalkan jejak instalasi pv di belakang saya.
BMDan

Saya memiliki masalah yang sama persis baru-baru ini. Terima kasih!
richo

Jawaban:


30

The data=writebackmount option layak untuk dicoba, untuk mencegah journal dari sistem file. Ini harus dilakukan hanya selama waktu penghapusan, namun ada risiko jika server sedang dimatikan atau di-reboot selama operasi penghapusan.

Menurut halaman ini ,

Beberapa aplikasi menunjukkan peningkatan kecepatan yang sangat signifikan ketika digunakan. Misalnya, peningkatan kecepatan dapat dilihat (...) ketika aplikasi membuat dan menghapus volume besar file kecil.

Opsi ini diatur dalam fstabatau selama operasi pemasangan, diganti data=ordereddengan data=writeback. Sistem file yang berisi file yang akan dihapus harus di-remount.


1
Dia juga dapat menambah waktu dari commit opsi : "Nilai default ini (atau nilai rendah apa pun) akan merusak kinerja, tetapi bagus untuk keamanan data. Mengaturnya ke 0 akan memiliki efek yang sama dengan membiarkannya pada default (5 detik). Pengaturan ke nilai yang sangat besar akan meningkatkan kinerja ".
Cristian Ciupitu

1
Writeback terlihat sangat bagus, kecuali dokumentasi yang saya lihat ( gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4 ) secara eksplisit menyebutkan bahwa itu masih jurnal metadata, yang saya anggap mencakup semua data saya mengubah (saya tentu tidak mengubah data apa pun di file itu sendiri). Apakah pemahaman saya tentang opsi salah?
BMDan

Terakhir, FYI, yang tidak disebutkan dalam tautan itu adalah fakta bahwa data = writeback dapat menjadi lubang keamanan yang sangat besar, karena data yang ditunjukkan oleh entri yang diberikan mungkin tidak memiliki data yang ditulis di sana oleh aplikasi, yang berarti bahwa kerusakan dapat terjadi dalam data lama, mungkin sensitif / pribadi yang diekspos. Bukan masalah di sini, karena kami hanya menyalakannya sementara, tapi saya ingin mengingatkan semua orang tentang peringatan itu jika Anda atau orang lain yang menemukan saran itu tidak menyadarinya.
BMDan

commit: itu cukup apik! Terima kasih untuk penunjuknya.
BMDan

2
data=writebackmasih jurnal metadata sebelum menulisnya ke sistem file utama. Seperti yang saya pahami, itu hanya tidak memaksakan pemesanan antara hal-hal seperti menulis peta luas dan menulis data ke luasan tersebut. Mungkin ada kendala pemesanan lain yang membuatnya rileks juga, jika Anda melihat keuntungan dari hal ini. Tentu saja, pemasangan tanpa jurnal sama sekali bisa menjadi kinerja yang lebih tinggi. (Mungkin membiarkan perubahan metadata terjadi begitu saja di RAM, tanpa perlu memiliki apa pun pada disk sebelum op tautan keluar selesai).
Peter Cordes

80

Sementara penyebab utama masalah ini adalah kinerja ext3 dengan jutaan file, penyebab sebenarnya dari masalah ini berbeda.

Ketika direktori perlu terdaftar readdir () dipanggil pada direktori yang menghasilkan daftar file. readdir adalah panggilan posix, tetapi panggilan sistem Linux yang digunakan di sini disebut 'getdents'. Entri daftar direktori entri dengan mengisi buffer dengan entri.

Masalahnya terutama karena fakta bahwa readdir () menggunakan ukuran buffer tetap sebesar 32Kb untuk mengambil file. Ketika direktori semakin besar dan lebih besar (ukurannya bertambah ketika file ditambahkan) ext3 semakin lambat untuk mengambil entri dan ukuran buffer 32Kb readdir tambahan hanya cukup untuk memasukkan sebagian kecil dari entri dalam direktori. Ini menyebabkan readdir berulang-ulang dan memanggil system call yang mahal berulang kali.

Sebagai contoh, pada direktori pengujian yang saya buat dengan lebih dari 2,6 juta file di dalamnya, menjalankan "ls -1 | wc-l" menunjukkan output strace besar dari banyak panggilan sistem getdent.

$ strace ls -1 | wc -l
brk(0x4949000)                          = 0x4949000
getdents(3, /* 1025 entries */, 32768)  = 32752
getdents(3, /* 1024 entries */, 32768)  = 32752
getdents(3, /* 1025 entries */, 32768)  = 32760
getdents(3, /* 1025 entries */, 32768)  = 32768
brk(0)                                  = 0x4949000
brk(0x496a000)                          = 0x496a000
getdents(3, /* 1024 entries */, 32768)  = 32752
getdents(3, /* 1026 entries */, 32768)  = 32760
...

Selain itu waktu yang dihabiskan dalam direktori ini sangat signifikan.

$ time ls -1 | wc -l
2616044

real    0m20.609s
user    0m16.241s
sys 0m3.639s

Metode untuk menjadikan ini proses yang lebih efisien adalah dengan memanggil getdents secara manual dengan buffer yang jauh lebih besar. Ini meningkatkan kinerja secara signifikan.

Sekarang, Anda tidak seharusnya memanggil getdents sendiri secara manual sehingga tidak ada antarmuka untuk menggunakannya secara normal (lihat halaman manual untuk getdents untuk melihat!), Namun Anda dapat memanggilnya secara manual dan membuat doa pemanggilan sistem Anda lebih efisien.

Ini secara drastis mengurangi waktu yang diperlukan untuk mengambil file-file ini. Saya menulis sebuah program yang melakukan ini.

/* I can be compiled with the command "gcc -o dentls dentls.c" */

#define _GNU_SOURCE

#include <dirent.h>     /* Defines DT_* constants */
#include <err.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

struct linux_dirent {
        long           d_ino;
        off_t          d_off;
        unsigned short d_reclen;
        char           d_name[256];
        char           d_type;
};

static int delete = 0;
char *path = NULL;

static void parse_config(
        int argc,
        char **argv)
{
    int option_idx = 0;
    static struct option loptions[] = {
      { "delete", no_argument, &delete, 1 },
      { "help", no_argument, NULL, 'h' },
      { 0, 0, 0, 0 }
    };

    while (1) {
        int c = getopt_long(argc, argv, "h", loptions, &option_idx);
        if (c < 0)
            break;

        switch(c) {
          case 0: {
              break;
          }

          case 'h': {
              printf("Usage: %s [--delete] DIRECTORY\n"
                     "List/Delete files in DIRECTORY.\n"
                     "Example %s --delete /var/spool/postfix/deferred\n",
                     argv[0], argv[0]);
              exit(0);                      
              break;
          }

          default:
          break;
        }
    }

    if (optind >= argc)
      errx(EXIT_FAILURE, "Must supply a valid directory\n");

    path = argv[optind];
}

int main(
    int argc,
    char** argv)
{

    parse_config(argc, argv);

    int totalfiles = 0;
    int dirfd = -1;
    int offset = 0;
    int bufcount = 0;
    void *buffer = NULL;
    char *d_type;
    struct linux_dirent *dent = NULL;
    struct stat dstat;

    /* Standard sanity checking stuff */
    if (access(path, R_OK) < 0) 
        err(EXIT_FAILURE, "Could not access directory");

    if (lstat(path, &dstat) < 0) 
        err(EXIT_FAILURE, "Unable to lstat path");

    if (!S_ISDIR(dstat.st_mode))
        errx(EXIT_FAILURE, "The path %s is not a directory.\n", path);

    /* Allocate a buffer of equal size to the directory to store dents */
    if ((buffer = calloc(dstat.st_size*3, 1)) == NULL)
        err(EXIT_FAILURE, "Buffer allocation failure");

    /* Open the directory */
    if ((dirfd = open(path, O_RDONLY)) < 0) 
        err(EXIT_FAILURE, "Open error");

    /* Switch directories */
    fchdir(dirfd);

    if (delete) {
        printf("Deleting files in ");
        for (int i=5; i > 0; i--) {
            printf("%u. . . ", i);
            fflush(stdout);
            sleep(1);
        }
        printf("\n");
    }

    while (bufcount = syscall(SYS_getdents, dirfd, buffer, dstat.st_size*3)) {
        offset = 0;
        dent = buffer;
        while (offset < bufcount) {
            /* Don't print thisdir and parent dir */
            if (!((strcmp(".",dent->d_name) == 0) || (strcmp("..",dent->d_name) == 0))) {
                d_type = (char *)dent + dent->d_reclen-1;
                /* Only print files */
                if (*d_type == DT_REG) {
                    printf ("%s\n", dent->d_name);
                    if (delete) {
                        if (unlink(dent->d_name) < 0)
                            warn("Cannot delete file \"%s\"", dent->d_name);
                    }
                    totalfiles++;
                }
            }
            offset += dent->d_reclen;
            dent = buffer + offset;
        }
    }
    fprintf(stderr, "Total files: %d\n", totalfiles);
    close(dirfd);
    free(buffer);

    exit(0);
}

Meskipun ini tidak memerangi masalah mendasar yang mendasarinya (banyak file, dalam sistem file yang berkinerja buruk dalam hal itu). Kemungkinan besar, jauh lebih cepat daripada banyak alternatif yang diposting.

Sebagai pemikiran, seseorang harus menghapus direktori yang terkena dampak dan membuat ulang setelahnya. Direktori hanya bertambah ukurannya dan dapat tetap berkinerja buruk bahkan dengan beberapa file di dalamnya karena ukuran direktori.

Sunting: Saya sudah membersihkan ini sedikit. Menambahkan opsi untuk memungkinkan Anda menghapus pada baris perintah saat runtime dan menghapus banyak hal treewalk yang, jujur ​​melihat ke belakang dipertanyakan di terbaik. Juga terbukti menghasilkan korupsi memori.

Sekarang Anda bisa melakukannya dentls --delete /my/path

Hasil baru. Didasarkan pada direktori dengan 1,82 juta file.

## Ideal ls Uncached
$ time ls -u1 data >/dev/null

real    0m44.948s
user    0m1.737s
sys 0m22.000s

## Ideal ls Cached
$ time ls -u1 data >/dev/null

real    0m46.012s
user    0m1.746s
sys 0m21.805s


### dentls uncached
$ time ./dentls data >/dev/null
Total files: 1819292

real    0m1.608s
user    0m0.059s
sys 0m0.791s

## dentls cached
$ time ./dentls data >/dev/null
Total files: 1819292

real    0m0.771s
user    0m0.057s
sys 0m0.711s

Terkejut ini masih bekerja dengan baik!


1
Dua masalah kecil: satu, [256]mungkin seharusnya [FILENAME_MAX], dan dua, Linux saya (2.6.18 == CentOS 5.x) tampaknya tidak memasukkan entri d_type dalam direktori (setidaknya menurut getdents (2)).
BMDan

1
Bisakah Anda menjelaskan sedikit tentang penyeimbangan kembali btree dan mengapa penghapusan agar mencegahnya? Saya mencoba Googling untuk itu, sayangnya tidak berhasil.
ovgolovin

1
Karena sekarang menurut saya jika kita menghapus secara berurutan, kami memaksakan penyeimbangan kembali, karena kami menghapus daun di satu sisi dan meninggalkan di sisi lain: en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion
ovgolovin

1
Saya harap saya tidak mengganggu Anda dengan masalah ini. Tapi saya masih memulai pertanyaan tentang menghapus file in-order stackoverflow.com/q/17955459/862380 , yang tampaknya tidak menerima jawaban yang akan menjelaskan masalah dengan contoh ini, yang akan dimengerti oleh programmer biasa. Jika Anda punya waktu dan merasa seperti itu, dapatkah Anda melihatnya? Mungkin Anda bisa menulis penjelasan yang lebih baik.
ovgolovin

2
Ini adalah kode yang luar biasa. Itu adalah satu-satunya alat yang saya dapat temukan untuk daftar dan menghapus sekitar 11.000.000 (sebelas juta) file sesi yang telah dibangun dalam direktori, mungkin selama beberapa tahun. Proses Plesk yang seharusnya membuat mereka tetap terkendali menggunakan find dan trik lain dalam jawaban lain di sini, tidak dapat menyelesaikan proses, sehingga file-file terus bertambah. Ini adalah penghargaan untuk pohon biner yang digunakan sistem file untuk menyimpan direktori, bahwa sesi dapat bekerja sama sekali - Anda dapat membuat file dan mengambilnya tanpa penundaan. Hanya cantuman yang tidak dapat digunakan.
Jason

31

Apakah mungkin untuk membuat cadangan semua file lain dari sistem file ini ke lokasi penyimpanan sementara, memformat ulang partisi, dan kemudian mengembalikan file?


3
Sebenarnya saya sangat suka jawaban ini. Secara praktis, dalam hal ini, tidak, tapi itu bukan yang saya pikirkan. Bravo!
BMDan

Persis seperti yang kupikirkan juga. Ini adalah jawaban untuk pertanyaan 3. Ideal jika Anda bertanya kepada saya :)
Joshua

12

Tidak ada batas per direktori file dalam ext3 hanya batas inode filesystem (saya pikir ada batas pada jumlah subdirektori meskipun).

Anda mungkin masih mengalami masalah setelah menghapus file.

Ketika sebuah direktori memiliki jutaan file, entri direktori itu sendiri menjadi sangat besar. Entri direktori harus dipindai untuk setiap operasi penghapusan, dan itu membutuhkan berbagai jumlah waktu untuk setiap file, tergantung di mana entri itu berada. Sayangnya bahkan setelah semua file dihapus, entri direktori mempertahankan ukurannya. Jadi operasi lebih lanjut yang memerlukan pemindaian entri direktori masih akan memakan waktu lama bahkan jika direktori sekarang kosong. Satu-satunya cara untuk mengatasi masalah itu adalah mengubah nama direktori, membuat yang baru dengan nama lama, dan mentransfer file yang tersisa ke yang baru. Kemudian hapus yang diganti namanya.


Memang, saya perhatikan perilaku ini setelah menghapus semuanya. Untungnya, kami sudah menemukan direktori keluar dari "garis api", jadi, jadi saya bisa rmdir itu.
BMDan

2
Yang mengatakan, jika tidak ada batas file per-direktori, mengapa saya mendapatkan "ext3_dx_add_entry: Indeks direktori penuh!" ketika masih ada inode yang tersedia di partisi itu? Tidak ada subdirektori di dalam direktori ini.
BMDan

3
hmm saya melakukan sedikit riset lebih lanjut dan sepertinya ada batasan jumlah blok yang bisa diambil oleh sebuah direktori. Jumlah pasti file tergantung pada beberapa hal, misalnya panjang nama file. Gossamer-threads.com/lists/linux/kernel/921942 ini tampaknya menunjukkan bahwa dengan blok 4k Anda harus dapat memiliki lebih dari 8 juta file dalam direktori. Apakah mereka nama file yang panjang?
Alex J. Roberts

Setiap nama file panjangnya tepat 36 karakter.
BMDan

baik itu saya kehabisan ide :)
Alex J. Roberts


4

temukan sama sekali tidak bekerja untuk saya, bahkan setelah mengubah parameter fs ext3 seperti yang disarankan oleh pengguna di atas. Memori yang dikonsumsi terlalu banyak. Script PHP ini melakukan trik - penggunaan CPU yang cepat dan tidak signifikan, penggunaan memori yang tidak signifikan:

<?php 
$dir = '/directory/in/question';
$dh = opendir($dir)) { 
while (($file = readdir($dh)) !== false) { 
    unlink($dir . '/' . $file); 
} 
closedir($dh); 
?>

Saya memposting laporan bug mengenai masalah ini dengan menemukan: http://savannah.gnu.org/bugs/?31961


Ini menyelamatkan saya !!
jestro

3

Saya baru-baru ini menghadapi masalah yang sama dan tidak bisa mendapatkan data=writebacksaran ring0 untuk bekerja (mungkin karena fakta bahwa file-file tersebut ada di partisi utama saya). Saat mencari solusi, saya menemukan ini:

tune2fs -O ^has_journal <device>

Ini akan mematikan penjurnalan sepenuhnya, terlepas dari dataopsi yang diberikan untuk mount. Saya menggabungkan ini dengan noatimedan volume telah dir_indexdiatur, dan tampaknya bekerja dengan cukup baik. Penghapusan benar-benar selesai tanpa saya perlu membunuhnya, sistem saya tetap responsif, dan sekarang kembali aktif dan berjalan (dengan penjurnalan kembali aktif) tanpa masalah.


Saya akan menyarankan untuk menginstalnya sebagai ext2 daripada ext3, untuk menghindari penjurnalan metadata ops. Ini harus melakukan hal yang sama.
Peter Cordes

3

Pastikan Anda melakukannya:

mount -o remount,rw,noatime,nodiratime /mountpoint

yang seharusnya mempercepat sedikit juga.


4
Panggilan bagus, tapi ini sudah dipasang noatime, seperti yang saya sebutkan di header pertanyaan. Dan nodiratime berlebihan; lihat lwn.net/Articles/245002 .
BMDan

1
ppl ulangi mantra ini "noatime, nodiratime, nodevatime, noreadingdocsatime"
poige

2

Perintahnya sangat lambat. Mencoba:

find /dir_to_delete ! -iname "*.png" -type f -delete

rm -rf berlari selama satu setengah hari, dan aku akhirnya membunuhnya, tanpa pernah tahu apakah itu benar-benar mencapai sesuatu. Saya membutuhkan bilah kemajuan.
BMDan

4
Mengenai rm karena sangat lambat, "time find. -Delete" pada file 30k: 0m0.357s / 0m0.019s / 0m0.337s real / user / sys. "time (ls -1U | xargs rm -f)" pada file yang sama: 0m0.366s / 0m0.025s / 0m0.340s. Yang pada dasarnya adalah wilayah margin of error.
BMDan

1
Anda bisa saja menjalankan strace -r -p <pid of rm>untuk melampirkan ke proses rm yang sudah berjalan. Kemudian Anda dapat melihat seberapa cepat unlinkpanggilan sistem bergulir. ( -rMenempatkan waktu sejak system call sebelumnya di awal setiap baris.)
Peter Cordes

2

Sudah dir_indexdiatur untuk sistem file? ( tune2fs -l | grep dir_index) Jika tidak, aktifkan. Biasanya aktif untuk RHEL baru.


1
Ya, ini diaktifkan, tetapi saran yang luar biasa!
BMDan

2

Beberapa tahun yang lalu, saya menemukan direktori dengan 16 juta file XML di sistem /file. Karena kritikan server, kami menggunakan perintah berikut yang memakan waktu sekitar 30 jam untuk menyelesaikan:

perl -e 'for(<*>){((stat)[9]<(unlink))}'

Itu adalah hdd 7200 rpm lama , dan meskipun hambatan IO dan paku CPU, server web lama melanjutkan layanannya.


1

Opsi pilihan saya adalah pendekatan newfs, sudah disarankan. Masalah dasarnya adalah, sekali lagi seperti yang sudah dicatat, pemindaian linier untuk menangani penghapusan bermasalah.

rm -rfharus mendekati optimal untuk sistem file lokal (NFS akan berbeda). Tetapi pada jutaan file, 36 byte per nama file dan 4 per inode (tebakan, tidak memeriksa nilai untuk ext3), itu 40 * jutaan, harus disimpan dalam RAM hanya untuk direktori.

Pada tebakan, Anda meronta-ronta memori cache metadata sistem file di Linux, sehingga blok untuk satu halaman file direktori sedang dihapus sementara Anda masih menggunakan bagian lain, hanya untuk menekan halaman cache lagi ketika berikutnya file dihapus. Penyesuaian kinerja Linux bukan area saya, tetapi / proc / sys / {vm, fs} / mungkin berisi sesuatu yang relevan.

Jika Anda dapat melakukan downtime, Anda dapat mempertimbangkan untuk mengaktifkan fitur dir_index. Ini mengalihkan indeks direktori dari linear ke sesuatu yang jauh lebih optimal untuk dihapus dalam direktori besar (hashed b-tree). tune2fs -O dir_index ...diikuti oleh e2fsck -Dakan bekerja. Namun, sementara saya yakin ini akan membantu sebelum ada masalah, saya tidak tahu bagaimana kinerja konversi (e2fsck dengan -D) dilakukan ketika berhadapan dengan direktori v.large yang ada. Backup + pay-it-and-see.


1
pubbs.net/201008/squid/… menyarankan bahwa /proc/sys/fs/vfs_cache_pressuremungkin itu nilai untuk digunakan, tapi saya tidak tahu apakah direktori itu sendiri diperhitungkan terhadap cache halaman (karena memang seperti itu) atau cache inode (karena, meskipun tidak menjadi inode, itu metadata FS dan dibundel ke sana karena alasan itu). Seperti yang saya katakan, penyetelan Linux VM bukan area saya. Mainkan dan lihat apa yang bisa membantu.
Phil P

1

Jelas bukan apel untuk apel di sini, tapi saya menyiapkan sedikit tes dan melakukan yang berikut:

Menciptakan 100.000 file 512-byte dalam suatu direktori ( dddan /dev/urandomdalam satu lingkaran); lupa mengatur waktu, tetapi butuh sekitar 15 menit untuk membuat file-file itu.

Jalankan yang berikut untuk menghapus file yang dikatakan:

ls -1 | wc -l && time find . -type f -delete

100000

real    0m4.208s
user    0m0.270s
sys     0m3.930s 

Ini adalah kotak Pentium 4 2.8GHz (beberapa ratus GB IDE 7200 RPM saya pikir; EXT3). Kernel 2.6.27.


Menarik, jadi mungkin fakta bahwa file sedang dibuat dalam jangka waktu yang lama relevan? Tapi itu seharusnya tidak masalah; cache blok harus memiliki semua blok metadata terkait dalam RAM. Mungkin itu karena pembatalan tautan (2) bersifat transaksional? Menurut perkiraan Anda, apakah mematikan jurnal untuk durasi perusahaan akan menjadi solusi potensial (walaupun diakui agak berbahaya)? Sepertinya Anda tidak bisa menonaktifkan penjurnalan sepenuhnya pada sistem file yang terpasang tanpa tune2fs / fsck / reboot, yang agak mengalahkan tujuannya.
BMDan

Saya tidak dapat mengomentari itu, tetapi secara anekdot (dalam berbagai diskusi NIX selama bertahun-tahun), saya selalu mendengar bahwa rmsangat lambat pada sejumlah besar file, karenanya find -deletepilihan. Dengan wildcard pada shell, itu akan memperluas setiap nama file yang cocok, dan saya mengasumsikan ada buffer memori terbatas untuk itu, sehingga Anda bisa melihat bagaimana itu akan menjadi tidak efisien.
gravyface

1
rm akan lambat karena mencari file dengan nama, yang berarti mengulang melalui entri direktori satu per satu sampai menemukannya. Namun, dalam hal ini, karena setiap entri yang diserahkan adalah (pada titik itu) yang pertama dalam daftar (ls -U / ls -f), seharusnya hampir sama cepat. Yang mengatakan, rm -rf <dir>, yang seharusnya berjalan seperti juara, berjalan lambat. Mungkin sudah waktunya untuk menulis patch ke coreutils untuk mempercepat penghapusan besar? Mungkin diam-diam globbing / pengurutan dalam beberapa cara rekursif untuk menerapkan rm -rf? Ketidakpastian seperti inilah mengapa saya mengajukan pertanyaan. ;)
BMDan

1
Nyalakan ulang mesin setelah Anda menjalankan langkah pembuatan. Anda harus menghapus lebih lambat.
Matt

1

Terkadang Perl dapat bekerja secara ajaib dalam kasus-kasus seperti ini. Sudahkah Anda mencoba jika skrip kecil seperti ini dapat mengungguli bash dan perintah dasar shell?

#!/usr/bin/perl 
open(ANNOYINGDIR,"/path/to/your/directory");
@files = grep("/*\.png/", readdir(ANNOYINGDIR));
close(ANNOYINGDIR);

for (@files) {
    printf "Deleting %s\n",$_;
    unlink $_;
}

Atau yang lain, mungkin pendekatan Perl yang lebih cepat:

#!/usr/bin/perl
unlink(glob("/path/to/your/directory/*.png")) or die("Could not delete files, this happened: $!");

EDIT: Saya baru saja mencoba skrip Perl saya. Semakin banyak yang melakukan sesuatu dengan benar. Dalam kasus saya, saya mencoba ini dengan server virtual dengan 256 MB RAM dan setengah juta file.

time find /test/directory | xargs rm hasil:

real    2m27.631s
user    0m1.088s
sys     0m13.229s

dibandingkan dengan

time perl -e 'opendir(FOO,"./"); @files = readdir(FOO); closedir(FOO); for (@files) { unlink $_; }'

real    0m59.042s
user    0m0.888s
sys     0m18.737s

Saya ragu membayangkan apa yang akan dilakukan panggilan glob (); Saya menganggap itu scandir (). Jika demikian, itu akan membutuhkan SELAMANYA untuk kembali. Modifikasi dari saran pertama yang tidak membaca sebelumnya semua entri dir mungkin memiliki beberapa kaki; Namun, dalam bentuk saat ini, ia juga akan menggunakan jumlah CPU yang tidak suci hanya dengan membaca semua entri direktori sekaligus. Bagian dari tujuan di sini adalah untuk memecah belah dan menaklukkan; kode ini pada dasarnya tidak berbeda dari 'rm -f * .png', terlepas dari masalah dengan ekspansi shell. Jika ini membantu, tidak ada dalam direktori yang saya tidak ingin hapus.
BMDan

Saya harus mencoba lebih banyak segera setelah saya mulai bekerja. Saya hanya mencoba membuat 100.000 file dalam satu direktori dan menemukan + xargs + rm kombinasi butuh 7,3 detik, Perl + unlink (glob) ... kombinasi selesai dalam 2,7 detik. Sudah mencoba beberapa kali, hasilnya selalu sama. Di tempat kerja saya akan mencoba ini dengan lebih banyak file.
Janne Pikkarainen

Saya belajar sesuatu yang baru saat menguji ini. Setidaknya dengan ext3 dan ext4 entri direktori itu sendiri tetap besar bahkan setelah menghapus semua file dari sana. Setelah beberapa tes, direktori / tmp / test saya mengambil ruang disk 15 MB. Apakah ada cara lain untuk membersihkannya selain menghapus direktori dan membuatnya kembali?
Janne Pikkarainen

2
Tidak, Anda harus membuatnya kembali. Saya menemukan ini ketika berhadapan dengan sistem surat dan folder-per-penerima dan pembersihan setelah masalah yang signifikan: tidak ada cara, selain membuat direktori baru dan mengacak direktori tentang, kemudian nuking yang lama. Jadi Anda dapat mengurangi jendela waktu saat tidak ada direktori, tetapi tidak menghilangkannya.
Phil P

Perhatikan bahwa glob () akan mengurutkan hasilnya, sama seperti shell globbing biasanya, jadi karena Anda hanya memiliki file 100k, semuanya cocok dengan mudah dan pengurutannya cepat. Dengan direktori yang jauh lebih besar, Anda ingin opendir () / readdir () / closedir () hanya untuk menghindari penyortiran. [Saya katakan secara normal untuk shell, karena zsh memiliki pengubah glob untuk membuat urutan sortir yang tidak disortir, yang berguna ketika berhadapan dengan banyak file; *(oN)]
Phil P

1

Dari apa yang saya ingat penghapusan inode dalam sistem file ext adalah O (n ^ 2), sehingga semakin banyak file yang Anda hapus semakin cepat sisanya akan pergi.

Ada satu kali saya dihadapkan dengan masalah yang sama (meskipun perkiraan saya melihat ~ 7h waktu penghapusan), pada akhirnya pergi rute yang disarankan jftuga di komentar pertama .


0

Yah, ini bukan jawaban yang nyata, tapi ...

Apakah mungkin untuk mengubah filesystem ke ext4 dan melihat apakah ada perubahan?


Tampaknya melakukan "live" ini membutuhkan fsck pada sistem file yang terpasang, yang ... mengkhawatirkan. Punya cara yang lebih baik?
BMDan

Sistem file harus dilepas sebelum konversi, yaitu sebelum perintah tunefs yang diperlukan.
marcoc

0

Baiklah ini telah dibahas dalam berbagai cara di sisa utas tapi saya pikir saya akan membuang dua sen saya. Penyebab kinerja dalam kasus Anda mungkin readdir. Anda mendapatkan kembali daftar file yang tidak selalu berurutan pada disk yang menyebabkan akses disk di semua tempat ketika Anda memutuskan tautan. File-file tersebut cukup kecil sehingga operasi pembatalan tautan mungkin tidak melompat terlalu jauh sehingga ruang kosong. Jika Anda membaca dan kemudian mengurutkan dengan menaik inode Anda mungkin akan mendapatkan kinerja yang lebih baik. Jadi readdir menjadi ram (urutkan berdasarkan inode) -> unlink -> profit.

Inode adalah perkiraan kasar di sini saya pikir .. tetapi mendasarkan pada kasus penggunaan Anda mungkin cukup akurat ...


1
Koreksi saya jika saya salah, tetapi batalkan tautan (2) tidak nol pada inode, itu hanya menghilangkan referensi untuk itu dari direktori. Saya suka chutzpah dari pendekatan ini. Ingin menjalankan beberapa uji coba waktu dan melihat apakah itu benar?
BMDan

0

Saya mungkin akan mengeluarkan kompiler C dan melakukan moral yang setara dengan skrip Anda. Yaitu, gunakan opendir(3)untuk mendapatkan pegangan direktori, kemudian gunakan readdir(3)untuk mendapatkan nama file, lalu menghitung data ketika saya memutuskan tautan dan sesekali cetak "% d file dihapus" (dan mungkin waktu yang berlalu atau cap waktu saat ini).

Saya tidak berharap ini akan terasa lebih cepat daripada versi skrip shell, hanya saja saya terbiasa harus merobek kompiler sekarang dan lagi, baik karena tidak ada cara bersih untuk melakukan apa yang saya inginkan dari shell atau karena sementara bisa dilakukan di shell, itu lambat secara produktif seperti itu.


Dia setidaknya bisa memulai dengan memodifikasi kode sumber rm dari coreutils .
Cristian Ciupitu

0

Anda mungkin mengalami masalah penulisan ulang dengan direktori. Coba hapus file terbaru terlebih dahulu. Lihatlah opsi-opsi mount yang akan menunda writeback ke disk.

Untuk bilah kemajuan coba jalankan sesuatu seperti rm -rv /mystuff 2>&1 | pv -brtl > /dev/null


Dalam hal menghapus file terbaru terlebih dahulu: ls -Ur? Saya cukup yakin itu akan memuat entri dir, lalu membalikkannya; Saya tidak percaya ini cukup pintar untuk memulai di akhir daftar entri dir dan mundur jalan kembali ke awal. "ls -1" juga mungkin bukan ide yang bagus, karena mungkin akan membutuhkan 50+ MB inti dan beberapa menit untuk berjalan; Anda ingin "ls -U" atau "ls -f".
BMDan

Kemungkinan hanya praktis jika nama file meningkat dalam pola yang dapat diprediksi. Namun Anda saya coba ls -1 piped untuk membalikkan, dan piped ke xargs. Gunakan file daripada pipa jika Anda ingin melihat hasil antara Anda. Anda belum memberikan informasi apa pun tentang penamaan file. Anda akan menghasilkan derai terbalik dan menghapus file menggunakan pola. Anda mungkin perlu menangani entri file yang hilang. Mengingat komentar Anda tentang memori yang diperlukan, Anda memiliki ide I / O perlu menulis ulang direktori.
BillThor

0

Inilah cara saya menghapus jutaan file jejak yang kadang-kadang dapat dikumpulkan pada server database Oracle besar:

for i in /u*/app/*/diag/*/*/*/trace/*.tr? ; do rm $i; echo -n . ;  done

Saya menemukan bahwa ini menghasilkan penghapusan yang cukup lambat yang berdampak rendah pada kinerja server, biasanya sekitar sejam per satu juta file pada pengaturan 10.000 IOPS "khas".

Sering dibutuhkan beberapa menit sebelum direktori dipindai, daftar file awal dibuat dan file pertama dihapus. Dari sana dan seterusnya, a. diulang untuk setiap file yang dihapus.

Keterlambatan yang disebabkan oleh gema ke terminal telah membuktikan cukup penundaan untuk mencegah beban signifikan saat penghapusan sedang berlangsung.


Anda dimakan hidup-hidup oleh globbing. Bagaimana dengan sesuatu yang lebih seperti find /u* -maxdepth 3 -mindepth 3 -type d -path '*/app/*' -name diag -print0 | xargs -0I = find = -mindepth 4 -maxdepth 4 -type d -name 'trace' -print0 | xargs -0I = find = -mindepth 1 -maxdepth 1 -name '*.tr':? Tambahkan -deleteke yang terakhir untuk benar-benar menghapus sesuatu; seperti tertulis, itu hanya mencantumkan apa yang akan dihapus. Perhatikan bahwa ini dioptimalkan untuk keadaan di mana Anda memiliki banyak hal yang tidak menarik di direktori terdekat; jika bukan itu masalahnya, Anda bisa menyederhanakan logika banyak.
BMDan

find -delete cenderung menyebabkan terlalu banyak I / O dan mudah mempengaruhi kinerja produksi. Mungkin dengan ionice.
Roy

Itu menyebabkan semua I / O hanya dengan menjadi lebih efisien! Globbing adalah semua front-load untuk contoh Anda (yaitu, daftar lengkap file dihasilkan sebelum yang pertama rmterjadi), sehingga Anda memiliki I / O yang relatif efisien pada saat startup, diikuti oleh s, menyakitkan out-of-order rm. yang mungkin tidak menyebabkan banyak I / O, tetapi melibatkan scandirberjalan direktori berulang kali (tidak menyebabkan I / O karena itu sudah dimuat ke dalam blok cache; lihat juga vfs_cache_pressure). Jika Anda ingin memperlambat, ioniceini merupakan pilihan, tetapi saya mungkin akan menggunakan fraksional-detik sleep.
BMDan

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +akan menjalankan satu rmper direktori, jadi Anda akan memiliki lebih sedikit overhead CPU. Selama Anda memiliki banyak waktu CPU untuk cadangan, pelambatan disk IO dengan mengacaukan seluruh rmproses untuk setiap unlinkpekerjaan, saya kira, tapi itu jelek. perl dengan sleep per unlink akan lebih baik jika tidur di antara rmseluruh direktori pada satu waktu terlalu bursty. ( -execdir sh -c ...mungkin)
Peter Cordes

-1

Anda dapat menggunakan fitur paralelisasi 'xargs':

ls -1|xargs -P nb_concurrent_jobs -n nb_files_by_job rm -rf

1
Ini tidak akan membantu. Hambatannya adalah I / O acak yang buruk pada drive. Melakukan penghapusan paralel dapat membuatnya lebih buruk dan hanya menambah beban CPU.
Wim Kerkhoff

-2
ls|cut -c -4|sort|uniq|awk '{ print "rm -rf " $1 }' | sh -x

1
Wow. Saya kira itu jatuh cukup kuat di kamp "lebih dari satu cara untuk menguliti kucing". Serius, dengan jenis dan uniq? "ls" secara default, bagaimanapun, dan saya yakin berharap nama file itu unik. : /
BMDan

-2

sebenarnya, yang ini sedikit lebih baik jika shell yang Anda gunakan melakukan ekspansi baris perintah:

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.