milyonlarca dosya içeren bir dizindeki rm


104

Arka plan: Fiziksel sunucu, yaklaşık iki yaşında, 3Ware RAID karta bağlı 7200 RPM SATA sürücüler, ext3 FS'ye monte edilmiş bir zaman dilimi ve veri = sipariş edildi, çılgın yük altında değil, çekirdek 2.6.18-92.1.22.el5, çalışma süresi 545 gün . Dizin, daha büyük (birkaç KB) dosya içeren milyonlarca küçük (~ 100 byte) dosya içeren herhangi bir alt dizin içermez.

Son birkaç ay boyunca biraz guguklandıran bir sunucumuz var, ancak geçen gün sadece çok fazla dosya içerdiği için bir dizine yazamaya başladığını fark ettik. Ö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% /

Dolayısıyla, bunun dizin dosyasında kaç giriş olabileceğinin sınırına ulaştığımız anlamına geldiğini tahmin ediyorum. Ne kadar dosya olacağı hakkında hiçbir fikrim yok, ancak görebileceğiniz gibi, üç milyondan fazla olamaz. Bu iyi değil, dikkat et! Ama bu sorumun bir parçası: tam olarak bu üst sınır nedir? Ayarlanabilir mi? Ben bağırdı önce at-ı ayar bunu istiyorum aşağı ; Bu devasa dizin her türlü soruna yol açtı.

Neyse, tüm bu dosyaları oluşturan koddaki sorunu tespit ettik ve düzelttik. Şimdi dizini silmeme şaşırdım.

Burada birkaç seçenek:

  1. rm -rf (dir)

    Bunu önce denedim. Kesin bir etkisi olmadan bir buçuk gün çalıştıktan sonra pes ettim ve onu öldürdüm.

  2. Dizinde unlink (2): Kesinlikle dikkate değer, ancak soru, fsck ile dizindeki dosyaları silmenin unlink (2) ile silmekten daha hızlı olup olmayacağıdır. Yani, öyle ya da böyle, bu düğümleri kullanılmamış olarak işaretlemeliyim. Bu, elbette fsck’e girişleri / kayıp + bulunan dosyalara bırakmamasını söyleyebilir; Aksi takdirde, sorunumu yeni çözdüm. Diğer tüm kaygılara ek olarak, bu konuyu biraz daha okuduktan sonra, bulabileceğim unlink (2) türevlerinin hiçbiri sadece blithly silmeme izin vermeyeceğinden muhtemelen bazı iç FS işlevlerini çağırmak zorunda kalacağım ortaya çıktı. girişleri olan bir dizin. Pooh.
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    Bu aslında kısaltılmış versiyonu; Çalıştırmakta olduğum gerçek, sadece biraz ilerleme bildiren ve silmek için dosyalarımız bittiğinde temiz bir durma sağlayan:

    ihracat i = 0;
    zaman (iken [gerçek]; yap
      ls -Uf | kafa -n 3 | grep -qF '.png' || break;
      ls -Uf | kafa -n 10000 | xargs rm -f2> / dev / null;
      dışa aktarma 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ı silmiş.

Şimdi, sorular için:
  1. Yukarıda belirtildiği gibi, dizin başına giriş sınırı ayarlanabilir mi?
  2. Listenin başında gelen ilk dosya olan tek bir dosyayı silmek neden "gerçek 7m9.561s / kullanıcı 0m0.001s / sys 0m0.001s" ls -Ualdı ve ilk 10.000 girişi silmek Komuta # 3'te, ama şimdi oldukça mutlu bir şekilde hareket ediyor mu? Bu nedenle, yaklaşık otuz dakikada 260.000 sildi, ancak şimdi 60.000 daha fazla silmek için on beş dakika daha sürdü. Neden dev hızda sallanıyor?
  3. Böyle bir şey yapmanın daha iyi bir yolu var mı? Bir dizinde milyonlarca dosya saklamayın; Aptalca olduğunu biliyorum ve saatimde olmazdı. Sorunun googling edilmesi ve SF ve SO'lara findbakılması, bazı belirgin nedenlerden dolayı benim yaklaşımımdan önemli ölçüde daha hızlı olmayacak birçok değişiklik sunar . Ancak fsck yoluyla silme fikrinin herhangi bir ayağı var mı? Ya da tamamen başka bir şey? Kutudan çıkma (ya da tanınmayan kutunun içi) düşüncesini duymak için sabırsızlanıyorum.
Küçük romanı okuduğunuz için teşekkürler; soru sormakta özgürsünüz ve cevap vereceğimden emin olacağım. Ayrıca, soruyu son dosya sayısıyla ve sildiğimde silmek için ne kadar süre çalıştığını da güncelleştireceğim.

Son senaryo çıktısı !:

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

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

Böylece, üç milyon dosya dört saatten biraz daha fazla bir sürede silindi.


1
rm (GNU coreutils) 8.4 bu seçeneğe sahiptir: "-v, --verbose ne yapıldığını açıkla" . Silinen tüm dosyaları gösterecektir.
Cristian Ciupitu

2
Aslında, ilerleme çubuğunu yapmanın güzel bir yolu olurdu: her dosya otuz yedi karakter uzunluğunda olacağı için (36 + a '\ n'), bunun için kolayca bir çözümleyici yazabilirim ve printf () ucuz ve rm komutu zaten yüklenen dosyanın ismine sahip, özel bir performans cezası yok. Zaten böyle bir şey yapmak için asla “rm” alamadığımdan, bütün shebang'ı yapmak için başlangıçsız gibi görünüyor. Ancak 10.000'lik bir ilerleme çubuğu kadar iyi çalışabilir; belki bir "." her yüz dosya için mi?
BMDan

8
rm -rfv | pv -l >/dev/null. pv, EPEL deposunda mevcut olmalıdır .
Cristian Ciupitu

5
pv ezici bir şekilde harika. Ardından pv kurulumları izini bırakıyorum.
BMDan

Son zamanlarda aynı sorunu yaşadım. Teşekkür ederim!
richo

Yanıtlar:


30

data=writebackMontaj opsiyonu dosya sisteminin günlük kaydını engellemek için, denenmiş hak ediyor. Bu sadece silme süresi içerisinde yapılmalıdır, silme işlemi sırasında sunucunun kapatılması veya yeniden başlatılması durumunda bir risk vardır.

Göre bu sayfada ,

Bazı uygulamalar kullanıldığında çok önemli bir hız artışı göstermektedir. Örneğin, uygulamalar büyük hacimlerde küçük dosyalar oluştururken ve silerken hız iyileştirmeleri görülebilir (...).

Seçeneği ya ayarlanır fstabdeğiştirme veya bağlama işlemi sırasında data=orderedile data=writeback. Silinecek dosyaları içeren dosya sistemi yeniden kurulmalıdır.


1
Aynı zamanda şu commit seçeneğe göre zamanı da artırabilir : "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 ayarlamak, varsayılan ayarda bırakmakla aynı etkiye sahiptir (5 saniye) Çok büyük değerlere ayarlamak performansı artıracak ".
Cristian Ciupitu

1
Writeback, baktığım belgeler dışında, yıldızlara benziyor ( gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4 ), açıkça belirttiğim tüm verileri içerdiğini düşündüğüm meta verilerden hala bahsettiğini söylüyor. değiştiriliyor (kesinlikle dosyaların kendi içindeki verileri değiştirmiyorum). Bu seçeneği anlamam yanlış mı?
BMDan

Son olarak, bu bağlantıda bahsedilmeyen FYI, verinin = geri yazma işleminin çok büyük bir güvenlik açığı olabileceği gerçeğidir; çünkü belirli bir girişin gösterdiği veriler, uygulamanın orada yazdığı verilere sahip olmayabilir; Eski, muhtemelen hassas / özel veriler açığa çıkıyor. Buradaki bir endişe değil, yalnızca geçici olarak başlattığımız için, ama siz veya o öneriyle karşılaşan diğer kişilerin farkında olmaması durumunda herkesi bu uyarıya karşı uyarmak istedim.
BMDan

taahhüt: bu oldukça kaygan! İşaretçi için teşekkürler.
BMDan

2
data=writebackHala ana dosya sistemine yazmadan önce meta verileri yayınlar. Anladığım kadarıyla, bir boyut haritası yazmak ve bu alanlara veri yazmak gibi şeyler arasında sıralamayı zorlamaz. Belki bundan daha iyi bir kazanç elde ettiyseniz, onun da rahatlattığı başka sipariş kısıtlamaları da var. Tabii ki, hiç dergi olmadan montaj bile daha yüksek performans olabilir. (Bağlantı kaldırma işlemi tamamlanmadan önce diskte bir şey olmasına gerek kalmadan meta veri değişikliklerinin RAM'de gerçekleşmesine izin verebilir).
Peter Cordes

80

Bu sorunun asıl nedeni milyonlarca dosyayla ext3 performansı olsa da, bu sorunun asıl kök nedeni farklıdır.

Bir dizinin listelenmesi gerektiğinde, dosyaların listesini 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ı 'getdents' olarak adlandırılır. Getdents dizinleri girişleri arabellekleri girişlerle doldurarak listeler.

Sorun temel olarak, readdir () öğesinin dosyaları almak için 32Kb sabit arabellek boyutu kullanmasından kaynaklanmaktadır. Bir dizin büyüdükçe ve büyüdükçe (dosyalar eklendikçe boyut artar) ext3 girişleri almak için yavaşlar ve yavaşlar ve ek readdir'in 32Kb arabellek boyutu yalnızca dizine girdilerin bir kısmını eklemek için yeterlidir. Bu, readdir'in tekrar tekrar dönmesine ve tekrar tekrar pahalı sistem çağrısını çağırmasına neden olur.

Örneğin, içinde 2.6 milyondan fazla dosyayla oluşturduğum bir test dizininde, "ls -1 | wc-l" nin çalıştırılması birçok getdent sistem çağrısının büyük bir çekici çı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 harcanan zaman önemliydi.

$ time ls -1 | wc -l
2616044

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

Bunu daha verimli bir işlem haline getirmenin yöntemi getdents'i daha büyük bir tamponla manuel olarak çağırmaktır. Bu, performansı önemli ölçüde artırır.

Şimdi, kendinizi elle hiçbir arayüzü normal kullanmak için var getdents aramak gerekmiyor (görmek getdents man sayfasına bakın!), Ancak yapabilirsiniz elle arayıp sistem çağrısı çağırma yol daha verimli hale getirir.

Bu, bu dosyaların alınması için gereken süreyi büyük ö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, temel problemle (kötü performans gösteren bir dosya sisteminde çok sayıda dosyayla) savaşmaz. Gönderilecek alternatiflerin çoğundan çok, çok daha hızlı olması muhtemeldir.

Öngörü olarak, kişi etkilenen dizini silmeli ve sonra yeniden düzenlemelidir. Dizinler yalnızca boyut olarak çoğalır ve dizinin boyutu nedeniyle içinde bulunan birkaç dosyada bile düşük performans göstermeye devam eder.

Düzenleme: Bu biraz temizledik. Çalışma zamanında komut satırından silmenize izin veren bir seçenek eklendi ve dürüstçe geriye bakıldığında en iyi şekilde sorgulanabilecek bir grup treewalk maddesini kaldırın. Ayrıca hafıza bozulmasına yol açtığı da gösterildi.

Şimdi yapabilirsin dentls --delete /my/path

Yeni sonuçlar 1,82 milyon dosya içeren bir dizinden alınmıştır.

## 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 hala çok iyi çalıştığını şaşırttı!


1
İki küçük kaygı: biri, [256]muhtemelen olmalı [FILENAME_MAX]ve ikisi, benim Linux'um (2.6.18 == CentOS 5.x) dirent'te bir d_type girişi içermiyor gibi görünüyor (en azından alıcılara göre (2)).
BMDan

1
Lütfen btree yeniden dengelemeyi biraz açıklayabilir misiniz ve neden silme işleminin engellenmesine yardımcı olabilirsiniz? Bunun için Googling denedim, ne yazık ki boşuna.
ovgolovin

1
Çünkü şimdi sıralı olarak silmemiz bana göre görünüyor, bir taraftaki yaprakları çıkarıp diğer tarafa bıraktıkça yeniden dengelemeye
zorlanıyoruz

1
Umarım bu meselelerle sizi rahatsız etmem. Ancak yine de sıralı programcılar için anlaşılabilir olacak olan örnekle konuyu açıklayacak bir yanıt almamış gibi görünen sıradaki stackoverflow.com/q/17955459/862380 dosyalarının silinmesiyle ilgili bir soru başlattım . Vaktiniz varsa ve böyle hissederseniz, içine bakar mısınız? Belki daha iyi bir açıklama yazabilirsin.
ovgolovin

2
Bu harika bir kod parçası. Dizinde oluşturulmuş 11.000.000 (on bir milyon) oturum dosyasını, muhtemelen birkaç yıldan fazla bir süredir listeleyip silebilecek tek araç buydu. Buradaki diğer cevaplarda bul ve diğer püf noktaları kullanılarak kontrol altında tutulması gereken Plesk işlemi, bir işlemi tamamlayamadı, bu yüzden dosyalar daha yeni oluşturulmaya devam etti. Dosya sisteminin dizini depolamak için kullandığı, oturumların çalışabildiği bir ikili ağacıdır - bir dosya oluşturabilir ve gecikmeden geri alabilirsiniz. Sadece listeler kullanılamazdı.
Jason,

31

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


3
Aslında bu cevabı çok beğendim. Pratik bir mesele olarak, bu durumda, hayır, ama düşündüğümden değil. Bravo!
BMDan

Tam olarak ne düşünüyordum. Bu, 3. soruya verilen bir cevaptır. Bana sorarsanız ideal :)
Joshua

12

Sadece dosya sistemi inode limitinin ext3'te dizin başına dosya sınırı yoktur (sanırım alt dizinlerin sayısında bir sınır olduğunu düşünüyorum).

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

Bir dizin milyonlarca dosyaya sahip olduğunda, dizin girişinin kendisi çok büyük olur. Dizin girişi her kaldırma işlemi için taranmalıdır ve girişin bulunduğu yere bağlı olarak her dosya için çeşitli süreler gerekir. Ne yazık ki, tüm dosyalar kaldırıldıktan sonra bile, dizin girişi boyutunu korur. Bu nedenle, dizin girişinin taranmasını gerektiren diğer işlemler, dizin boş olsa bile hala uzun zaman alacaktır. Bu sorunu çözmenin tek yolu dizini yeniden adlandırmak, eski adıyla yeni bir tane oluşturmak ve kalan dosyaları yenisine aktarmaktır. Sonra yeniden adlandırılmış olanı silin.


Aslında, sadece bu davranışı her şeyi sildikten sonra farkettim. Neyse ki, diziyi zaten “ateş hattının” dışına çıkarmıştık, böylece yeniden düzenleyebildim.
BMDan

2
Bu, eğer dizin başına dosya limiti yoksa, neden "ext3_dx_add_entry: Directory index dolu!" Dedim. Bu bölüm üzerinde hala inode bulunduğunda? Bu dizinde alt dizin bulunmuyor.
BMDan

3
hmm biraz daha araştırma yaptım ve bir dizinin alabileceği blokların bir sınırı varmış gibi görünüyor. Tam dosya sayısı birkaç şeye bağlıdır, örneğin dosya adı uzunluğu. Bu gossamer-threads.com/lists/linux/kernel/921942 , 4k blokları ile bir dizinde 8 milyondan fazla dosyaya sahip olmanız gerektiğini gösteriyor. Özellikle uzun dosya isimleri miydi?
Alex J. Roberts

Her dosya adı tam olarak 36 karakter uzunluğundaydı.
BMDan

iyi fikir bu benim :)
Alex J. Roberts


4

ext3 fs'in parametrelerini yukarıdaki kullanıcılar tarafından önerilen şekilde değiştirdikten sonra bile, bul, benim için işe yaramadı. Tüketilen yol çok fazla hafıza. Bu PHP betiği hile - 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 buldum: http://savannah.gnu.org/bugs/?31961


Bu beni kurtardı!
jestro

3

Geçenlerde benzer bir sorunla karşı karşıya kaldım ve ring0’in data=writebackçalışma önerisini alamadım (muhtemelen dosyalar benim ana bölümümde olduğu için). Geçici çözümler araştırırken bunun üzerine düştüm:

tune2fs -O ^has_journal <device>

Bu, dataseçeneğin ne olduğuna bakılmaksızın günlük kaydını tamamen kapatır mount. Bunu birleştirdim noatimeve ses dir_indexayarlanmış ve oldukça iyi çalışıyor gibiydi. Silme aslında onu öldürme gereği duymadan bitti, sistemim yanıt vermeye devam etti ve şimdi sorunsuz bir şekilde çalışmaya devam ediyor.


Meta veri işlemlerinin günlüğe kaydedilmesini önlemek için ext3 yerine ext2 olarak yerleştirmeyi önerecektim. Bu aynı şeyi yapmalı.
Peter Cordes

3

Yaptığınızdan emin olun:

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

Bu da işleri biraz hızlandırmalı.


4
İyi çağrı, ama zaten soruya başlıkta bahsettiğim gibi, zaten monte edilmiş. Ve nodiratime gereksizdir; bkz. lwn.net/Articles/245002 .
BMDan

1
ppl bu mantrayı tekrar tekrar "noatime, nodiratime, nodevatime, noreadingdocsatime"
poige

2

Çok yavaş bir emir. Deneyin:

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

rm -rf bir buçuk gün boyunca koştu ve sonunda bir şey yapıp yapmadığını bilmeden nihayet öldürdüm. Bir ilerleme çubuğuna ihtiyacım vardı.
BMDan

4
Rm çok yavaş olduğu için, 30k dosyalarında "time find. -Delete": 0m0.357s / 0m0.019s / 0m0.337s gerçek / kullanıcı / sys. "aynı zamanda (ls -1U | xargs rm -f)" aynı dosyalarda: 0m0.366s / 0m0.025s / 0m0.340s. Temelde hata payı sınırı.
BMDan

1
strace -r -p <pid of rm>Zaten çalışan rm işlemine eklemek için sadece koşabilirsiniz . Ardından, unlinksistem aramalarının ne kadar hızlı geçtiğini görebilirsiniz . ( -rönceki sistemin çağrılmasından bu yana, her hattın başlangıcındaki zamanı koyar.)
Peter Cordes

2

dir_indexDosya sistemi için ayarlandı mı ? ( tune2fs -l | grep dir_index) Değilse, etkinleştirin. Genellikle yeni RHEL için açık.


1
Evet, etkin, ancak harika bir öneri!
BMDan

2

Birkaç yıl önce, dosya sisteminde 16 milyon XML dosyası bulunan bir dizin buldum /. Sunucunun eleştirisi nedeniyle bitirilmesi yaklaşık 30 saat süren aşağıdaki komutu kullandık :

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

Eski bir 7200 rpm hdd idi ve IO darboğazına ve CPU artışlarına rağmen, eski web sunucusu hizmetine devam etti.


1

Tercih ettiğim seçenek, daha önce önerilmiş olan newfs yaklaşımı. Temel problem, daha önce de belirtildiği gibi, silme işlemine yönelik doğrusal taramanın sorunlu olmasıdır.

rm -rfYerel bir dosya sistemi için en uygun yere yakın olmalıdır (NFS farklı olacaktır). Ancak milyonlarca dosyada, dosya adı başına 36 bayt ve inode başına 4 (tahmin, ext3 değerini kontrol etmiyor), bu sadece dizin için RAM'de tutulacak 40 * milyon.

Tahmin ediyorum ki, Linux'ta dosya sistemi meta veri önbellek belleğine çarpıyorsunuz, böylece yine de başka bir parça kullanırken dizin dosyasının bir sayfasının blokları atılıyor, yalnızca bir dahaki sefere önbelleğin o sayfasına tekrar basmak için dosya silindi. Linux performans ayarı benim alanım değil, ancak / proc / sys / {vm, fs} / muhtemelen alakalı bir şeyler içeriyor.

Arıza süresi karşılayabiliyorsanız, dir_index özelliğini açmayı düşünebilirsiniz. Dizin endeksini büyük dizinlerde (karma ağaçlarda) silmek için doğrusaldan çok daha uygun bir şeye geçirir. tune2fs -O dir_index ...ardından e2fsck -Dişe yarardı. Bununla birlikte, sorun çıkmadan önce bunun yardımcı olacağından emin olmakla birlikte, -Dmevcut bir v.large diziniyle çalışırken dönüşümün (e2fsck) ile nasıl çalıştığını bilmiyorum . Yedekler + em ve gör.


1
pubbs.net/201008/squid/… , /proc/sys/fs/vfs_cache_pressurekullanılacak değer olabileceğini öne sürüyor , ancak dizinin kendisinin sayfa önbelleğine mi sayıldığını (çünkü oydu ) veya inode önbelleğini sayıyor mu bilmiyorum. inode, bu FS meta verileridir ve bu nedenle oraya paketlenmiştir). Dediğim gibi, Linux VM ayarlama benim alanım değil. Oynayın ve neyin yardımcı olduğunu görün.
Phil P

1

Belli ki burada elmalar için elmalar yok, fakat küçük bir test yaptım ve şunları yaptım:

Bir dizinde ( ddve /dev/urandombir döngüde) 100.000 512 bayt dosya oluşturuldu ; Zamanında unuttum, ancak bu dosyaları oluşturmak için yaklaşık 15 dakika sürdü.

Söz konusu dosyaları silmek için aşağıdakileri çalıştırın:

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

100000

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

Bu bir Pentium 4 2.8GHz kutusudur (birkaç yüz GB IDE 7200 RPM sanırım; EXT3). Çekirdek 2.6.27.


İlginç, bu yüzden belki de dosyaların uzun bir süre boyunca oluşturulmakta olduğu gerçeği ile ilgilidir. Ancak bu önemli olmamalı; blok önbelleği, RAM'deki tüm ilgili meta veri bloklarına sahip olmalıdır. Belki de bunun nedeni unlink (2) işlemsel olmasıdır? Tahmininizde, rm süresince günlüğe kaydetmenin kapatılması potansiyel olabilir (kuşkusuz biraz da olsa) bir çözüm olabilir mi? Tamamen monte edilmiş bir dosya sistemindeki günlük kaydını, bir amaca hitap eden bir tune2fs / fsck / reboot olmadan kapatabilirsiniz gibi görünmüyor.
BMDan

Bunun hakkında yorum yapamam, ancak fıkralarıyla (yıllar boyunca yapılan çeşitli NIX tartışmalarında), rmçok sayıda dosyada çok yavaş olduğunu ve dolayısıyla find -deleteseçeneğin çok yavaş olduğunu duydum . Kabuktaki bir joker karakterle, eşleşen her bir dosya adını genişletir ve bunun için sınırlı bir bellek arabelleği olduğunu farz ediyorum, böylece bunun nasıl verimsiz olacağını görebiliyorsunuz.
Gravyface

1
rm yavaş olacaktır, çünkü bir dosyayı isme göre aramaktadır, bu da dizin girişlerini bulana kadar teker teker yineleme anlamına gelir. Bununla birlikte, bu durumda, verilen her bir giriş (o noktada) listedeki birincisi (ls -U / ls -f) olduğundan, neredeyse hızlı olmalıdır . Bununla birlikte, bir şampiyon gibi koşması gereken rm -rf <dir>, olabildiğince yavaştı. Belki de masif silmeleri hızlandırmak için coreutils'e bir yama yazma zamanı gelmiştir? Belki de rm -rf komutunu uygulamak için özyinelemeli bir şekilde bazı özyinelemeli sıralama yapıyor. Bunun gibi belirsizlikler soruyu neden sorduğumdur. ;)
BMDan

1
Oluşturma adımını çalıştırdıktan sonra makineyi yeniden başlatın. Belirgin derecede yavaş bir silme elde etmelisiniz.
Matt

1

Perl bazen böyle durumlarda harikalar yaratabilir. Böyle küçük bir betiğin bash ve temel kabuk komutlarından daha iyi performans gösterip göstermediğini zaten denediniz mi?

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

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

Ya da başka bir, belki daha hızlı, Perl yaklaşıyor:

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

EDIT: Ben sadece Perl scriptlerimi bir deneyin. Daha ayrıntılı olanı doğru bir şey yapar. Benim durumumda bunu 256 MB RAM ve yarım milyon dosya içeren sanal bir sunucu ile denedim.

time find /test/directory | xargs rm Sonuçlar:

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

nazaran

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

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

Bu glob () çağrısının ne yapacağını hayal etmekte tereddüt ediyorum; Bir scandir () yaptığını varsayıyorum. Öyleyse, geri dönmek sonsuza dek sürecek. Tüm öneri okumayan ilk öneriye ait bir değişiklik, bazı bacaklara sahip olabilir; ancak, şu anki haliyle, aynı zamanda, sadece tüm dizin girişlerini bir kerede okumak için kutsal olmayan miktarda CPU kullanacaktır. Buradaki hedefin bir kısmı, böl ve ele geçir; Bu kod, kabuk genişlemesine rağmen, temelde 'rm -f * .png'den farklı değildir. Eğer yardımı olacaksa, ben dizindeki şey yok etmedi silmek istiyorum.
BMDan

İşe başlar başlamaz daha çok şey denemeliyim. Tek bir dizinde 100.000 dosya oluşturmaya çalıştım ve + xargs + rm kombinasyonunun 7.3 saniye sürdüğünü, Perl + unlink (glob) ... kombinasyonunu 2.7 saniyede bitirdiğimi öğrendim. Birkaç kez denedim, sonuç hep aynıydı. İşyerinde daha fazla dosyayla deneyeceğim.
Janne Pikkarainen

Bunu test ederken yeni bir şey öğrendim. En azından ext3 ve ext4 ile dizin girişinin kendisi, tüm dosyaları oradan sildikten sonra bile çok büyük kalır. Birkaç testten sonra / tmp / test dizini 15 MB disk alanı kullanıyordu. Dizini kaldırmak ve yeniden oluşturmaktan başka, temizlemenin başka bir yolu var mı?
Janne Pikkarainen

2
Hayır, yeniden oluşturmanız gerek. Posta sistemiyle, alıcı başına klasörle ve önemli sorunlardan sonra yapılan temizleme işlemleriyle uğraşırken buna çarptım: yeni bir dizin oluşturmak ve dizinleri karıştırmaktan sonra eskisini çekmekten başka bir yol yok. Böylece, dizin olmadığında zaman penceresini azaltabilir, ancak ortadan kaldıramazsınız.
Phil P

Glob () uygulamasının, kabuk globbing'in normalde yaptığı gibi sonuçları sıralayacağını unutmayın, bu nedenle yalnızca 100k dosyanız olduğundan, her şey kolayca uyuyor ve sıralama hızlı. Çok daha geniş bir dizinde, sıralamadan kaçınmak için opendir () / readdir () / closedir () işlevini kullanmak istersiniz. [ Kabuk için normal diyorum , zsh, sıralama düzenini sıralamak için bir glob değiştiricisine sahip olduğundan, çok sayıda dosya ile çalışırken yararlıdır; *(oN)]
Phil P

1

Eklenti dosya sistemindeki inode'ların silinmesini hatırladığım kadarıyla O (n ^ 2), yani ne kadar çok silerseniz o kadar hızlı gidersiniz.

Bir kez benzer bir sorunla karşı karşıya kaldım (tahminlerim ~ 7 saat silme zamanına baksa da), sonunda ilk yorumu yapılan jftuga önerilen rotaya gitti .


0

Bu gerçek bir cevap değil ama ...

Dosya sistemini ext4'e dönüştürmek ve işlerin değişip değişmediğini görmek mümkün müdür?


Bu "canlı" yapmanın monte edilmiş bir dosya sistemi üzerinde fsck gerektirdiği anlaşılıyor, ki bu ... alarm. Daha iyi bir yolun var mı?
BMDan

Dosya sisteminin dönüşümden önce, yani gerekli tunef komutlarından önce çıkarılması gerekir.
marcoc

0

Tamam, bu iş parçacığının geri kalanında çeşitli şekillerde ele alındı, ancak iki sentimi de atabileceğimi düşündüm. Davanızdaki performans suçlusu muhtemelen okundu. Bağlantıyı kaldırdığınızda her yere disk erişimine neden olan disk üzerinde sıralı bir şekilde olması gerekmeyen dosyaların bir listesini geri alıyorsunuz. Dosyalar, unlink işleminin büyük olasılıkla boşluğu sıfırlayarak atlamamaya yetecek kadar küçüktür. Okudunuz ve sonra artan inode'a göre sıralarsanız, muhtemelen daha iyi performans elde edersiniz. Yani ram içine readdir (inode'a göre sırala) -> unlink -> kar.

Inode burda kaba bir yaklaşım. Sanırım .. ama kullanım durumunuza dayanarak oldukça doğru olabilir ...


1
Eğer hatalıysam düzelt, fakat unlink (2) inode'u sıfırlamaz, sadece referansı dizinden kaldırır. Yine de, bu yaklaşımın chutzpah'ını seviyorum. Bazı denemeler yapmak ve doğru olup olmadığını görmek ister misiniz?
BMDan

0

Muhtemelen bir C derleyicisini çırpmış ve betiğinizin ahlaki eşdeğerini yapmış olurdum. Diğer bir deyişle, opendir(3)bir dizin tanıtıcısı readdir(3)almak için kullanın, sonra dosyaların adını almak için kullanın , sonra da bağlantılarını açtıklarında dosyaları toparlayın ve arada bir "% d dosya silindi" (ve muhtemelen geçen zaman veya geçerli zaman damgası) yazdırın.

Kabuk betiği versiyonundan belirgin bir şekilde daha hızlı olmasını beklemiyorum, sadece derleyiciyi şimdi ve tekrar sökmek zorunda kalmam gerektiğinden, çünkü kabuğundan istediklerimi temiz bir şekilde yapmamın nedeni ya da çünkü kabuklu yapılabilirken, bu şekilde verimsiz yavaşlar.


En azından rutm kaynak kodunu coreutils'den değiştirerek başlayabilirdi .
Cristian Ciupitu

0

Muhtemelen dizinde yeniden yazım sorunlarıyla karşılaşıyorsunuzdur. Önce en yeni dosyaları silmeyi deneyin. Diske geri yazma işlemini geciktirecek takma seçeneklerine bakın.

Bir ilerleme çubuğu için gibi bir şey çalıştırmayı deneyin rm -rv /mystuff 2>&1 | pv -brtl > /dev/null


Önce en yeni dosyaları silmek için: ls -Ur? Dir girişlerini yükleyip sonraları ters çevireceğinden eminim; Dizin giriş listesinin sonunda başlayıp yeterince başını geri saracak kadar akıllı olduğuna inanmıyorum. "ls -1" de muhtemelen iyi bir fikir değil, çünkü 50+ MB çekirdek ve çalışması birkaç dakika alacaktır; "ls -U" veya "ls -f" istersiniz.
BMDan

Büyük olasılıkla, yalnızca dosya adları tahmin edilebilir düzende artarsa ​​pratik olabilir. Ama sen benim denemem -1 olarak tersine döndü, ve xargs'a getirildi. Ara sonuçlarınızı görmek istiyorsanız, borular yerine dosyaları kullanın. Dosya adlandırma hakkında herhangi bir bilgi vermediniz. Pıtırtı tersten oluşturacak ve deseni kullanarak dosyaları sileceksiniz. Eksik dosya girişlerini işlemeniz gerekebilir. Gereken hafızaya ilişkin yorumunuz dikkate alındığında, dizini yeniden yazmak için gereken G / Ç hakkında bir fikriniz vardır.
BillThor

0

Bazen büyük bir Oracle veritabanı sunucusunda toplanabilecek milyonlarca izleme dosyasını nasıl sildiğim:

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

Bunun, "tipik" 10.000 IOPS kurulumunda genellikle milyonda bir saatlik bir satır boyunca olan sunucu performansı üzerinde düşük etkiye sahip oldukça yavaş bir silme ile sonuçlandığını buluyorum.

Dizinlerin taranması, ilk dosya listesi oluşturulmuş ve ilk dosya silinmesi genellikle birkaç dakika alacaktır. Oradan ve oradan, bir. silinen her dosya için yankılanır.

Terminale yankılanmanın gecikmesi, silme işlemi devam ederken herhangi bir önemli yükü önlemek için yeterli bir gecikme olduğunu kanıtlamıştır.


Globbing tarafından canlı olarak yeniliyorsun. Daha çok nasıl bir şey olur 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'? -deleteBir şeyleri silmek için sonuncuya ekleyin ; yazıldığı gibi, sadece sileceği şeyi listeler. Bunun, yakındaki dizinlerde ilgi çekici olmayan birçok şeyin olduğu durumlar için optimize edildiğini unutmayın; durum böyle değilse, mantığı büyük ölçüde basitleştirebilirsiniz.
BMDan

find -delete çok fazla G / Ç'ye neden olma eğilimindedir ve üretim performansını kolayca etkiler. Belki de iyon ile.
Roy,

Tüm bu I / O’lara basitçe daha verimli olmalarına neden oluyor! Globbing tüm ön yüklü sizin (örneğin ilk önce, dosyaların tam listesini oluşturulur olduğunu rmsen bundan başlangıçta nispeten verimli I / O'yu var çok acı, out-of-order ardından, olur) rmlar bu muhtemelen çok fazla G / Ç'ye neden olmamakla birlikte scandir, dizinin art arda yürümesini içerir (zaten G / Ç'ye neden olmaz çünkü zaten blok önbelleğe yüklenmiştir; ayrıca bakınız vfs_cache_pressure). İşleri yavaşlatmak istersen ionicebir seçenek, ama muhtemelen kesirli saniye kullanırım sleep.
BMDan

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +rmDizin başına bir tane çalıştırabilir , böylece daha fazla CPU ek yükünüz olur. Her bir rmişlem için bir bütün süreci zorlayarak, disk IO'yu boşa harcayacak tonlarca CPU zamanınız olduğu sürece unlink, sanırım, ama çirkin. rmBir seferde tüm dizinler arasında uyumak çok patlamışsa, bağlantı başına bir uyku ile perl daha iyi olurdu . ( -execdir sh -c ...belki)
Peter Cordes

-1

'Xargs' paralelleştirme özelliklerini kullanabilirsiniz:

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

1
Bu yardımcı olmaz. Darboğaz, sürücüdeki zayıf rastgele G / Ç'dir. Paralel silmelerin yapılması daha da kötüleşebilir ve CPU yükünü arttırabilir.
Wim Kerkhoff

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

1
Vay. Sanırım bu, "bir kediyi ciltlemenin birden fazla yolu" kampına sıkıca düşüyor. Cidden, yine de, sıralama ve uniq ile? Yine de "ls" varsayılan olarak sıralar ve dosya adlarının benzersiz olduğunu umarım. : /
BMDan

-2

Aslında, kullandığınız kabuk komut satırı genişletme yaparsa bu biraz daha iyidir:

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.