ext4에 비해 디렉토리의 파일이 너무 많습니까?

ext4에 비해 디렉토리의 파일이 너무 많습니까?

나는 100개의 새 파일을 생성하고 2,000개의 파일이 포함된 디렉터리와 200,000개의 파일이 포함된 디렉터리에서 100개의 기존 파일을 읽는 데 걸리는 시간을 측정하기 위해 Golang 프로그램을 작성했습니다.

// Create 200k files in one directory vs 200k files in 100 separate directories
// See if speed of accessing files is affected
package main

import (
    "fmt"
    "log"
    "os"
    "time"

    "github.com/1f604/util"
)

func main() {
    // First, create 100 directories
    filepaths := []string{}
    for i := 0; i < 100; i++ {
        newfilepath := "/tmp/dir" + util.Int64_to_string(int64(i)) + "/"
        filepaths = append(filepaths, newfilepath)
        err := os.MkdirAll(newfilepath, os.ModePerm)
        util.Check_err(err)
    }
    fmt.Println("Created 100 directories.")
    // Next, create 2k files in each directory
    fmt.Println("Now creating 2k x 10kb files in each small directory.")
    for i := 0; i < 100; i++ {
        for j := 0; j < 2000; j++ {
            f, err := os.Create("/tmp/dir" + util.Int64_to_string(int64(i)) + "/" + util.Int64_to_string(int64(j)) + ".txt")
            if err != nil {
                log.Fatal(err)
            }

            if err := f.Truncate(1e4); err != nil {
                log.Fatal(err)
            }
        }
    }

    // Next, create 200k files in one directory
    fmt.Println("Now creating 200k x 10kb files in one big directory.")
    for j := 0; j < 200000; j++ {
        f, err := os.Create("/tmp/bigdir/" + util.Int64_to_string(int64(j)) + ".txt")
        if err != nil {
            log.Fatal(err)
        }

        if err := f.Truncate(1e4); err != nil {
            log.Fatal(err)
        }
    }

    // Now time read and write times
    fmt.Println("Now creating 100 x 10kb files in a small directory.")
    start := time.Now()
    for j := 0; j < 100; j++ {
        f, err := os.Create("/tmp/dir1/test" + util.Int64_to_string(int64(j)) + ".txt")
        if err != nil {
            log.Fatal(err)
        }

        if err := f.Truncate(1e4); err != nil {
            log.Fatal(err)
        }
    }
    fmt.Println("Time taken:", time.Now().Sub(start))

    fmt.Println("Now reading 100 random 10kb files in a small directory.")
    start = time.Now()
    list := [][]byte{}
    for j := 0; j < 100; j++ {
        num, err := util.Crypto_Randint(2000)
        util.Check_err(err)
        contents, err := os.ReadFile("/tmp/dir2/" + util.Int64_to_string(int64(num)) + ".txt")
        if err != nil {
            log.Fatal(err)
        }
        list = append(list, contents)
    }
    fmt.Println("Time taken:", time.Now().Sub(start))

    fmt.Println("Now creating 100 x 10kb files in a big directory.")
    start = time.Now()
    for j := 0; j < 100; j++ {
        f, err := os.Create("/tmp/bigdir/test" + util.Int64_to_string(int64(j)) + ".txt")
        if err != nil {
            log.Fatal(err)
        }

        if err := f.Truncate(1e4); err != nil {
            log.Fatal(err)
        }
    }
    fmt.Println("Time taken:", time.Now().Sub(start))

    fmt.Println("Now reading 100 random 10kb files in a big directory.")
    start = time.Now()
    for j := 0; j < 100; j++ {
        num, err := util.Crypto_Randint(200000)
        util.Check_err(err)
        contents, err := os.ReadFile("/tmp/bigdir/" + util.Int64_to_string(int64(num)) + ".txt")
        if err != nil {
            log.Fatal(err)
        }
        list = append(list, contents)
    }
    fmt.Println("Time taken:", time.Now().Sub(start))

    start = time.Now()
}

Debian 12(ext4) 시스템에서의 결과는 다음과 같습니다.

Created 100 directories.
Now creating 2k x 10kb files in each small directory.
Now creating 200k x 10kb files in one big directory.
Now creating 100 x 10kb files in a small directory.
Time taken: 2.361316ms
Now reading 100 random 10kb files in a small directory.
Time taken: 5.792292ms
Now creating 100 x 10kb files in a big directory.
Time taken: 2.922209ms
Now reading 100 random 10kb files in a big directory.
Time taken: 3.835541ms

큰 디렉터리에서 무작위로 100개의 파일을 읽는 것이 작은 디렉터리에서 무작위로 100개의 파일을 읽는 것보다 항상 더 빠르지만 이것이 어떻게 가능합니까?

내 벤치마크 코드가 올바르지 않나요?

감사해요.

업데이트: 파일을 생성한 후 페이지 캐시를 새로 고치라는 @Paul_Pedant의 제안을 적용한 후 완전히 다른 결과를 얻었습니다!

이것이 나의 새로운 결과입니다:

Now creating 100 x 10kb files in a small directory.
Time taken: 19.475348ms
Now reading 100 random 10kb files in a small directory.
Time taken: 26.309475ms
Now creating 100 x 10kb files in a big directory.
Time taken: 75.570411ms
Now reading 100 random 10kb files in a big directory.
Time taken: 152.495391ms

이는 내가 이전에 본 놀라운 결과가 단순히 페이지 캐시 효과 때문이었음을 시사합니다. 200K 파일 디렉터리에서 100개의 무작위 파일을 읽는 것이 실제로 2K 파일 디렉터리에서 100개의 무작위 파일을 읽는 것보다 훨씬 느렸습니다(152ms 대 26ms).

업데이트: 동일한 작은 디렉터리에서 100개 파일 모두에 액세스했기 때문에 초기 테스트가 공정하지 않았다는 것을 알고 있지만 실제 시나리오에서는 임의의 디렉터리에서 해당 파일에 액세스하게 됩니다.

그래서 좀 더 현실적으로 벤치마크 프로그램을 업데이트했습니다(참고: 이 프로그램에서는 사용자가 이미 디렉터리와 파일을 생성했다고 가정합니다. 프로그램을 실행하기 전에 페이지 캐시를 플러시해야 합니다).

package main

import (
    "fmt"
    "log"
    "os"
    "time"

    "math/rand"

    "github.com/1f604/util"
)

func main() {
    // Now time read and write times
    fmt.Println("Now creating 100 x 10kb files in a small directory.")
    start := time.Now()
    for j := 0; j < 100; j++ {
        num1 := rand.Intn(100)
        num2 := rand.Intn(2000)
        f, err := os.Create("/tmp/dir" + util.Int64_to_string(int64(num1)) + "/test" + util.Int64_to_string(int64(num2)) + ".txt")
        if err != nil {
            log.Fatal(err)
        }

        if err := f.Truncate(1e5); err != nil {
            log.Fatal(err)
        }
    }
    fmt.Println("Time taken:", time.Now().Sub(start))

    fmt.Println("Now reading 1000 random 10kb files in a small directory.")
    start = time.Now()
    list := [][]byte{}
    for j := 0; j < 1000; j++ {
        num1 := rand.Intn(100)
        num2 := rand.Intn(2000)
        contents, err := os.ReadFile("/tmp/dir" + util.Int64_to_string(int64(num1)) + "/" + util.Int64_to_string(int64(num2)) + ".txt")
        if err != nil {
            log.Fatal(err)
        }
        list = append(list, contents)
    }
    fmt.Println("Time taken:", time.Now().Sub(start))

    fmt.Println("Now creating 100 x 10kb files in a big directory.")
    start = time.Now()
    for j := 0; j < 100; j++ {
        f, err := os.Create("/tmp/bigdir/test" + util.Int64_to_string(int64(j)) + ".txt")
        if err != nil {
            log.Fatal(err)
        }

        if err := f.Truncate(1e5); err != nil {
            log.Fatal(err)
        }
    }
    fmt.Println("Time taken:", time.Now().Sub(start))

    fmt.Println("Now reading 1000 random 10kb files in a big directory.")
    start = time.Now()
    for j := 0; j < 1000; j++ {
        num := rand.Intn(200000)
        contents, err := os.ReadFile("/tmp/bigdir/" + util.Int64_to_string(int64(num)) + ".txt")
        if err != nil {
            log.Fatal(err)
        }
        list = append(list, contents)
    }
    fmt.Println("Time taken:", time.Now().Sub(start))
}

내 새로운 결과는 다음과 같습니다.


Now creating 100 x 10kb files in a small directory.
Time taken: 70.31699ms
Now reading 1000 random 10kb files in a small directory.
Time taken: 758.609004ms
Now creating 100 x 10kb files in a big directory.
Time taken: 32.695134ms
Now reading 1000 random 10kb files in a big directory.
Time taken: 574.266544ms

(이 결과는 페이지 캐시를 새로 고친 후 얻은 결과입니다)

이제 작은 디렉토리의 장점이 모두 사라진 것 같습니다. 반대로 이제는 큰 디렉토리가 더 빠른 것 같습니다.

이는 동일한 디렉토리에 반복적으로 액세스하면 후속 파일 액세스가 더 빨라진다는 것을 의미한다고 가정합니다. 또 다른 설명은 파일이 너무 작기 때문에(10kb) 물리적 장치의 동일한 블록에 있으므로 근처 파일에 액세스하는 것이 더 빠르다는 것입니다. 하지만 모르겠어요.

답변1

ext4에 비해 디렉토리의 파일이 너무 많습니까?

  • https://stackoverflow.com/questions/17537471/what-is-the-max-files-per-directory-in-ext4

    • 이는 파일 시스템 생성 중에 사용된 MKFS 매개변수에 따라 다릅니다. Linux 버전마다 기본값이 다르기 때문에 실제로 답이 없습니다.

    • 48비트 블록 주소 지정에 권장되는 절대 최대 파일 수는 281,474,976,710,656입니다.

    • ~에 따르면https://www.phoronix.com/news/EXT4-Linux-4.13-Work"단일 디렉토리에 약 1천만 개의 항목이 허용됩니다". 그러나 제한사항/문제가 있기는 하지만(예: GRUB는 이 파티션으로 부팅하지 못할 수 있음), Large_dir 기능을 사용하여 확장할 수 있습니다. 일화적인 경험 - 단일 디렉토리에 있는 3,200만 개가 넘는 파일에서 문제가 발생했습니다.

  • https://access.redhat.com/solutions/29894

    • 목차ext4에는 최대 64,000개의 하위 디렉터리가 있을 수 있습니다.
    • 매우 긴 파일 이름을 사용하는 경우 블록에 들어갈 수 있는 항목 수가 줄어들어 더 짧은 파일 이름을 사용하는 경우보다 "디렉터리 색인이 꽉 찼습니다" 오류가 더 빨리 나타납니다.
  • https://docs.kernel.org/admin-guide/ext4.html

    • 마운트된 ext4 파일 시스템에 대한 정보는 /sys/fs/ext4에서 찾을 수 있습니다.

관련 정보