rm trên một thư mục có hàng triệu tập tin


104

Bối cảnh: máy chủ vật lý, khoảng hai năm tuổi, các ổ đĩa SATA 7200-RPM được kết nối với thẻ RAID 3Ware, ext3 FS gắn noatime và dữ liệu = đã đặt hàng, không tải điên, kernel 2.6,18-92.1,22.el5, thời gian hoạt động 545 ngày . Thư mục không chứa bất kỳ thư mục con nào, chỉ có hàng triệu tệp nhỏ (~ 100 byte), với một số tệp lớn hơn (vài KB).

Chúng tôi có một máy chủ đã hoạt động một chút trong vài tháng qua, nhưng chúng tôi chỉ nhận thấy nó vào một ngày khác khi nó bắt đầu không thể ghi vào một thư mục do nó chứa quá nhiều tệp. Cụ thể, nó bắt đầu ném lỗi này trong / var / log / message:

ext3_dx_add_entry: Directory index full!

Đĩa trong câu hỏi có nhiều nút còn lại:

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

Vì vậy, tôi đoán điều đó có nghĩa là chúng ta đạt đến giới hạn số lượng mục có thể có trong tệp thư mục. Không biết có bao nhiêu tệp sẽ có, nhưng nó không thể nhiều hơn, như bạn có thể thấy, hơn ba triệu hoặc hơn thế. Không phải vậy đâu, phiền bạn! Nhưng đó là phần một trong câu hỏi của tôi: chính xác giới hạn trên là gì? Có điều chỉnh được không? Trước khi tôi hét lên, tôi muốn điều chỉnh lại ; thư mục khổng lồ này gây ra tất cả các loại vấn đề.

Dù sao, chúng tôi đã theo dõi vấn đề trong mã đang tạo ra tất cả các tệp đó và chúng tôi đã sửa nó. Bây giờ tôi bị mắc kẹt với việc xóa thư mục.

Một vài lựa chọn ở đây:

  1. rm -rf (dir)

    Tôi đã thử điều này đầu tiên. Tôi đã từ bỏ và giết nó sau khi nó chạy được một ngày rưỡi mà không có tác động rõ rệt nào.

  2. hủy liên kết (2) trên thư mục: Chắc chắn đáng xem xét, nhưng câu hỏi đặt ra là liệu có thể xóa các tệp trong thư mục qua fsck nhanh hơn so với xóa qua liên kết (2). Đó là, bằng cách này hay cách khác, tôi đã phải đánh dấu các nút đó là không sử dụng. Tất nhiên, điều này giả định rằng tôi có thể nói với fsck không bỏ các mục vào các tệp trong / mất + tìm thấy; mặt khác, tôi vừa chuyển vấn đề của mình Ngoài tất cả các mối quan tâm khác, sau khi đọc về vấn đề này thêm một chút, hóa ra tôi có thể phải gọi một số chức năng FS nội bộ, vì không có biến thể unlink (2) nào tôi có thể tìm thấy sẽ cho phép tôi xóa hoàn toàn một thư mục với các mục trong đó. Pooh.
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    Đây thực sự là phiên bản rút gọn; cái thực sự tôi đang chạy, nó chỉ thêm một số báo cáo tiến trình và dừng sạch khi chúng tôi hết tệp để xóa, là:

    xuất i = 0;
    thời gian (trong khi [đúng]; làm
      ls -Uf | đầu -n 3 | grep -qF '.png' || phá vỡ;
      ls -Uf | đầu -n 10000 | xargs rm -f 2> / dev / null;
      xuất i = $ (($ i + 10000));
      tiếng vang "$ i ...";
    làm xong )

    Điều này dường như đang làm việc khá tốt. Khi tôi viết bài này, nó đã xóa 260.000 tệp trong ba mươi phút qua.

Bây giờ, cho các câu hỏi:
  1. Như đã đề cập ở trên, giới hạn mục nhập trên mỗi thư mục có thể điều chỉnh?
  2. Tại sao phải mất "7m9.561s / người dùng 0m0.001s / sys 0m0.001s" thực sự để xóa một tệp duy nhất là tệp đầu tiên trong danh sách được trả về ls -Uvà phải mất mười phút để xóa 10.000 mục đầu tiên với chỉ huy trong số 3, nhưng bây giờ nó đang di chuyển khá vui vẻ? Đối với vấn đề đó, nó đã xóa 260.000 trong khoảng ba mươi phút, nhưng giờ mất thêm mười lăm phút để xóa thêm 60.000. Tại sao sự thay đổi lớn về tốc độ?
  3. Có cách nào tốt hơn để làm điều này không? Không lưu trữ hàng triệu tệp trong một thư mục; Tôi biết điều đó thật ngớ ngẩn, và nó sẽ không xảy ra trên đồng hồ của tôi. Giải quyết vấn đề và xem qua SF và SO cung cấp rất nhiều biến thể về findviệc sẽ không nhanh hơn đáng kể so với cách tiếp cận của tôi vì một số lý do rõ ràng. Nhưng ý tưởng xóa-qua-fsck có chân nào không? Hay cái gì khác hoàn toàn? Tôi rất muốn nghe suy nghĩ ngoài luồng (hoặc bên trong hộp không nổi tiếng).
Cảm ơn đã đọc tiểu thuyết nhỏ; vui lòng đặt câu hỏi và tôi chắc chắn sẽ trả lời. Tôi cũng sẽ cập nhật câu hỏi với số lượng tệp cuối cùng và tập lệnh xóa đã chạy trong bao lâu khi tôi có.

Đầu ra kịch bản cuối cùng!:

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

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

Vì vậy, ba triệu tệp đã bị xóa trong hơn bốn giờ.


1
rm (GNU coreutils) 8.4 có tùy chọn này: "-v, --verbose giải thích những gì đang được thực hiện" . Nó sẽ hiển thị tất cả các tập tin đang bị xóa.
Cristian Ciupitu

2
Trên thực tế, đó là một cách gọn gàng để thực hiện một thanh tiến trình: vì mỗi tệp sẽ dài ba mươi bảy ký tự (36 + a '\ n'), tôi có thể dễ dàng viết một trình phân tích cú pháp cho điều đó và vì printf () là giá rẻ và lệnh rm đã có tên của tệp được tải, không có hình phạt hiệu suất đặc biệt. Có vẻ như không phải là người khởi đầu cho việc thực hiện toàn bộ shebang, vì dù sao tôi cũng không bao giờ có thể có "rm" để làm bất cứ điều gì như vậy. Nhưng nó có thể hoạt động khá tốt như một thanh tiến bộ trong vòng 10.000; có lẽ là một "." cho mỗi trăm tập tin?
BMDan

8
rm -rfv | pv -l >/dev/null. pv nên có sẵn trong kho EPEL .
Cristian Ciupitu

5
pv là quá tuyệt vời. Tôi để lại dấu vết cài đặt pv sau khi thức dậy.
BMDan

Tôi đã có vấn đề chính xác tương tự gần đây. Cảm ơn bạn!
richo

Câu trả lời:


30

Các data=writebacktùy chọn gắn kết xứng đáng được cố gắng, để ngăn chặn nhật ký của hệ thống tập tin. Điều này chỉ nên được thực hiện trong thời gian xóa, có một rủi ro tuy nhiên nếu máy chủ đang bị tắt hoặc khởi động lại trong quá trình xóa.

Theo trang này ,

Một số ứng dụng cho thấy sự cải thiện tốc độ rất đáng kể khi nó được sử dụng. Ví dụ: có thể thấy sự cải thiện tốc độ (...) khi các ứng dụng tạo và xóa khối lượng lớn các tệp nhỏ.

Tùy chọn được đặt trong fstabhoặc trong khi vận hành gắn kết, thay thế data=orderedbằng data=writeback. Hệ thống tập tin chứa các tập tin sẽ bị xóa phải được tính lại.


1
Anh ta cũng có thể tăng thời gian từ commit tùy chọn : "Giá trị mặc định này (hoặc bất kỳ giá trị thấp nào) sẽ ảnh hưởng đến hiệu suất, nhưng nó tốt cho an toàn dữ liệu. Đặt nó thành 0 sẽ có tác dụng tương tự như để mặc định (5 giây ). Đặt nó thành các giá trị rất lớn sẽ cải thiện hiệu suất ".
Cristian Ciupitu

1
WritBack trông rất nổi bật, ngoại trừ tài liệu tôi đang xem ( gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4 ) đề cập rõ ràng rằng nó vẫn ghi nhật ký siêu dữ liệu, mà tôi cho là bao gồm tất cả dữ liệu tôi thay đổi (tôi chắc chắn không thay đổi bất kỳ dữ liệu nào trong các tệp). Là sự hiểu biết của tôi về các lựa chọn không chính xác?
BMDan

Cuối cùng, FYI, không được đề cập trong liên kết đó là thực tế rằng dữ liệu = ghi lại có thể là một lỗ hổng bảo mật rất lớn, vì dữ liệu được chỉ ra bởi một mục nhất định có thể không có dữ liệu được viết bởi ứng dụng đó, có nghĩa là sự cố có thể xảy ra trong dữ liệu cũ, có thể nhạy cảm / riêng tư bị lộ. Không phải là vấn đề đáng lo ngại ở đây, vì chúng tôi chỉ bật nó tạm thời, nhưng tôi muốn cảnh báo mọi người về sự cảnh báo đó trong trường hợp bạn hoặc những người khác chạy qua đề nghị đó không biết.
BMDan

cam kết: đó là khá trơn tru! Cảm ơn con trỏ.
BMDan

2
data=writebackvẫn ghi nhật ký siêu dữ liệu trước khi ghi nó vào hệ thống tập tin chính. Theo tôi hiểu, nó chỉ không thực thi trật tự giữa những thứ như viết bản đồ phạm vi và ghi dữ liệu vào các phạm vi đó. Có thể có những hạn chế đặt hàng khác, nó cũng thư giãn, nếu bạn thấy một lợi ích hoàn hảo từ điều này. Tất nhiên, gắn mà không có tạp chí ở tất cả có thể thậm chí hiệu suất cao hơn. (Nó có thể cho phép thay đổi siêu dữ liệu chỉ xảy ra trong RAM, mà không cần phải có bất cứ thứ gì trên đĩa trước khi hủy liên kết hoàn thành).
Peter Cordes

80

Mặc dù nguyên nhân chính của vấn đề này là hiệu suất ext3 với hàng triệu tệp, nguyên nhân gốc thực sự của vấn đề này là khác nhau.

Khi một thư mục cần được liệt kê, readdir () được gọi trên thư mục mang lại danh sách các tệp. readdir là một cuộc gọi posix, nhưng cuộc gọi hệ thống Linux thực sự đang được sử dụng ở đây được gọi là 'getdents'. Getdents liệt kê các mục thư mục bằng cách điền vào một bộ đệm với các mục.

Vấn đề chủ yếu là do readdir () sử dụng kích thước bộ đệm cố định 32Kb để tìm nạp các tệp. Khi một thư mục trở nên lớn hơn và lớn hơn (kích thước tăng khi thêm tệp), ext3 sẽ chậm hơn và chậm hơn để tìm nạp các mục và kích thước bộ đệm 32Kb của readdir bổ sung chỉ đủ để bao gồm một phần của các mục trong thư mục. Điều này khiến readdir lặp đi lặp lại và gọi đi gọi lại hệ thống đắt tiền.

Ví dụ, trên một thư mục thử nghiệm mà tôi đã tạo với hơn 2,6 triệu tệp bên trong, chạy "ls -1 | wc-l" cho thấy một đầu ra bước lớn của nhiều cuộc gọi hệ thống 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
...

Ngoài ra thời gian trong thư mục này là đáng kể.

$ time ls -1 | wc -l
2616044

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

Phương pháp để thực hiện quá trình này hiệu quả hơn là gọi getdents bằng tay với bộ đệm lớn hơn nhiều. Điều này cải thiện hiệu suất đáng kể.

Bây giờ, bạn không nên tự mình gọi getdents để không có giao diện tồn tại để sử dụng bình thường (kiểm tra trang man để xem getdents!), Tuy nhiên, bạn có thể gọi thủ công và làm cho cách gọi hệ thống của bạn hiệu quả hơn.

Điều này làm giảm đáng kể thời gian cần thiết để tìm nạp các tệp này. Tôi đã viết một chương trình làm điều này.

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

Trong khi điều này không chống lại vấn đề cơ bản tiềm ẩn (rất nhiều tệp, trong một hệ thống tệp hoạt động kém ở đó). Nó có thể sẽ nhanh hơn nhiều so với nhiều lựa chọn thay thế được đăng.

Như đã biết trước, người ta nên xóa thư mục bị ảnh hưởng và làm lại sau. Các thư mục chỉ tăng kích thước và có thể vẫn hoạt động kém ngay cả với một vài tệp bên trong do kích thước của thư mục.

Chỉnh sửa: Tôi đã làm sạch điều này khá nhiều. Đã thêm một tùy chọn để cho phép bạn xóa trên dòng lệnh trong thời gian chạy và loại bỏ một loạt các công cụ treewalk, điều mà nhìn lại một cách trung thực là tốt nhất. Cũng được hiển thị để sản xuất tham nhũng bộ nhớ.

Bây giờ bạn có thể làm dentls --delete /my/path

Kết quả mới. Dựa trên một thư mục với 1,82 triệu tệp.

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

Đã loại ngạc nhiên này vẫn hoạt động rất tốt!


1
Hai mối quan tâm nhỏ: một, [256]có lẽ nên là [FILENAME_MAX], và hai, Linux của tôi (2.6,18 == CentOS 5.x) dường như không bao gồm một mục nhập d_type trong dirent (ít nhất là theo getdents (2)).
BMDan

1
Bạn có thể vui lòng giải thích một chút về tái cân bằng btree và tại sao xóa để giúp ngăn chặn nó? Tôi đã thử Googling cho nó, tiếc là không có kết quả.
ovgolovin

1
Bởi vì bây giờ có vẻ như với tôi nếu chúng tôi đang xóa trong trật tự, chúng tôi buộc tái cân bằng, như chúng ta loại bỏ lá ở một bên và để lại trên người kia: en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion
ovgolovin

1
Tôi hy vọng tôi không làm phiền bạn về vấn đề này. Nhưng tôi vẫn bắt đầu một câu hỏi về việc xóa các tệp theo thứ tự stackoverflow.com/q/17955459/862380 , dường như không nhận được câu trả lời sẽ giải thích vấn đề bằng ví dụ, điều này có thể hiểu được đối với các lập trình viên thông thường. Nếu bạn có thời gian và cảm thấy như vậy, bạn có thể xem xét nó không? Có lẽ bạn có thể viết một lời giải thích tốt hơn.
ovgolovin

2
Đây là một đoạn mã tuyệt vời. Đó là công cụ duy nhất tôi có thể tìm thấy có thể liệt kê và xóa một số tệp phiên 11.000.000 (mười một triệu) đã được xây dựng trong một thư mục, có thể trong vài năm. Quá trình Plesk được cho là giữ cho chúng được kiểm soát bằng cách sử dụng find và các thủ thuật khác trong các câu trả lời khác ở đây, không thể hoàn thành một lần chạy, vì vậy các tệp chỉ tiếp tục xây dựng. Nó là một cống nạp cho cây nhị phân mà hệ thống tập tin sử dụng để lưu trữ thư mục, rằng các phiên có thể hoạt động hoàn toàn - bạn có thể tạo một tệp và truy xuất nó không chậm trễ. Chỉ có danh sách là không thể sử dụng.
Jason

31

Có thể sao lưu tất cả các tệp khác từ hệ thống tệp này vào một vị trí lưu trữ tạm thời, định dạng lại phân vùng và sau đó khôi phục các tệp không?


3
Tôi thực sự thích câu trả lời này, thực sự. Như một vấn đề thực tế, trong trường hợp này, không, nhưng đó không phải là điều tôi sẽ nghĩ đến. Bravo!
BMDan

Chính xác những gì tôi đã nghĩ quá. Đây là câu trả lời cho câu hỏi 3. Lý tưởng nếu bạn hỏi tôi :)
Joshua

12

Không có giới hạn tệp thư mục trong ext3 chỉ giới hạn inode của hệ thống tệp (tôi nghĩ rằng có giới hạn về số lượng thư mục con mặc dù).

Bạn vẫn có thể gặp vấn đề sau khi gỡ bỏ các tập tin.

Khi một thư mục có hàng triệu tệp, mục nhập thư mục sẽ trở nên rất lớn. Mục nhập thư mục phải được quét cho mọi thao tác xóa và mất nhiều thời gian khác nhau cho mỗi tệp, tùy thuộc vào vị trí của mục nhập. Thật không may, ngay cả sau khi tất cả các tệp đã bị xóa, mục nhập thư mục vẫn giữ nguyên kích thước của nó. Vì vậy, các hoạt động tiếp theo yêu cầu quét mục nhập thư mục sẽ vẫn mất nhiều thời gian ngay cả khi thư mục hiện đang trống. Cách duy nhất để giải quyết vấn đề đó là đổi tên thư mục, tạo một cái mới với tên cũ và chuyển bất kỳ tệp nào còn lại sang cái mới. Sau đó xóa cái đã đổi tên.


Thật vậy, tôi nhận thấy chỉ hành vi này sau khi xóa tất cả mọi thứ. May mắn thay, chúng tôi đã đưa thư mục ra khỏi "dòng lửa", vì vậy, tôi chỉ có thể rmdir nó.
BMDan

2
Điều đó nói rằng, nếu không có giới hạn tệp trên mỗi thư mục, tại sao tôi lại nhận được "ext3_dx_add_entry: Chỉ mục thư mục đầy đủ!" khi vẫn còn inodes trên phân vùng đó? Không có thư mục con trong thư mục này.
BMDan

3
hmm tôi đã nghiên cứu thêm một chút và dường như có giới hạn số lượng khối mà một thư mục có thể chiếm. Số lượng tệp chính xác phụ thuộc vào một số thứ, ví dụ độ dài tên tệp. Gossamer-threads.com/lists/linux/kernel/921942 này dường như chỉ ra rằng với các khối 4k, bạn sẽ có thể có hơn 8 triệu tệp trong một thư mục. Họ có tên tập tin đặc biệt dài không?
Alex J. Roberts

Mỗi tên tệp dài chính xác 36 ký tự.
BMDan

Vâng, đó là ý tưởng của tôi :)
Alex J. Roberts


4

Tìm đơn giản là không hoạt động đối với tôi, ngay cả sau khi thay đổi các tham số của ext3 fs theo đề xuất của người dùng ở trên. Tiêu thụ quá nhiều bộ nhớ. Kịch bản PHP này đã thực hiện thủ thuật - sử dụng CPU nhanh, không đáng kể, sử dụng bộ nhớ không đáng kể:

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

Tôi đã đăng một báo cáo lỗi liên quan đến rắc rối này với find: http://savannah.gnu.org/bugs/?31961


Điều này đã cứu tôi !!
jestro

3

Gần đây tôi đã gặp phải một vấn đề tương tự và không thể làm cho data=writebackđề xuất của ring0 hoạt động (có thể do thực tế là các tệp nằm trên phân vùng chính của tôi). Trong khi nghiên cứu cách giải quyết, tôi tình cờ phát hiện ra điều này:

tune2fs -O ^has_journal <device>

Điều này sẽ tắt nhật ký hoàn toàn, bất kể datatùy chọn đưa ra mount. Tôi đã kết hợp điều này với noatimevà âm lượng đã được dir_indexthiết lập, và nó dường như hoạt động khá tốt. Việc xóa thực sự kết thúc mà tôi không cần phải giết nó, hệ thống của tôi vẫn phản hồi và hiện đã sao lưu và chạy (với nhật ký trở lại) mà không gặp vấn đề gì.


Tôi sẽ đề nghị gắn nó dưới dạng ext2 thay vì ext3, để tránh ghi nhật ký các siêu dữ liệu. Điều này nên làm như vậy.
Peter Cordes

3

Hãy chắc chắn rằng bạn làm:

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

mà nên tăng tốc mọi thứ lên một chút là tốt.


4
Cuộc gọi tốt, nhưng nó đã được gắn vào buổi trưa, như tôi đã đề cập trong tiêu đề cho câu hỏi. Và gật đầu là dư thừa; xem lwn.net/Articles/245002 .
BMDan

1
ppl lặp lại câu thần chú này "noatime, gật đầu, gật đầu, noreaddocsatime"
poige

2

Lệnh rất chậm. Thử:

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

rm -rf đã chạy trong một ngày rưỡi, và cuối cùng tôi đã giết nó, mà không bao giờ biết liệu nó đã thực sự hoàn thành bất cứ điều gì. Tôi cần một thanh tiến trình.
BMDan

4
Vì rm rất chậm, "thời gian tìm. -Xóa" trên các tệp 30k: 0m0.357s / 0m0.019s / 0m0.337s real / user / sys. "thời gian (ls -1U | xargs rm -f)" trên cùng các tệp đó: 0m0.366s / 0m0.025s / 0m0.340s. Về cơ bản là lãnh thổ có lỗi.
BMDan

1
Bạn có thể vừa chạy strace -r -p <pid of rm>để gắn vào quy trình rm đã chạy. Sau đó, bạn có thể thấy unlinkcác cuộc gọi hệ thống đang di chuyển nhanh như thế nào . ( -rđặt thời gian kể từ khi hệ thống trước đó gọi vào đầu mỗi dòng.)
Peter Cordes

2

Được dir_indexđặt cho hệ thống tập tin? ( tune2fs -l | grep dir_index) Nếu không, kích hoạt nó. Nó thường được bật cho RHEL mới.


1
Vâng, nó được kích hoạt, nhưng gợi ý tuyệt vời!
BMDan

2

Vài năm trước, tôi tìm thấy một thư mục chứa 16 triệu tệp XML trong /hệ thống tệp. Do sự nghiêm trọng của máy chủ, chúng tôi đã sử dụng lệnh sau mất khoảng 30 giờ để hoàn thành:

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

Đó là một hdd 7200 vòng / phút cũ , và mặc dù tắc nghẽn IO và CPU tăng đột biến, máy chủ web cũ vẫn tiếp tục dịch vụ của mình.


1

Tùy chọn ưa thích của tôi là cách tiếp cận newfs, đã được đề xuất. Vấn đề cơ bản là, một lần nữa như đã lưu ý, quét tuyến tính để xử lý xóa là có vấn đề.

rm -rfnên gần tối ưu cho một hệ thống tập tin cục bộ (NFS sẽ khác). Nhưng với hàng triệu tệp, 36 byte cho mỗi tên tệp và 4 cho mỗi nút (một phỏng đoán, không kiểm tra giá trị cho ext3), đó là 40 * triệu, được giữ trong RAM chỉ dành cho thư mục.

Theo phỏng đoán, bạn đang sử dụng bộ nhớ đệm siêu dữ liệu của hệ thống tệp trong Linux, do đó, các khối cho một trang của tệp thư mục sẽ bị hết hạn trong khi bạn vẫn đang sử dụng một phần khác, chỉ để nhấn lại trang đó của bộ đệm khi tiếp theo tập tin bị xóa Điều chỉnh hiệu suất Linux không phải là khu vực của tôi, nhưng / Proc / sys / {vm, fs} / có thể chứa một cái gì đó có liên quan.

Nếu bạn có thể đủ khả năng ngừng hoạt động, bạn có thể xem xét bật tính năng dir_index. Nó chuyển chỉ mục thư mục từ tuyến tính sang thứ gì đó tối ưu hơn nhiều để xóa trong các thư mục lớn (b-cây băm). tune2fs -O dir_index ...tiếp theo e2fsck -Dsẽ làm việc. Tuy nhiên, mặc dù tôi tin rằng điều này sẽ giúp ích trước khi có vấn đề, tôi không biết cách chuyển đổi (e2fsck với -D) thực hiện khi xử lý thư mục v.lund hiện có. Sao lưu + mút nó và xem.


1
pubbs.net/201008/squid/, gợi ý rằng đó /proc/sys/fs/vfs_cache_pressurecó thể là giá trị để sử dụng, nhưng tôi không biết liệu thư mục đó có được tính vào bộ đệm trang hay không (vì đó là gì) hoặc bộ đệm inode (vì, mặc dù không phải là một bộ đệm inode, đó là siêu dữ liệu FS và được gói vào đó vì lý do đó). Như tôi nói, điều chỉnh Linux VM không phải là lĩnh vực của tôi. Chơi và xem những gì giúp.
Phil P

1

Rõ ràng không phải táo để táo ở đây, nhưng tôi thiết lập một thử nghiệm nhỏ và làm như sau:

Tạo 100.000 tệp 512 byte trong một thư mục ( dd/dev/urandomtrong một vòng lặp); quên thời gian, nhưng mất khoảng 15 phút để tạo các tệp đó.

Chạy các mục sau để xóa các tệp đã nói:

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

100000

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

Đây là hộp Pentium 4 2.8GHz (tôi nghĩ vài trăm GB IDE 7200 RPM; EXT3). Hạt nhân 2.6.27.


Thật thú vị, vậy có lẽ thực tế là các tập tin đã được tạo ra trong một thời gian dài có liên quan? Nhưng điều đó không quan trọng; bộ đệm khối nên có tất cả các khối siêu dữ liệu thích hợp trong RAM. Có lẽ vì unlink (2) là giao dịch? Theo ước tính của bạn, việc tắt nhật ký trong thời gian của rm có phải là một giải pháp tiềm năng (mặc dù có phần nguy hiểm được thừa nhận) không? Có vẻ như bạn không thể tắt nhật ký hoàn toàn trên một hệ thống tập tin được gắn mà không cần chỉnh2 / fsck / khởi động lại, điều này phần nào đánh bại mục đích.
BMDan

Tôi không thể nhận xét về điều đó, nhưng theo giai thoại (trong các cuộc thảo luận NIX khác nhau trong nhiều năm qua), tôi luôn nghe thấy rằng rmnó chậm khủng khiếp trên một số lượng lớn các tệp, do đó find -deletetùy chọn này. Với ký tự đại diện trên vỏ, nó sẽ mở rộng từng tên tệp phù hợp và tôi giả sử có bộ đệm hạn chế cho điều đó, vì vậy bạn có thể thấy điều đó sẽ kém hiệu quả như thế nào.
gravyface

1
rm sẽ bị chậm bởi vì nó đang tìm kiếm một tệp theo tên, có nghĩa là lặp đi lặp lại qua các mục nhập thư mục cho đến khi tìm thấy nó. Trong trường hợp này, tuy nhiên, vì mỗi mục nó là tay là (tại thời điểm đó) là người đầu tiên trong danh sách (ls -U / ls -f), nó phải là gần như nhanh. Điều đó nói rằng, rm -rf <dir>, đáng lẽ phải chạy như một nhà vô địch, thì càng chậm càng tốt. Có lẽ đã đến lúc viết một bản vá cho coreutils để tăng tốc độ xóa lớn? Có lẽ đó là bí mật toàn cầu hóa / sắp xếp theo một cách đệ quy nào đó để thực hiện rm -rf? Những điều không chắc chắn như thế này là lý do tại sao tôi đặt câu hỏi. ;)
BMDan

1
Khởi động lại máy sau khi bạn chạy bước tạo. Bạn sẽ nhận được một sự xóa chậm đáng chú ý.
Matt

1

Đôi khi Perl có thể làm việc kỳ diệu trong những trường hợp như thế này. Bạn đã thử nếu một tập lệnh nhỏ như thế này có thể vượt trội hơn bash và các lệnh shell cơ bản chưa?

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

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

Hoặc cách khác, có lẽ còn nhanh hơn, cách tiếp cận Perl:

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

EDIT: Tôi vừa thử kịch bản Perl của mình. Càng dài càng làm một cái gì đó đúng. Trong trường hợp của tôi, tôi đã thử điều này với một máy chủ ảo có RAM 256 MB và nửa triệu tệp.

time find /test/directory | xargs rm các kết quả:

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

so với

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

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

Tôi ngần ngại tưởng tượng những gì mà cuộc gọi toàn cầu () sẽ làm; Tôi giả sử nó làm một scandir (). Nếu vậy, điều đó sẽ khiến FOREVER trở lại. Một sửa đổi của đề xuất đầu tiên không đọc trước tất cả các mục dir có thể có một số chân; tuy nhiên, ở dạng hiện tại, nó cũng sẽ sử dụng một lượng CPU không đáng kể khi chỉ đọc tất cả các mục trong thư mục cùng một lúc. Một phần của mục tiêu ở đây là phân chia và chinh phục; mã này về cơ bản không khác biệt với 'rm -f * .png', bất kể các vấn đề với việc mở rộng hệ vỏ. Nếu nó giúp, không có gì trong thư mục mà tôi không muốn xóa.
BMDan

Tôi phải cố gắng nhiều hơn ngay khi đi làm. Tôi chỉ cố gắng tạo 100 000 tệp trong một thư mục và kết hợp + xargs + rm mất 7,3 giây, kết hợp Perl + unlink (global) ... kết thúc sau 2,7 giây. Đã thử rằng vài lần, kết quả luôn giống nhau. Trong công việc tôi sẽ thử điều này với nhiều tập tin hơn.
Janne Pikkarainen

Tôi đã học được một cái gì đó mới trong khi thử nghiệm điều này. Ít nhất là với ext3 và ext4, mục nhập thư mục vẫn rất lớn ngay cả sau khi xóa tất cả các tệp từ đó. Sau vài lần kiểm tra, thư mục / tmp / test của tôi đã chiếm 15 MB dung lượng đĩa. Có cách nào khác để dọn sạch nó ngoài việc xóa thư mục và tạo lại nó không?
Janne Pikkarainen

2
Không, bạn cần phải tạo lại nó. Tôi gặp phải điều này khi xử lý một hệ thống thư và thư mục cho mỗi người nhận và dọn dẹp sau những vấn đề quan trọng: không có cách nào khác ngoài việc tạo một thư mục mới và xáo trộn các thư mục về, sau đó gỡ bỏ thư mục cũ. Vì vậy, bạn có thể giảm cửa sổ thời gian khi không có thư mục, nhưng không loại bỏ nó.
Phil P

Lưu ý rằng global () sẽ sắp xếp các kết quả, giống như toàn cầu shell thường làm, vì vậy bạn chỉ có các tệp 100k, mọi thứ đều dễ dàng và sắp xếp nhanh chóng. Với một thư mục lớn hơn nhiều, bạn muốn opendir () / readdir () / closir () chỉ để tránh sắp xếp. [Tôi nói bình thường cho shell, vì zsh có một bộ sửa đổi toàn cục để làm cho thứ tự sắp xếp không được sắp xếp, rất hữu ích khi xử lý số lượng lớn tệp; *(oN)]
Phil P

1

Từ những gì tôi nhớ, việc xóa các nút trong hệ thống tập tin ext là O (n ^ 2), do đó, càng nhiều tệp bạn xóa thì phần còn lại sẽ càng nhanh.

Có một lần tôi đã phải đối mặt với vấn đề tương tự (mặc dù ước tính của tôi đã xem xét thời gian xóa ~ 7h), cuối cùng đã đi theo lộ trình đề xuất jftuga trong bình luận đầu tiên .


0

Chà, đây không phải là một câu trả lời thực sự, nhưng ...

Có thể chuyển đổi hệ thống tập tin thành ext4 và xem nếu mọi thứ thay đổi?


Có vẻ như việc thực hiện "trực tiếp" này đòi hỏi một fsck trên một hệ thống tập tin được gắn kết, đó là ... đáng báo động. Có cách nào tốt hơn không?
BMDan

Hệ thống tập tin phải được ngắt kết nối trước khi chuyển đổi, tức là trước các lệnh tunefs cần thiết.
marcoc

0

Được rồi, điều này đã được trình bày theo nhiều cách khác nhau trong phần còn lại của chủ đề nhưng tôi nghĩ rằng tôi sẽ ném vào hai xu của mình. Thủ phạm hiệu suất trong trường hợp của bạn có lẽ là readdir. Bạn đang nhận lại một danh sách các tệp không nhất thiết phải theo thứ tự trên đĩa, điều này gây ra sự truy cập đĩa khắp nơi khi bạn hủy liên kết. Các tệp đủ nhỏ để thao tác hủy liên kết có thể không nhảy quá nhiều khoảng trống. Nếu bạn readdir và sau đó sắp xếp theo inode tăng dần, bạn có thể sẽ có hiệu suất tốt hơn. Vì vậy, readdir vào ram (sắp xếp theo inode) -> hủy liên kết -> lợi nhuận.

Inode là một xấp xỉ thô ở đây tôi nghĩ rằng .. nhưng dựa trên trường hợp sử dụng của bạn, nó có thể khá chính xác ...


1
Sửa lỗi cho tôi nếu tôi sai, nhưng bỏ liên kết (2) không bằng inode, nó chỉ xóa tham chiếu đến nó khỏi thư mục. Tôi thích chutzpah của phương pháp này, mặc dù. Quan tâm để chạy một số thử nghiệm thời gian và xem nếu nó đúng?
BMDan

0

Tôi có thể đã đánh cắp trình biên dịch C và thực hiện tương đương đạo đức với kịch bản của bạn. Đó là, sử dụng opendir(3)để có được một thư mục xử lý, sau đó sử dụng readdir(3)để lấy tên của các tệp, sau đó kiểm tra các tệp khi tôi hủy liên kết chúng và thỉnh thoảng in "% d các tệp đã bị xóa" (và có thể là thời gian trôi qua hoặc dấu thời gian hiện tại).

Tôi không hy vọng nó sẽ nhanh hơn đáng kể so với phiên bản shell script, chỉ là tôi đã từng phải sử dụng trình biên dịch hết lần này đến lần khác, bởi vì không có cách nào rõ ràng để làm những gì tôi muốn từ shell hoặc bởi vì trong khi có thể thực hiện được trong vỏ, nó không hiệu quả làm chậm theo cách đó.


Anh ta ít nhất có thể bắt đầu bằng cách sửa đổi mã nguồn của rm từ coreutils .
Cristian Ciupitu

0

Bạn có khả năng chạy vào các vấn đề viết lại với thư mục. Hãy thử xóa các tập tin mới nhất trước. Nhìn vào các tùy chọn gắn kết sẽ trì hoãn ghi vào đĩa.

Đối với thanh tiến trình, hãy thử chạy một cái gì đó như rm -rv /mystuff 2>&1 | pv -brtl > /dev/null


Về mặt xóa các tập tin mới nhất trước tiên: ls -Ur? Tôi khá chắc chắn rằng sẽ tải các mục dir, sau đó đảo ngược chúng; Tôi không tin rằng ls đủ thông minh để bắt đầu ở cuối danh sách mục nhập và tua lại từ đầu. "ls -1" có lẽ cũng không phải là một ý tưởng hay, vì có thể sẽ mất hơn 50 MB lõi và vài phút để chạy; bạn muốn "ls -U" hoặc "ls -f".
BMDan

Nó chỉ có thể thực tế nếu tên tệp tăng theo mẫu có thể dự đoán được. Tuy nhiên, bạn hãy thử ls -1 đường ống để đảo ngược và chuyển sang xargs. Sử dụng tệp thay vì đường ống nếu bạn muốn xem kết quả trung gian của mình. Bạn đã không cung cấp bất kỳ thông tin về tên tập tin. Bạn sẽ tạo ra các patter ngược và xóa các tập tin bằng cách sử dụng mẫu. Bạn có thể cần phải xử lý các mục tập tin bị thiếu. Đưa ra nhận xét của bạn về bộ nhớ cần thiết, bạn có ý tưởng về yêu cầu I / O để viết lại thư mục.
BillThor

0

Đây là cách tôi xóa hàng triệu tệp theo dõi đôi khi có thể thu thập trên một máy chủ cơ sở dữ liệu Oracle lớn:

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

Tôi thấy rằng điều này dẫn đến việc xóa khá chậm, ảnh hưởng đến hiệu suất máy chủ thấp, thường là một thứ gì đó trong một giờ trên một triệu tệp trên thiết lập 10.000 IOPS "điển hình".

Nó thường sẽ mất vài phút trước khi các thư mục được quét, danh sách tệp ban đầu được tạo và tệp đầu tiên bị xóa. Từ đó trở đi, a. được lặp lại cho mỗi tập tin bị xóa.

Sự chậm trễ gây ra bởi tiếng vang đến thiết bị đầu cuối đã chứng minh đủ độ trễ để ngăn chặn bất kỳ tải đáng kể nào trong khi quá trình xóa đang diễn ra.


Bạn đang bị ăn sống bởi Globing. Làm thế nào về một cái gì đó giống như : 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'? Thêm -deletevào cái cuối cùng để thực sự xóa mọi thứ; như đã viết, nó chỉ liệt kê những gì nó sẽ xóa. Lưu ý rằng điều này được tối ưu hóa cho các trường hợp bạn có nhiều thứ không thú vị trong các thư mục gần đó; nếu đó không phải là trường hợp, bạn có thể đơn giản hóa logic rất nhiều.
BMDan

find -delete có xu hướng gây ra quá nhiều I / O và dễ dàng ảnh hưởng đến hiệu suất sản xuất. Có lẽ với ionice.
Roy

Tuy nhiên, điều đó gây ra tất cả những gì tôi / O đơn giản là hiệu quả hơn! Các globbing là tất cả phía trước nạp ví dụ của bạn (có nghĩa là, toàn bộ danh sách các tập tin được tạo ra trước khi là người đầu tiên rmxảy ra), vì vậy bạn phải tương đối hiệu quả I / O lúc khởi động từ đó, tiếp theo là đau đớn, out-of-trật tự rms điều đó có thể không gây ra nhiều I / O, nhưng liên quan đến việc scandirđi bộ thư mục nhiều lần (không gây ra I / O vì điều đó đã được tải vào bộ đệm ẩn; xem thêm vfs_cache_pressure). Nếu bạn muốn làm chậm mọi thứ, ionicelà một tùy chọn, nhưng tôi có thể sử dụng giây phân đoạn sleep.
BMDan

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +sẽ chạy một rmthư mục cho mỗi thư mục, do đó bạn có ít chi phí CPU hơn. Miễn là bạn có hàng tấn thời gian CPU dự phòng, điều chỉnh IO đĩa bằng cách tạo ra toàn bộ rmquá trình cho mọi unlinkcông việc, tôi đoán, nhưng nó thật xấu xí. perl với một giấc ngủ trên mỗi liên kết sẽ đẹp hơn nếu ngủ giữa rmtoàn bộ thư mục tại một thời điểm quá bùng nổ. ( -execdir sh -c ...có thể)
Peter Cordes

-1

Bạn có thể sử dụng các tính năng song song của 'xargs':

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

1
Điều này sẽ không giúp đỡ. Nút thắt là I / O ngẫu nhiên kém trên ổ đĩa. Thực hiện xóa song song có thể làm cho nó thậm chí còn tồi tệ hơn và chỉ cần tăng tải CPU.
Wim Kerkhoff

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

1
Ồ Tôi đoán rằng rơi khá chắc chắn trong trại "nhiều hơn một cách để da mèo". Nghiêm túc, mặc dù, với các loại và uniq? "Ls" sắp xếp theo mặc định, dù sao, và tôi chắc chắn hy vọng rằng tên tệp là duy nhất. : /
BMDan

-2

thực tế, cái này tốt hơn một chút nếu shell bạn sử dụng không mở rộng dòng lệnh:

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.