Soru milyonlarca dosya içeren bir dizinde rm


Arka plan: fiziksel sunucu, yaklaşık iki yaşında, bir 3Ware RAID kartına bağlı 7200-RPM SATA sürücüleri, ext3 FS takılı birime ve veriye = sipariş, çılgın yük altında değil, çekirdek 2.6.18-92.1.22.el5, uptime 545 gün . Dizin, herhangi bir alt dizin içermez, sadece birkaç milyon küçük dosya (sadece birkaç KB) olan milyonlarca küçük (~ 100 bayt) dosya içerir.

Son birkaç ay boyunca biraz guguklu bir sunucuya sahibiz, ancak sadece çok fazla dosya içerdiğinden dolayı bir dizine yazamaya başladığında geçen gün farkettik. Özellikle, bu hatayı / var / log / messages içine atmaya başladı:

ext3_dx_add_entry: Directory index full!

Söz konusu diskte kalan çok sayıda düğüm var:

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

Yani tahmin ediyorum ki, dizin dosyasında kaç girişin olabileceğini sınırladık. Kaç dosya olacağına dair bir fikriniz yok, ama gördüğünüz gibi üç milyondan fazla olamaz. Bu iyi değil, sorun değil! Ama bu benim sorumdan bir tanesi: tam olarak bu üst sınır nedir? Bu ayarlanabilir mi? Bağırmadan önce - onu ayarlamak istiyorum aşağı; Bu muazzam dizin her türlü soruna neden oldu.

Her halükarda, bu dosyaların tamamını oluşturan kodu izledik ve onu düzelttik. Şimdi dizini silerek takılıyorum.

Burada birkaç seçenek:

  1. rm -rf (dir)

Önce bunu denedim. Herhangi bir fark yaratmadan bir buçuk gün koştuktan sonra pes ettim ve öldürdüm.

  • dizinde bağlantıyı kes (2): Kesinlikle düşünmeye değer, ama soru, klasörün bağlantısını fsck yoluyla silmek için daha hızlı olmanın, bağlantının kesilmesi (2) üzerinden silmek için daha mı olacağıdır. Yani, bir şekilde ya da böyle, bu inode'ları kullanılmamış olarak işaretlemeliyim. Bu, elbette, fsck'in / lost + bulunan dosyalara girdilerinin girmemesini söyleyebileceğimi varsayar; aksi halde, sorunumu yeni taşıdım. Diğer tüm endişelere ek olarak, biraz daha fazla okuduktan sonra, bazı içsel FS işlevlerini çağırmak zorunda kalacağım, çünkü bulabileceğim bağlantısız (2) varyantların hiçbiri sadece tam olarak silmeye izin vermez girişleri olan bir dizin. Pooh.
  • while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )
  • Bu aslında kısaltılmış versiyonu; Çalıştığım gerçek, sadece bir kaç ilerleme raporlaması ve silmek için dosyalar bittiğinde temiz bir durur:

    ihracat i = 0;
    zaman (iken [true];
      ls -Uf | kafa -n 3 | grep -qF '.png' || break;
      ls -Uf | kafa -n 10000 | xargs rm -f 2> / dev / null;
      ihracat i = $ (($ i + 10000));
      echo "$ i ...";
    bitti)

    Bu oldukça iyi çalışıyor gibi görünüyor. Bunu yazarken, son otuz dakika içinde 260.000 dosyayı sildi.


    97
    2017-09-22 23:57


    Menşei


    rm (GNU coreutils) 8.4 bu seçeneğe sahiptir: "-v, --verbose ne yapıldığını açıklıyor". Silmekte olan tüm dosyaları gösterecektir. - Cristian Ciupitu
    Aslında, bu ilerleme çubuğu yapmak için düzgün bir yol olurdu: her dosya otuz yedi karakter uzunluğunda (36 + a '\ n') olduğu için, bunun için bir ayrıştırıcı yazabilirim ve printf () ucuz ve rm komutu zaten yüklenen dosyanın ismine sahip, özel bir performans cezası yok. Her ne kadar böyle bir şey yapmak için hiçbir zaman "rm" elde edemediğimden, bütün shebang'ı yapmak için bir başlangıççı gibi görünüyor. Ancak, 10.000'inde bir ilerleme çubuğu olarak oldukça işe yarayabilir; belki bir "." her yüz dosya için? - BMDan
    rm -rfv | pv -l >/dev/null. pv mevcut olmalıdır Epel deposu. - Cristian Ciupitu
    pv ezici bir şekilde harika. Uyandığımda pv kurulumlarının izini bırakıyorum. - BMDan
    Son zamanlarda bu aynı sorunu vardı. Teşekkür ederim! - richo


    Cevaplar:


    data=writeback dosya sistemi kayıtlarının önlenmesi için mount seçeneği denenmeyi hak ediyor. Bu sadece silme sırasında yapılmalıdır, ancak sunucu kapatılırken veya silme işlemi sırasında yeniden başlatılıyorsa risk vardır.

    Göre bu sayfa,

    Bazı uygulamalar, kullanıldığında çok önemli bir hız artışı gösterir. Örneğin, uygulamalar büyük hacimli küçük dosyalar oluşturup sildiğinde hız iyileştirmeleri görülebilir (...).

    Bu seçenek ya ayarlanır fstab veya montaj işlemi sırasında değiştirilir data=ordered ile data=writeback. Silinecek dosyaları içeren dosya sistemi yeniden yapılmalıdır.


    30
    2017-09-26 05:49



    O zamandan commit  seçenek: "Bu varsayılan değer (veya herhangi bir düşük değer) performansa zarar verir, ancak veri güvenliği için iyidir. 0'a ayarlanması, varsayılan değerde (5 saniye) aynı etkiyi bırakır. Çok büyük değerlere ayarlanması performans geliştirme". - Cristian Ciupitu
    Writeback, baktığım belgeler hariç, yıldız gibi görünüyor (gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4) açıkça değiştirdiğim tüm verileri içerdiğini düşündüğüm meta verileri (hala dosyalarda herhangi bir veriyi değiştirmem) olduğunu açıkça belirtiyor. Bu seçenek yanlış anlaşılıyor mu? - BMDan
    Son olarak, bu bağlantıda belirtilmeyen FYI, veri = yazıcının büyük bir güvenlik deliği olabileceği gerçeğidir, çünkü verili bir giriş tarafından işaret edilen veri, uygulama tarafından orada yazılan verilere sahip olmayabilir, yani bir çarpışmanın sonuçlanabileceği anlamına gelir. eski, muhtemelen duyarlı / özel verilerin açığa çıkması. Burada bir endişe yok, sadece geçici olarak açtığımız için, ya da bu öneriye rastlayan başkalarının farkında olmasaydınız, herkesi bu uyarıya karşı uyarmak istedim. - BMDan
    taahhüt: Bu oldukça kaygan! İşaretçi için teşekkürler. - BMDan
    data=writeback hala ana dosya sistemine yazmadan önce meta verileri yayınlar. Anladığım kadarıyla, bir haritayı yazmak ve bu kapsamlara veri yazmak gibi şeyler arasında sıralamayı zorlamıyor. Belki de, bundan daha iyi bir kazanç elde ederseniz, rahatladığı başka sipariş kısıtlamaları vardır. Tabii ki, dergi olmadan montaj, daha da yüksek performans olabilir. (Meta veri değişikliklerinin, RAM bağlantısızlığı tamamlanmadan önce diskte herhangi bir şey yapmanıza gerek kalmadan RAM'de gerçekleşmesine izin verebilir). - Peter Cordes


    Bu sorunun büyük bir nedeni milyonlarca dosya ile ext3 performans olsa da, bu sorunun asıl nedeni farklıdır.

    Bir dizinin listelenmesi gerektiğinde, bir dosya listesi veren dizinde readdir () çağrılır. readdir bir posix çağrısıdır, ancak burada kullanılan gerçek Linux sistem çağrısına 'getdents' denir. Getdents, girişlerle bir arabellek doldurarak dizin girişlerini listeler.

    Sorun, aslında readdir () dosyaları almak için 32 KB'lık bir sabit arabellek boyutunu kullanması gerçeğidir. Bir dizin daha büyük ve daha büyük hale geldikçe (dosyalar eklendikçe boyut artar) ext3, girişleri almak için daha yavaş ve daha yavaş olur ve ek readdir'ün 32Kb arabellek boyutu, yalnızca dizindeki girdilerin bir kısmını dahil etmek için yeterlidir. Bu, tekrar tekrar devreye girmeye neden olur ve pahalı sistem çağrısını tekrar tekrar başlatır.

    Örneğin, içinde 2.6 milyondan fazla dosyayla oluşturduğum bir test dizininde "ls -1 | wc-l" ifadesi, birçok getdent sistem çağrısının büyük bir strace çıktısını gösterir.

    $ 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
    ...
    

    Ek olarak bu dizinde geçirilen zaman anlamlıydı.

    $ time ls -1 | wc -l
    2616044
    
    real    0m20.609s
    user    0m16.241s
    sys 0m3.639s
    

    Bunu daha verimli bir süreç haline getirme yöntemi, getdentleri daha büyük bir tamponla manuel olarak çağırmaktır. Bu, performansı önemli ölçüde artırır.

    Şimdi, kendiniz için mandalları kendiniz aramak zorunda değilsiniz, böylece normal olarak kullanmak için herhangi bir arabirim mevcut değildir (görmek için getdents için man sayfasını kontrol edin). kutu manuel olarak çağırın ve sistem çağrınızı çağırma yöntemini daha verimli hale getirin.

    Bu, bu dosyaları almak için gereken süreyi önemli ölçüde azaltır. Bunu yapan bir program yazdım.

    /* 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);
    }
    

    Bu, temeldeki temel problemle (pek çok dosyada, kötü bir şekilde gerçekleştiren bir dosya sisteminde) mücadele etmez. Gönderilen alternatiflerin çoğundan çok daha hızlı olmalı.

    Bir öngörü olarak, etkilenen dizini kaldırmalı ve sonra yeniden uyarlamalıdır. Dizinler yalnızca boyut olarak artar ve dizinin büyüklüğüne bağlı olarak birkaç dosyada bile yetersiz performans gösterebilir.

    Düzenle: Bunu biraz temizledim. Komut satırında çalışma zamanında silmenize izin veren bir seçenek ekledik ve dürüstçe geriye dönük olarak en iyi şekilde sorgulanabilen bir grup treewalk parçasını kaldırdık. Ayrıca hafıza bozulmasına neden olduğu ortaya çıktı.

    Şimdi yapabilirsin dentls --delete /my/path

    Yeni sonuçlar. 1.82 milyon dosya içeren bir dizinden yoksun.

    ## 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
    

    Bu tür hala çok iyi şaşırdı!


    73
    2017-11-06 19:06



    İki küçük endişe: bir, [256] Muhtemelen olmalı [FILENAME_MAX]ve iki, Linux'um (2.6.18 == CentOS 5.x) bir d_type girişini (en azından getdents (2) 'ye göre) dir_değiştir. - BMDan
    Btree yeniden dengeleme konusunda biraz detaylandırır mısınız ve neden silmeyi engellemek yardımcı olur? Bunun için Googling'i denedim, ne yazık ki boşuna. - ovgolovin
    Çünkü şimdi, eğer siparişi siliyorsak, bir taraftaki yaprakları bir kenara bırakıp diğeri üzerinde bıraktığımız için yeniden dengelenmeyi zorluyoruz: en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion - ovgolovin
    Umarım bu konularda seni rahatsız etmem. Ancak yine de, siparişteki dosyaları silmeyle ilgili bir soru sordum stackoverflow.com/q/17955459/862380Bu, sıradan programcılar için anlaşılabilir bir örnek olan konuyu açıklayacak bir cevap almıyor gibi görünüyor. Zamanınız varsa ve böyle hissediyorsanız, ona bakabilir misiniz? Belki daha iyi bir açıklama yazabilirsin. - ovgolovin
    Bu harika bir kod parçası. Muhtemelen birkaç yıl içinde bir dizinde oluşturulan 11.000.000 (on bir milyon) oturum dosyasını listeleyebildiğim ve silebildiğim tek araç buydu. Buradaki diğer cevaplardaki bul ve diğer hileleri kullanarak onları kontrol altında tutabilen Plesk süreci, bir koşuyu tamamlayamadı, bu yüzden dosyalar yeni oluşturuldu. Dosya sisteminin dizini saklamak için kullandığı ikili ağacın bir hatırasıdır, oturumların hiç çalışamadığı - bir dosya oluşturabilir ve gecikme olmadan geri alabilirsiniz. Sadece listeleri kullanılamaz. - Jason


    Diğer dosyaların tümünü bu dosya sisteminden geçici bir depolama konumuna yedeklemek, bölümü yeniden biçimlendirmek ve sonra dosyaları geri yüklemek mümkün olabilir mi?


    31
    2017-09-23 00:27



    Aslında bu cevabı gerçekten çok beğeniyorum. Pratik bir mesele olarak, bu durumda, hayır, ama düşündüğüm bir tane değil. Bravo! - BMDan
    Tam olarak ne düşündüğümü de. Bu soru 3 için bir cevaptır. Bana sorarsan ideal :) - Joshua


    Ext3'te sadece dosya sistemi inode limitinde bir dizin dosyası sınırı yoktur (sanırım alt dizinlerin sayısı üzerinde bir sınır olduğunu düşünüyorum).

    Dosyaları kaldırdıktan sonra hala sorunlarınız olabilir.

    Bir dizinde milyonlarca dosya varsa, dizin girişi çok büyük olur. Her giriş işlemi için dizin girişi taranmalıdır ve bu, girişin bulunduğu yere bağlı olarak her dosya için farklı süreler alır. Ne yazık ki tüm dosyalar kaldırıldıktan sonra bile dizin girişi büyüklüğünü korur. Dizin şimdi boş olsa bile, dizin girişini taramak isteyen diğer işlemler hala uzun bir zaman alacaktır. Bu sorunu çözmenin tek yolu, dizini yeniden adlandırmak, eski adıyla yeni bir tane oluşturmak ve kalan dosyaları yeni olana aktarmaktır. Sonra yeniden adlandırılmış olanı silin.


    11
    2017-09-23 05:45



    Gerçekten de, her şeyi sildikten sonra bu davranışı fark ettim. Neyse ki, dizini "ateş hattı" ndan çoktan çıkarmıştık, o yüzden öyle bir şey yapabildim. - BMDan
    Diyelim ki, dizin başına dosya sınırı yoksa, neden "ext3_dx_add_entry: Dizin dizini dolu!" Bu bölümde hala inode'lar mevcut olduğunda? Bu dizinde alt dizin bulunamadı. - BMDan
    Biraz daha fazla araştırma yaptım ve bir dizinin alabileceği blok sayısının bir sınırı var gibi görünüyor. Tam dosya sayısı, dosya adı uzunluğu gibi birkaç şeye bağlıdır. Bu gossamer-threads.com/lists/linux/kernel/921942 4k blokları ile bir dizinde 8 milyondan fazla dosyaya sahip olabileceğinizi belirtmektedir. Özellikle uzun dosya adları mıydı? - Alex J. Roberts
    Her dosya adı tam olarak 36 karakter uzunluğundaydı. - BMDan
    iyi bu benim fikirlerim :) - Alex J. Roberts


    Karşılaştırmadım ama bu adam yaptı:

    rsync -a --delete ./emptyDirectoty/ ./hugeDirectory/
    

    5
    2018-06-04 11:52





    ext3 fs parametrelerini yukarıdaki kullanıcı tarafından önerilen şekilde değiştirdikten sonra bile, benim için işe yaramadı. Tüketilen yol çok fazla bellek. Bu PHP betiği hile yaptı - hızlı, önemsiz CPU kullanımı, önemsiz bellek kullanımı:

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

    Bu sorunla ilgili bir hata raporu gönderdim: http://savannah.gnu.org/bugs/?31961


    4
    2017-12-23 19:54



    Bu beni kurtardı! - jestro


    Geçenlerde benzer bir sorunla karşılaştım ve ring0'ları alamadım. data=writeback çalışma önerisi (muhtemelen dosyaların ana bölümümde olması gerçeğinden dolayı). Geçici çözümler araştırırken bunlara rastladım:

    tune2fs -O ^has_journal <device>
    

    Bu, kayıtsız olarak, günlük kaydını tamamen kapatacaktır. data seçenek vermek mount. Bunu ile birleştirdim noatime ve hacim vardı dir_index set ve oldukça iyi görünüyordu. Silme, gerçekten onu öldürmeye gerek duymadan bitirdi, sistemim yanıt verdi, ve şimdi, herhangi bir sorun olmadan yedekleniyor ve çalışıyor.


    3
    2018-04-23 22:29



    Meta veri ops'larının kaydedilmesini önlemek için ext3 yerine ext2 olarak takmayı öneririm. Bu aynı şeyi yapmalı. - Peter Cordes