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「単一のディレクトリに約1000万のエントリが許可されています」。ただし、制限/問題がありますが(たとえば、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にあります。

関連情報