수백만 개의 파일이있는 디렉토리의 rm


104

배경 : 실제 서버, 약 2 년 된 3Ware RAID 카드에 연결된 7200RPM SATA 드라이브, ext3 FS 탑재 noatime 및 데이터 . 디렉토리에는 수백 개의 작은 (~ 100 바이트) 파일과 더 큰 (몇 KB) 파일이 포함 된 하위 디렉토리가 없습니다.

우리는 지난 몇 달 동안 약간의 뻐꾸기가 된 서버를 가지고 있지만, 너무 많은 파일을 포함하고 있기 때문에 디렉토리에 쓸 수 없었던 다른 날에만 서버를 발견했습니다. 특히, / var / log / messages에서이 오류가 발생하기 시작했습니다.

ext3_dx_add_entry: Directory index full!

문제의 디스크에는 많은 inode가 남아 있습니다.

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

그래서 그것은 우리가 디렉토리 파일 자체에있을 수있는 항목 수의 한계에 도달했음을 의미합니다. 얼마나 많은 파일이 될지 모릅니다. 그러나 3 백만 개 이상이 될 수 있습니다. 그게 좋은 게 아니에요 그러나 그것은 내 질문 중 하나입니다. 정확히 그 상한은 무엇입니까? 조정 가능합니까? 나는 외쳤다하기 전에에서-I는 조정을 원하는 아래로 ; 이 거대한 디렉토리는 모든 종류의 문제를 일으켰습니다.

어쨌든, 우리는 모든 파일을 생성하는 코드에서 문제를 추적하고 수정했습니다. 이제 디렉토리 삭제에 붙어 있습니다.

몇 가지 옵션은 다음과 같습니다.

  1. rm -rf (dir)

    나는 이것을 먼저 시도했다. 나는 눈에 띄는 영향없이 하루 반 동안 도망 간 후에 그것을 포기하고 죽였습니다.

  2. 디렉토리에서 unlink (2) : 고려할 가치가 있지만, unlink (2)를 통해 삭제하는 것보다 fsck를 통해 디렉토리 내의 파일을 삭제하는 것이 더 빠른지 여부가 문제입니다. 즉, 어떤 식 으로든, 나는 그 inode를 사용되지 않은 것으로 표시해야합니다. 이것은 물론 fsck가 / lost + found에있는 파일에 항목을 삭제하지 않도록 지시 할 수 있다고 가정합니다. 그렇지 않으면 방금 문제를 옮겼습니다. 다른 모든 관심사에 더하여, 이것에 대해 조금 더 읽은 후에, 내가 찾을 수있는 unlink (2) 변형 중 어느 것도 내가 거칠게 삭제할 수 없기 때문에 내부 FS 함수를 호출해야 할 것입니다 항목이있는 디렉토리 푸우
  3. while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )

    이것은 실제로 단축 버전입니다. 내가 실행중인 실제 파일은 진행률 보고서를 추가하고 삭제할 파일이 부족할 때 깨끗하게 중지하는 것입니다.

    수출 i = 0;
    시간 (while [true]; do
      LS -Uf | 헤드 -n 3 | grep -qF '.png'|| 단절;
      LS -Uf | 헤드 -n 10000 | xargs rm -f 2> / dev / null;
      수출 i = $ (($ i + 10000));
      에코 "$ i ...";
    완료)

    이것은 오히려 잘 작동하는 것 같습니다. 이 글을 쓰면서 지난 30 분 동안 26 만 개의 파일이 삭제되었습니다.

이제 질문이 있습니다.
  1. 위에서 언급했듯이 디렉토리 별 항목 제한은 조정 가능합니까?
  2. 왜에 의해 반환 된 목록의 첫 번째 하나 하나의 파일을 삭제 "실제 7m9.561s / 사용자 0m0.001s / SYS의 0m0.001s"을 했는가 ls -U, 그리고 그것은으로 처음 10,000 개 항목을 삭제 아마도 십분했다 3 번 명령을했지만 이제는 아주 행복하게 지내고 있습니까? 그 문제로 약 30 분 만에 260,000을 삭제했지만 이제 60,000 개를 더 삭제하는 데 15 분이 더 걸립니다. 왜 큰 속도로 스윙합니까?
  3. 이런 종류의 일을하는 더 좋은 방법이 있습니까? 디렉토리에 수백만 개의 파일을 저장하지 마십시오. 나는 그것이 어리 석다는 것을 안다. 그리고 그것은 내 시계에서 일어나지 않았을 것이다. 문제를 탐색하고 SF와 SO를 살펴보면 find몇 가지 자명 한 이유로 내 접근 방식보다 훨씬 빠르지 않은 많은 변형이 제공 됩니다. 그러나 Delete-via-fsck 아이디어에는 다리가 있습니까? 아니면 다른 것이 있습니까? 즉시 사용 가능한 (또는 잘 알려지지 않은 내부에서) 생각을 듣고 싶습니다.
작은 소설을 읽어 주셔서 감사합니다. 궁금한 점이 있으시면 언제든지 답변 드리겠습니다. 또한 최종 파일 수와 일단 삭제 스크립트가 실행 된 시간으로 질문을 업데이트합니다.

최종 스크립트 출력! :

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

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

따라서 3 시간 동안 3 백만 개의 파일이 약간 삭제되었습니다.


1
rm (GNU coreutils) 8.4에는 "-v, --verbose가 수행중인 작업을 설명합니다" 옵션이 있습니다 . 삭제중인 모든 파일이 표시됩니다.
Cristian Ciupitu

2
실제로, 그것은 진행률 표시 줄을 수행하는 깔끔한 방법입니다. 각 파일의 길이는 36 자 (36 + a '\ n')이므로 파서를 쉽게 작성할 수 있으며 printf ()는 저렴하고 rm 명령에는 이미로드 된 파일 이름이 있으며 특별한 성능 저하가 없습니다. 어쨌든 그런 식으로 "rm"을 얻을 수 없기 때문에 전체 shebang을 수행하는 것은 초보자가 아닌 것 같습니다. 그러나 10,000 개의 진행률 표시 줄로 매우 잘 작동 할 수 있습니다. 아마도 "." 백 파일마다?
BMDan

8
rm -rfv | pv -l >/dev/null. EPEL 저장소 에서 pv를 사용할 수 있어야 합니다.
Cristian Ciupitu

5
pv는 압도적으로 굉장합니다. 나는 잠에서 pv 설치의 흔적을 남긴다.
BMDan

나는 최근에 이와 똑같은 문제를 겪었다. 감사합니다!
richo

답변:


30

data=writeback마운트 옵션은 파일 시스템의 저널링을 방지하기 위해 시도 할 가치가있다. 삭제 시간에만 수행해야하지만 삭제 작업 중에 서버가 종료되거나 재부팅되는 경우 위험이 있습니다.

에 따르면 이 페이지 ,

일부 응용 프로그램은 사용될 때 속도가 크게 향상됩니다. 예를 들어, 응용 프로그램이 많은 양의 작은 파일을 작성하고 삭제할 때 속도 향상을 볼 수 있습니다 (...).

이 옵션은 fstab마운트 작업 중 또는 마운트 작업 중으로 설정되며로 대체 data=ordered됩니다 data=writeback. 삭제할 파일이 포함 된 파일 시스템을 다시 마운트해야합니다.


1
또한 commit 옵션 에서 시간을 늘릴 수도 있습니다 . "이 기본값 (또는 낮은 값)은 성능을 저하 시키지만 데이터 안전에 좋습니다. 0으로 설정하면 기본값 (5 초)으로 두는 것과 같은 효과가 있습니다. 매우 큰 값으로 설정하면 성능이 향상됩니다. "
Cristian Ciupitu

1
필자 가보고 있는 문서 ( gentoo.org/doc/en/articles/l-afig-p8.xml#doc_chap4 )를 제외하고는 Writeback이 더 훌륭해 보입니다. 메타 데이터를 여전히 저널링한다고 언급합니다. 변경 (확실히 파일 자체의 데이터는 변경하지 않습니다). 옵션에 대한 이해가 잘못 되었습니까?
BMDan

마지막으로, 해당 링크에서 언급되지 않은 FYI는 data = writeback이 주어진 항목이 가리키는 데이터에 앱이 작성한 데이터가 없을 수 있기 때문에 큰 보안 허점이 될 수 있다는 사실입니다. 이전에 민감하거나 개인 정보가 노출 될 수 있습니다. 여기서는 일시적으로 사용하기 때문에 걱정하지 않아도되지만 제안을 가로 질러 실행하는 다른 사람이 알지 못하는 경우 모든 사람에게 경고해야합니다.
BMDan

커밋 : 그것은 꽤 매끄러운입니다! 포인터 주셔서 감사합니다.
BMDan

2
data=writeback여전히 메타 데이터를 기본 파일 시스템에 쓰기 전에 저널링합니다. 내가 이해하는 것처럼 익스텐트 맵 작성 및 익스텐트에 데이터 작성과 같은 것들 사이의 순서를 강제하지는 않습니다. 이것으로부터 perf gain을 보았을 때 완화되는 다른 순서 제약이있을 수 있습니다. 물론, 저널없이 마운트하는 것이 훨씬 더 높은 성능 일 수 있습니다. (링크 해제 op가 완료되기 전에 디스크에 아무 것도 가질 필요없이 메타 데이터 변경이 RAM에서 발생하도록 할 수 있습니다).
Peter Cordes

80

이 문제의 주요 원인은 수백만 파일의 ext3 성능이지만이 문제의 실제 근본 원인은 다릅니다.

디렉토리를 나열해야 할 경우 파일 목록을 생성하는 디렉토리에서 readdir ()이 호출됩니다. readdir은 posix 호출이지만 여기서 사용되는 실제 Linux 시스템 호출은 'getdents'입니다. Getdents는 항목으로 버퍼를 채워서 디렉토리 항목을 나열합니다.

문제는 주로 readdir ()이 32Kb의 고정 버퍼 크기를 사용하여 파일을 가져 오는 것입니다. 디렉토리가 점점 커짐에 따라 (파일이 추가됨에 따라 크기가 커짐) ext3은 항목을 가져 오는 데 느리고 느리게되고 추가 readdir의 32Kb 버퍼 크기는 디렉토리에 항목의 일부를 포함하기에 충분합니다. 이로 인해 readdir이 반복해서 반복되어 값 비싼 시스템 호출을 반복해서 호출합니다.

예를 들어, 내부에 260 만 개가 넘는 파일로 만든 테스트 디렉토리에서 "ls -1 | wc-l"을 실행하면 많은 getdent 시스템 호출의 큰 strace 출력이 표시됩니다.

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

또한이 디렉토리에 소요 된 시간은 상당했습니다.

$ time ls -1 | wc -l
2616044

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

이를보다 효율적인 프로세스로 만드는 방법은 훨씬 큰 버퍼로 수동으로 getdents를 호출하는 것입니다. 이것은 성능을 크게 향상시킵니다.

지금, 당신은 자신이 직접 그래서 인터페이스는 일반적으로 그것을 사용하는 존재하지 getdents 전화를 안하고 (볼 getdents에 대한 man 페이지를 확인!) 그러나 당신이 할 수 수동으로 전화하여 시스템 콜 호출 방법을보다 효율적으로.

이렇게하면 이러한 파일을 가져 오는 데 걸리는 시간이 크게 줄어 듭니다. 나는 이것을하는 프로그램을 썼다.

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

이것은 근본적인 근본적인 문제 (파일 시스템에서 성능이 떨어지는 파일 시스템의 많은 파일)와 싸우지 않습니다. 게시 된 많은 대안보다 훨씬 빠를 가능성이 큽니다.

예상대로 영향을받는 디렉토리를 제거하고 나중에 다시 만들어야합니다. 디렉토리는 크기가 커질뿐 디렉토리의 크기 때문에 파일이 몇 개 있어도 성능이 저하 될 수 있습니다.

편집 : 나는 이것을 꽤 정리했습니다. 런타임에 명령 행에서 삭제할 수있는 옵션을 추가하고 솔직히 되돌아 보면 문제가되는 트리 워크 항목을 제거했습니다. 또한 메모리 손상을 일으키는 것으로 나타났습니다.

이제 할 수 있습니다 dentls --delete /my/path

새로운 결과. 182 만 개의 파일이있는 디렉토리를 기반으로합니다.

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

이것이 여전히 잘 작동한다는 것에 놀랐습니다!


1
두 가지 사소한 문제 : 하나 [256]는 아마 있어야 [FILENAME_MAX]하며 두 번째는 내 Linux (2.6.18 == CentOS 5.x)가 d_type 항목을 dirent에 포함하지 않는 것 같습니다 (적어도 getdents (2)에 따름).
BMDan

1
btree 리 밸런싱에 대해 조금 자세히 설명해 주시고 왜 삭제를 방지하는 데 도움이됩니까? 안타깝게도 인터넷 검색을 시도했습니다.
ovgolovin

1
순서대로 삭제하는 경우 나에게 보이는 것처럼 보이기 때문에 한쪽에서 나뭇잎을 제거하고 다른 쪽에서 나뭇잎을 제거하면서 재조정을 강제합니다. en.wikipedia.org/wiki/B-tree#Rebalancing_after_deletion
ovgolovin

1
이 문제로 당신을 귀찮게하지 않기를 바랍니다. 그러나 여전히 파일을 순서대로 삭제하는 것에 대한 질문을 시작했습니다. 스택 오버 플로우 .com / q / 17955459 / 862380. 이 예제의 문제를 설명하는 답변을받지 못하는 것 같습니다. 일반 프로그래머에게는 이해할 수 있습니다. 시간이 있고 그렇게 느낀다면 살펴볼 수 있습니까? 더 나은 설명을 쓸 수있을 것입니다.
ovgolovin

2
이것은 놀라운 코드입니다. 디렉토리에 구축 된 약 11,000,000 (1,100 만) 개의 세션 파일을 나열하고 삭제할 수있는 유일한 도구였습니다. 여기에 다른 답변의 find 및 기타 트릭을 사용하여 제어 할 수있는 Plesk 프로세스가 실행을 완료 할 수 없으므로 파일이 계속 빌드되었습니다. 파일 시스템이 디렉토리를 저장하는 데 사용하는 이진 트리에 대한 찬사이며 세션이 전혀 작동하지 않았으므로 파일을 만들고 지연없이 검색 할 수 있습니다. 목록을 사용할 수 없었습니다.
Jason

31

이 파일 시스템에서 다른 모든 파일을 임시 저장 위치로 백업하고 파티션을 다시 포맷 한 다음 파일을 복원 할 수 있습니까?


3
나는 실제로이 답변을 좋아합니다. 실질적인 문제로,이 경우에는 아닙니다. 그러나 제가 생각했던 것은 아닙니다. 브라보!
BMDan

정확히 내가 생각했던 것. 이것은 질문 3에 대한 답변입니다. 당신이 저에게 묻는다면 이상적입니다 :)
Joshua

12

ext3에는 디렉토리 당 파일 제한이 없으며 파일 시스템 inode 제한 만 있습니다 (하위 디렉토리의 수에는 제한이 있다고 생각합니다).

파일을 제거한 후에도 여전히 문제가있을 수 있습니다.

디렉토리에 수백만 개의 파일이 있으면 디렉토리 항목 자체가 매우 커집니다. 모든 제거 작업에 대해 디렉토리 항목을 스캔해야하며 항목 위치에 따라 각 파일에 대해 다양한 시간이 걸립니다. 불행히도 모든 파일을 제거한 후에도 디렉토리 항목의 크기가 유지됩니다. 따라서 디렉토리가 비어있는 경우에도 디렉토리 항목을 스캔해야하는 추가 조작은 여전히 ​​오랜 시간이 걸립니다. 이 문제를 해결하는 유일한 방법은 디렉토리의 이름을 바꾸고 이전 이름으로 새 디렉토리를 작성하고 나머지 파일을 새 디렉토리로 전송하는 것입니다. 그런 다음 이름을 바꾼 것을 삭제하십시오.


실제로, 나는 모든 것을 삭제 한 후이 동작을 발견했습니다. 운 좋게도, 우리는 디렉토리를 이미 "불의 행"에서 mv로 만들었습니다.
BMDan

2
즉, 디렉토리 별 파일 제한이 없으면 왜 "ext3_dx_add_entry : 디렉토리 색인이 가득 찼습니다!"라는 메시지가 나타납니다. 해당 파티션에서 여전히 사용할 수있는 inode가있을 때 이 디렉토리 안에 하위 디렉토리가 없습니다.
BMDan

3
흠 나는 좀 더 많은 연구를했는데 디렉토리가 취할 수있는 블록의 수가 제한되어있는 것 같습니다. 정확한 파일 수는 파일 이름 길이와 같은 몇 가지 사항에 따라 다릅니다. 이 gossamer-threads.com/lists/linux/kernel/921942 는 4k 블록으로 디렉토리에 8 백만 개 이상의 파일을 가질 수 있음을 나타냅니다. 파일 이름이 특히 길었습니까?
Alex J. Roberts

각 파일 이름은 정확히 36 자입니다.
BMDan

물론 그 :) 아이디어에서 저예요
알렉스 J. 로버츠


4

위의 사용자가 제안한대로 ext3 fs의 매개 변수를 변경 한 후에도 간단하게 작동하지 않습니다. 너무 많은 메모리를 소비했습니다. 이 PHP 스크립트는 빠르고 중요하지 않은 CPU 사용량, 중요하지 않은 메모리 사용량을 트릭했습니다.

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

find와 함께이 문제에 관한 버그 보고서를 게시했습니다 : http://savannah.gnu.org/bugs/?31961


이것은 나를 구했다!!
jestro

3

나는 최근 비슷한 문제에 직면하여 ring0의 data=writeback제안 을 얻을 수 없었습니다 (아마도 파일이 내 주 파티션에 있다는 사실 때문에). 해결 방법을 조사하는 동안 나는 이것을 우연히 발견했습니다.

tune2fs -O ^has_journal <device>

data옵션 은 옵션에 관계없이 저널링을 완전히 해제 합니다 mount. 나는 이것을 결합 noatime하고 볼륨을 dir_index설정했으며 꽤 잘 작동하는 것 같습니다. 삭제하지 않아도 실제로 삭제가 완료되고 시스템이 응답 상태를 유지하며 문제없이 백업 및 실행 (저널링을 다시 사용)합니다.


메타 데이터 op를 저널링하지 않기 위해 ext3 대신 ext2로 마운트하는 것이 좋습니다. 이것은 똑같이해야합니다.
Peter Cordes

3

당신이해야합니다 :

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

속도도 약간 빨라져야합니다.


4
좋은 전화이지만 질문의 머리글에서 언급했듯이 이미 noatime이 마운트되어 있습니다. 그리고 nodiratime은 중복 적입니다. lwn.net/Articles/245002를 참조하십시오 .
BMDan

1
PPL이 만트라를 반복 "하는 noatime, nodiratime, nodevatime, noreadingdocsatime"
poige

2

매우 느린 명령입니다. 시험:

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

rm -rf는 하루 반 동안 달렸고, 실제로 무언가를 달성했는지 알지 못하고 마침내 그것을 죽였습니다. 진행률 표시 줄이 필요했습니다.
BMDan

4
rm이 매우 느린 경우 30k 파일에서 "time find. -delete": 0m0.357s / 0m0.019s / 0m0.337s real / user / sys. 같은 파일에있는 "time (ls -1U | xargs rm -f)": 0m0.366s / 0m0.025s / 0m0.340s. 기본적으로 오류 마진 영역입니다.
BMDan

1
strace -r -p <pid of rm>이미 실행중인 rm 프로세스에 연결하기 위해 실행했을 수 있습니다 . 그런 다음 unlink시스템 호출이 얼마나 빨리 스크롤 되는지 확인할 수 있습니다 . ( -r모든 라인의 시작 부분에서 이전 시스템 호출 이후 시간이 걸립니다.)
Peter Cordes

2

되어 dir_index파일 시스템 설정? ( tune2fs -l | grep dir_index) 그렇지 않은 경우 활성화하십시오. 일반적으로 새로운 RHEL을 사용합니다.


1
예, 가능하지만 멋진 제안입니다!
BMDan

2

몇 년 전, 파일 시스템 에서 1600 만 개의 XML 파일이 있는 디렉토리를 찾았습니다 /. 서버에 대한 비판으로 인해 다음 명령을 사용하여 완료 하는 데 약 30 시간 이 걸렸 습니다.

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

이전 7200 rpm hdd 였고 IO 병목 현상과 CPU 급증에도 불구하고 이전 웹 서버는 계속 서비스를 계속했습니다.


1

내가 선호하는 옵션은 이미 제안한 newfs 접근 방식입니다. 기본 문제는 이미 언급했듯이 삭제를 처리하기위한 선형 스캔에 문제가 있다는 것입니다.

rm -rf로컬 파일 시스템에 대해 거의 최적이어야합니다 (NFS는 다름). 그러나 파일 이름 당 36 바이트와 inode 당 4 바이트 (ext3의 값을 확인하지 않는 것으로 추측)는 40 * 수백만이며 디렉토리의 RAM에만 유지됩니다.

추측에 따르면 Linux에서 파일 시스템 메타 데이터 캐시 메모리를 스 래싱하여 다른 부분을 사용하는 동안 디렉토리 파일의 한 페이지에 대한 블록이 정리되고 다음 부분이있을 때만 캐시의 해당 페이지를 다시 누르기 위해 파일이 삭제됩니다. 리눅스 성능 튜닝은 제 영역이 아니지만 / proc / sys / {vm, fs} /는 아마도 관련이있을 것입니다.

가동 중지 시간을 감당할 수 있으면 dir_index 기능을 설정하는 것이 좋습니다. 큰 디렉토리 (해시 된 b- 트리)에서 삭제하기 위해 디렉토리 색인을 선형에서 훨씬 더 최적으로 전환합니다. tune2fs -O dir_index ...그 다음에 e2fsck -D작동합니다. 그러나 이것이 문제가 발생 하기 전에 도움이 될 것이라고 확신하지만 -D기존 v.large 디렉토리를 처리 할 때 변환 (e2fsck 사용 )이 수행되는 방식을 모르겠습니다. 백업 + 빨래보기.


1
pubbs.net/201008/squid/…/proc/sys/fs/vfs_cache_pressure사용할 가치가 있다고 제안 하지만 디렉토리 자체가 페이지 캐시 (그것이 무엇인지) 또는 inode 캐시를 계산하는지 여부를 알지 못합니다. inode, 그것은 FS 메타 데이터이며 그 이유로 번들되어 있습니다). 내가 말했듯이 Linux VM 튜닝은 내 영역이 아닙니다. 무엇을 도와주고 도움이되는지보십시오.
Phil P

1

분명히 사과는 사과가 아니지만 약간의 테스트를 설정하고 다음을 수행했습니다.

디렉토리 ( dd/dev/urandom루프)에 100,000 512 바이트 파일을 작성했습니다 . 시간을 잊어 버렸지 만 파일을 만드는 데 약 15 분이 걸렸습니다.

해당 파일을 삭제하려면 다음을 실행하십시오.

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

100000

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

이것은 Pentium 4 2.8GHz 상자입니다 (커플 백 GB IDE 7200 RPM, EXT3). 커널 2.6.27.


흥미 롭다면 아마도 파일이 오랜 기간 동안 생성되었다는 사실이 관련이 있습니까? 그러나 그것은 중요하지 않습니다. 블록 캐시에는 RAM에있는 모든 관련 메타 데이터 블록이 있어야합니다. 아마도 unlink (2)가 트랜잭션이기 때문일까요? 귀하의 추정에 따르면, rm 기간 동안 저널링을 해제하면 잠재적으로 (어쩌면 다소 위험한) 솔루션입니까? tune2fs / fsck / reboot없이 마운트 된 파일 시스템에서 저널링을 완전히 해제 할 수있는 것처럼 보이지 않습니다.
BMDan

나는 그것에 대해 언급 할 수는 없지만 일화 적으로 (수년에 걸친 다양한 NIX 토론에서), rm많은 파일에서 find -delete옵션이 너무 느리다는 것을 항상 들었습니다 . 쉘에 와일드 카드를 사용하면 일치하는 각 파일 이름이 확장되고 메모리 버퍼가 제한되어 있다고 가정하므로 비효율적 인 방법을 알 수 있습니다.
gravyface

1
rm은 이름별로 파일을 찾고 있기 때문에 속도가 느릴 것입니다. 즉, 디렉토리 항목을 찾을 때까지 하나씩 반복합니다. 그러나이 경우, 전달되는 각 항목이 목록의 첫 번째 항목 (ls -U / ls -f)이므로 거의 빠릅니다. 즉, 챔피언처럼 뛰어야했던 rm -rf <dir>은 가능한 느리다. 대규모 삭제 속도를 높이기 위해 coreutils에 패치를 작성해야 할 때가 있습니까? 아마도 rm -rf를 구현하기 위해 비밀리에 재귀 적 방식으로 globbing / 정렬하는 것입니까? 이와 같은 불확실성이 내가 질문을 한 이유입니다. ;)
BMDan

1
작성 단계를 실행 한 후 머신을 재부팅하십시오. 삭제 속도가 현저히 느려집니다.
Matt

1

때때로 Perl은 이와 같은 경우에 놀라운 일을 할 수 있습니다. 이와 같은 작은 스크립트가 bash 및 기본 쉘 명령을 능가 할 수 있는지 이미 시도 했습니까?

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

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

또는 또 다른, 아마도 더 빠른 Perl 접근법 :

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

편집하다: 방금 Perl 스크립트를 사용해 보았습니다. 더 장황한 것이 옳은 일을합니다. 필자의 경우 256MB RAM과 50 만 개의 파일이있는 가상 서버로 시도했습니다.

time find /test/directory | xargs rm 결과 :

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

에 비해

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

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

glob () 호출이 무엇을하는지 상상해보십시오. 나는 그것이 scandir ()을한다고 가정합니다. 그렇다면 다시 돌아 오는 것입니다. 모든 dir 항목을 미리 읽지 않은 첫 번째 제안의 수정에는 다리가있을 수 있습니다. 그러나 현재 형식으로도 모든 디렉토리 항목을 한 번에 읽는 데 부정한 양의 CPU를 사용합니다. 여기서 목표의 일부는 나누고 정복하는 것입니다. 이 코드는 쉘 확장 문제에도 불구하고 기본적으로 'rm -f * .png'와 다르지 않습니다. 도움이된다면 디렉토리 에 삭제하고 싶지 않은 것이 없습니다 .
BMDan

일하자마자 더 많은 것을 시도해야합니다. 방금 단일 디렉토리에 100 000 개의 파일을 만들려고했는데 + xargs + rm 조합에 7.3 초가 걸렸고 Perl + unlink (glob) ... 조합이 2.7 초 안에 완료되었습니다. 몇 번 시도했지만 결과는 항상 동일했습니다. 직장에서 나는 더 많은 파일로 이것을 시도 할 것입니다.
Janne Pikkarainen

이것을 테스트하면서 새로운 것을 배웠습니다. 적어도 ext3 및 ext4의 경우 디렉토리 항목 자체는 모든 파일을 삭제 한 후에도 여전히 크게 남아 있습니다. 몇 번의 테스트 후 내 / tmp / test 디렉토리는 15MB의 디스크 공간을 차지했습니다. 디렉토리를 제거하고 다시 만드는 것 외에 다른 방법으로 정리할 수 있습니까?
Janne Pikkarainen

2
아니요, 다시 만들어야합니다. 중요한 문제가 발생한 후 메일 시스템 및 수신자 당 폴더 및 정리를 처리 할 때이 문제를 해결했습니다. 새 디렉토리를 만들고 디렉토리를 섞은 다음 이전 디렉토리를 압축하는 것 외에는 다른 방법이 없습니다. 따라서 디렉토리가 없을 때 시간 창을 줄일 수 있지만 제거 할 수는 없습니다.
Phil P

쉘 globbing이 일반적으로하는 것처럼 glob ()는 결과를 정렬하므로 100k 파일 만 있기 때문에 모든 것이 쉽게 맞고 정렬이 빠릅니다. 훨씬 더 큰 디렉토리를 사용하면 정렬을 피하기 위해 opendir () / readdir () / closedir ()을 원할 것입니다. [ zsh는 정렬 순서를 분류하지 않기 위해 glob 수정자를 가지고 있기 때문에 일반적 으로 쉘에 대해 말한다 . 이는 많은 수의 파일을 다룰 때 유용하다. *(oN)]
Phil P

1

내가 ext 파일 시스템에서 inode를 삭제하는 것은 O (n ^ 2)이므로 기억할 파일이 많을수록 나머지 파일은 더 빠릅니다.

비슷한 문제에 직면 한 적이있었습니다 (내 추정치가 ~ 7 시간 삭제 시간을 보았지만). 결국 jftuga 가 첫 번째 주석에서 경로 제안 했습니다 .


0

글쎄, 이것은 진정한 대답은 아니지만 ...

파일 시스템을 ext4로 변환하고 변경 사항이 있는지 확인할 수 있습니까?


이 "실시간"작업을 수행하려면 마운트 된 파일 시스템에서 fsck가 필요합니다. 더 나은 방법이 있습니까?
BMDan

변환 전에, 즉 필요한 tunefs 명령 전에 파일 시스템을 마운트 해제해야합니다.
marcoc

0

자, 이것은 실의 나머지 부분에서 다양한 방법으로 다루어졌지만 나는 2 센트를 던질 것이라고 생각했습니다. 귀하의 경우 성능 범인은 아마도 readdir입니다. 연결을 끊을 때 디스크에 액세스하는 원인이되는 디스크에서 반드시 순차적 인 파일 목록을 다시 가져옵니다. 연결 해제 작업이 공간을 너무 많이 제로화하지 않을 정도로 파일이 작습니다. readdir을 읽고 inode를 오름차순으로 정렬하면 아마도 더 나은 성능을 얻을 수 있습니다. 따라서 readdir을 램으로 (inode로 정렬)-> unlink-> 이익.

Inode는 여기에 대략적인 근사치입니다.


1
내가 틀렸다면 정정하지만 unlink (2)는 inode를 0으로 만들지 않고 디렉토리에서 참조를 제거합니다. 그래도 나는이 접근법의 추 츠파를 좋아한다. 시련을 실행하고 그것이 사실인지 확인 하시겠습니까?
BMDan

0

아마 C 컴파일러를 꺼내서 도덕적으로 동등한 스크립트를 수행했을 것입니다. 즉, opendir(3)디렉토리 핸들 readdir(3)을 가져오고 파일 이름을 가져 오는 데 사용하고 링크 를 해제 할 때 파일을 집계하고 "% d files deleted"(및 경과 된 시간 또는 현재 타임 스탬프)를 인쇄합니다.

쉘 스크립트 버전보다 눈에 띄게 빠를 것으로 기대하지는 않습니다. 쉘에서 원하는 것을 수행하는 명확한 방법이 없기 때문에 컴파일러를 반복해서 제거해야한다는 것입니다. 쉘에서 할 수는 있지만 비생산적으로 느립니다.


최소한 coreutils 에서 rm의 소스 코드를 수정하여 시작할 수있었습니다 .
Cristian Ciupitu

0

디렉토리에 다시 쓰기 문제가 발생했을 수 있습니다. 최신 파일을 먼저 삭제하십시오. 디스크에 쓰기 저장을 지연시키는 마운트 옵션을 확인하십시오.

진행률 표시 줄에 대해 다음과 같은 것을 실행하십시오. rm -rv /mystuff 2>&1 | pv -brtl > /dev/null


최신 파일을 먼저 삭제하는 관점에서 : ls -Ur? 나는 그것이 dir 항목을로드 한 다음 뒤집을 것이라고 확신합니다. 나는 ls가 dir entry list의 끝에서 시작하여 처음으로 다시 감을만큼 똑똑하지 않다고 생각합니다. "ls -1"은 아마도 50MB 이상의 코어와 실행하는 데 몇 분이 걸리기 때문에 좋은 생각이 아닙니다. "ls -U"또는 "ls -f"를 원할 것입니다.
BMDan

파일 이름이 예측 가능한 패턴으로 증가하는 경우에만 실용적입니다. 그러나 당신은 내 시도 ls -1을 반대로 파이프하고 xargs로 파이프했습니다. 중간 결과를 보려면 파이프 대신 파일을 사용하십시오. 파일 이름에 대한 정보를 제공하지 않았습니다. 후두둑을 반대로 생성하고 패턴을 사용하여 파일을 삭제합니다. 누락 된 파일 항목을 처리해야 할 수도 있습니다. 필요한 메모리에 대한 의견이 있으면 I / O가 디렉토리를 다시 작성해야한다는 아이디어가 있습니다.
BillThor

0

다음은 대규모 Oracle 데이터베이스 서버에서 수집 할 수있는 수백만 개의 추적 파일을 삭제하는 방법입니다.

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

이로 인해 삭제 속도가 느려 서버 성능에 미치는 영향이 적으며 일반적으로 "일반적인"10,000 IOPS 설정에서 백만 파일 당 1 시간짜리 파일이 표시됩니다.

디렉토리가 스캔되고 초기 파일 목록이 생성되고 첫 번째 파일이 삭제되기까지 몇 분이 걸릴 수 있습니다. 거기서부터. 삭제 된 모든 파일에 대해 에코됩니다.

터미널에 반향으로 인한 지연은 삭제가 진행되는 동안 상당한로드를 방지하기에 충분한 지연으로 입증되었습니다.


당신은 globbing에 의해 살아있는 먹고있다. 어떻 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'습니까? -delete실제로 항목을 삭제하려면 마지막 항목에 추가하십시오 . 서면으로, 그것은 단지 삭제할 내용을 나열합니다. 이것은 가까운 디렉토리에 많은 관심없는 것들이있는 환경에 최적화되어 있습니다. 그렇지 않은 경우 논리를 크게 단순화 할 수 있습니다.
BMDan

find -delete는 너무 많은 I / O를 유발하는 경향이 있으며 생산 성능에 쉽게 영향을줍니다. 아마도 ionice와 함께.
Roy

그래도 더 효율적으로 모든 I / O를 발생시킵니다! 글 로빙은 예제를 위해 모두 전면로드되므로 (즉, 전체 파일 목록이 처음 rm발생 하기 전에 생성됨 ) 시작시 상대적으로 효율적인 I / O가 있고 고통스럽고 순서가 rm잘못됩니다. 아마도 많은 I / O를 유발하지는 않지만 scandir디렉토리를 반복적으로 걷는 것을 포함 합니다 (I / O는 이미 블록 캐시에로드되어 있기 때문에 I / O를 유발하지 않습니다. 참조 vfs_cache_pressure). 당신이 일을 늦추고 싶다면 ionice옵션이지만, 아마도 분수 초를 사용할 것입니다 sleep.
BMDan

find /u*/app/*/diag -path '*/trace/*.tr' -execdir rm {} +rm디렉토리 당 하나씩 실행 하므로 CPU 오버 헤드가 줄어 듭니다. rm모든 unlink작업에 대해 전체 프로세스를 forking하여 디스크 IO를 조절하는 데 많은 CPU 시간이 필요하다면 추악합니다. rm한 번에 전체 디렉토리 사이에서 휴면 상태 가 너무 크면 연결 해제 당 휴면 상태가있는 perl이 더 좋습니다. ( -execdir sh -c ...어쩌면)
피터 코르

-1

'xargs'병렬화 기능을 사용할 수 있습니다.

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

1
도움이되지 않습니다. 병목 현상은 드라이브에서 불량한 임의 I / O입니다. 병렬 삭제를 수행하면 상황이 더욱 악화되고 CPU로드가 증가 할 수 있습니다.
Wim Kerkhoff

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

1
와. 나는 그것이 "고양이를 껍질을 벗기는 한 가지 이상의 방법"캠프에 꽤 빠졌다고 생각한다. 하지만 진심으로, 정렬과 유니크? 어쨌든 "ls"는 기본적으로 정렬되며 파일 이름이 고유하기를 바랍니다. : /
BMDan

-2

실제로, 사용하는 쉘이 명령 행 확장을 수행하는 경우이 방법이 약간 더 좋습니다.

ls|cut -c -4|sort|uniq|awk '{ print "echo " $1 ";rm -rf " $1 "*"}' |sh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.